• Linux网络编程(五)


    /*
    Linux网络编程(五)——多路IO复用之select()

    网络编程中,使用IO复用的典型场合: 1.当客户处理多个描述字时(交互式输入以及网络接口),必须使用IO复用。 2.一个客户同时处理多个套接口。 3.一个tcp服务程序既要处理监听套接口,又要处理连接套接口,一般需要用到IO复用。 4.如果一个服务器既要处理TCP,又要处理UDP,一般也需要用到IO复用。 5.如果一个服务器要处理多个服务或者多个协议,一般需要用到IO复用。 */ /*********************************************************************** 本程序功能: 使用单进程为多个客户端服务,接收到客户端发来的一条消息后,将该消息原样返回给客户端。 首先,建立一个监听套接字来接收来自客户端的连接。每当接收到一个连接后,将该连接套 接字加入客户端套接字数组,通过select实现多路复用。每当select返回时,检查套接字数 组的状态。并进行相应操作,如果是新的连接到来,则将新的连接套接字加入套接字数组, 如果客户端连接套接字变为可读,则对相应客户端进行响应。 ***********************************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/types.h> #include <netdb.h> #include <errno.h> #define SERV_PORT 2046 #define LISTENQ 32 #define MAXLINE 1024 int main(int argc, char **argv) { int i, maxi, maxfd, listenfd, connfd, sockfd; int nready, client[FD_SETSIZE]; ssize_t n; fd_set rset, allset; char buf[MAXLINE]; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; if((listenfd = socket(AF_INET, SOCK_STREAM,0))==-1){ fprintf(stderr,"Socket error:%s a",strerror(errno)); exit(1); } /* 服务器端填充 sockaddr结构*/ memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); /* 捆绑listenfd描述符 */ if(bind(listenfd,(struct sockaddr*)(&servaddr),sizeof(struct sockaddr))==-1){ fprintf(stderr,"Bind error:%s a",strerror(errno)); exit(1); } /* 监听listenfd描述符*/ if(listen(listenfd,LISTENQ)==-1){ fprintf(stderr,"Listen error:%s a",strerror(errno)); exit(1); } maxfd = listenfd; /* 初始化最大文件描述符*/ maxi = -1; /*client数组索引*/ for (i = 0; i < FD_SETSIZE; i++) client[i] = -1; /* -1代表未使用*/ FD_ZERO(&allset); FD_SET(listenfd, &allset); for ( ; ; ) { rset = allset; if((nready = select(maxfd+1, &rset, NULL, NULL, NULL))<0){ fprintf(stderr,"select Error "); exit(1); } if (FD_ISSET(listenfd, &rset)) { /*有新的客户端连接到来*/ clilen = sizeof(cliaddr); if((connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen))<0){ fprintf(stderr,"accept Error "); continue; } char des[sizeof(cliaddr)]; inet_ntop(AF_INET, &cliaddr.sin_addr, des, sizeof(cliaddr)); printf("new client: %s, port %d ",des,ntohs(cliaddr.sin_port)); for (i = 0; i < FD_SETSIZE; i++) if (client[i] < 0) { client[i] = connfd; /*保存新的连接套接字*/ break; } if (i == FD_SETSIZE){ fprintf(stderr,"too many clients"); exit(1); } FD_SET(connfd, &allset); /*将新的描述符加入监听数组中*/ if (connfd > maxfd) maxfd = connfd; if (i > maxi) maxi = i; /*当前client数组最大下标值*/ if (--nready <= 0) continue; /*可读的套接字全部出来完了*/ } for (i = 0; i <= maxi; i++) { /*检查所有已经连接的客户端是否由数据可读*/ if ( (sockfd = client[i]) < 0) continue; if (FD_ISSET(sockfd, &rset)) { if ( (n = read(sockfd, buf, MAXLINE)) == 0) {/*客户端主动断开了连接*/ close(sockfd); FD_CLR(sockfd, &allset); client[i] = -1; } else write(sockfd, buf, n); if (--nready <= 0) break; /*可读的套接字全部出来完了*/ } } } }
    /*
    使用IO复用客户端
    */
    /***********************************************************************
    本程序(客户端)功能:
    1.向服务器发起连接请求,并从标准输入stdin获取字符串,将字符串发往服务器。
    2.从服务器中接收字符串,并将接收到的字符串输出到标准输出stdout.
    =========================================================================
    问题:由于既要从标准输入获取数据,又要从连接套接字中读取服务器发来的数据。
          为避免当套接字上发生了某些事件时,程序阻塞于fgets()调用,使用select
          实现多路IO复用,或等待标准输入,或等待套接口可读。这样一来,若服务器
          进程终止,客户端能马上得到通知。
    =========================================================================
    对于客户端套接口,需要处理以下三种情况:
    1.服务器端发送了数据过来,套接口变为可读,且read返回值大于0
    2.服务器端发送了一个FIN(服务器进程终止),套接口变为可读,且read返回值等于0
    3.服务器端发送了一个RST(服务器进程崩溃,且重新启动,此时服务器程序已经不认
      识之前建立好了的连接,所以发送一个RST给客户端),套接口变为可读,且read返回-1
      错误码存放在了errno
    ***********************************************************************/
    //使用多路复用select的客户端程序
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <sys/types.h>
    #include <netdb.h>
    #define SERV_PORT 2046 
    #define MAXLINE 1024
    #define max(x,y) (x)>(y) ? (x):(y)
    void str_cli(FILE *fp, int sockfd);
    int
    main(int argc, char **argv)
     {
        int     sockfd;
        struct sockaddr_in servaddr;
        if (argc != 2){
            fprintf(stderr,"usage: tcpcli <IPaddress>
    a");
            exit(0);
        }
        if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){
            fprintf(stderr,"Socket error:%s
    a",strerror(errno));
            exit(1);
        }
        /*客户程序填充服务端的资料*/
          memset(&servaddr,0,sizeof(servaddr));
          servaddr.sin_family=AF_INET;
          servaddr.sin_port=htons(SERV_PORT);
          if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
                fprintf(stderr,"inet_pton Error:%sa
    ",strerror(errno));
                exit(1);
          }
           /* 客户程序发起连接请求*/ 
          if(connect(sockfd,(struct sockaddr *)(&servaddr),sizeof(struct sockaddr))==-1){
                fprintf(stderr,"connect Error:%sa
    ",strerror(errno));
                exit(1);
          }
         str_cli(stdin, sockfd);     /*重点工作都在此函数*/
         exit(0);
     }
    
    void
    str_cli(FILE *fp, int sockfd)
    {
        int            maxfdp1, stdineof;
        fd_set        rset;/*用于存放可读文件描述符集合*/
        char        buf[MAXLINE];
        int        n;
        stdineof = 0;/*用于标识是否结束了标准输入*/
        FD_ZERO(&rset);
        while(1){
            if (stdineof == 0)
                FD_SET(fileno(fp), &rset);
            FD_SET(sockfd, &rset);
            maxfdp1 = max(fileno(fp), sockfd) + 1;
            if(select(maxfdp1, &rset, NULL, NULL, NULL)<0){/*阻塞,直到有数据可读或出错*/
                fprintf(stderr,"select Error
    ");
                exit(1);
            }
            if (FD_ISSET(sockfd, &rset)) {    /*套接口有数据可读*/
                if ( (n = read(sockfd, buf, MAXLINE)) == 0) {
                    if (stdineof == 1)
                        return;        /*标准输入正常结束*/
                    else
                        fprintf(stderr,"str_cli: server terminated prematurely");
                }
                write(fileno(stdout), buf, n);/*将收到的数据写到标准输出*/
            }
            if (FD_ISSET(fileno(fp), &rset)) {  /*标准输入可读*/
                if ( (n = read(fileno(fp), buf, MAXLINE)) == 0) {
                    stdineof = 1;
                    /*向服务器发送FIN,告诉它,后续已经没有数据发送了,但仍为读而开放套接口*/
                    if(-1==shutdown(sockfd, SHUT_WR)){
                        fprintf(stderr,"shutdown Error
    ");
                    }
                    FD_CLR(fileno(fp), &rset);
                    continue;
                }
                write(sockfd, buf, n);
            }
        }
    }
    /**********************************************************
    注:关于close与shutdown的区别:
        close()将描述字的访问计数减1,仅在访问计数为0时才关闭套接字。
        而shutdown可以激发TCP的正常连接终止系列,而不管访问计数。
        close()终止了数据传输的两个方向:读、写。
    ***********************************************************/

     运行结果:

    服务器端:

    linux-code#./selectSrv &
    [1] 5998
    linux-code#new client: 127.0.0.1, port 53497
    new client: 192.168.1.162, port 55807

    客户端1:

    linux-code# ./selectCli 127.0.0.1
    hello,world!

    hello,world!

    客户端2:

    linux-code# ./selectCli 192.168.1.162
    hello,world!
    hello,world!

    利用netstat查看套接口连接情况如下:

    linux-code# netstat -at

    tcp 0 0 *:2046 *:* LISTEN
    tcp 0 0 localhost.localdom:2046 localhost.localdo:53497 ESTABLISHED
    tcp 0 0 ubuntu:2046 ubuntu:55807 ESTABLISHED
    tcp 0 0 ubuntu:55807 ubuntu:2046 ESTABLISHED
    tcp 0 0 localhost.localdo:53497 localhost.localdom:2046 ESTABLISHED

  • 相关阅读:
    统计插件无效问题
    Hearthbuddy跳过ConfigurationWindow窗口
    炉石兄弟更新修复记录(至2021年5月)
    HearthbuddyHelper已经开源
    2020年8月28日
    交易机制的实现
    Silverfish重构【2】-限制惩罚为某一behavior特有
    Silverfish重构【1】-发现卡牌的函数
    99-Flagstone Walk
    Behavior控场模式的解析(下)
  • 原文地址:https://www.cnblogs.com/jjdiaries/p/3422325.html
Copyright © 2020-2023  润新知