服务器编程入门(11)TCP并发回射服务器实现 - 单线程select实现

问题聚焦:

当客户端阻塞于从标准输入接收数据时,将读取不到别的途径发过来的必要信息,如TCP发过来的FIN标志。

因此,进程需要内核一旦发现进程指定的一个或多个IO条件就绪(即输入已准备好被读取,或者描述符已能承接更多的输出),它就通知进程。
这个机制称为I/O复用,这是由select, poll, epoll函数支持的。


编译环境:
    Ubuntu12.04  g++

需求描述:
  1. 单进程,IO复用,实现多个连接同时监听和收发信息
  2. 当服务器进程一终止,客户就能马上得到结果(select +shutdown实现)
  3. 当客户端使用"exit"命令或者Cirl+C结束进程时,服务器可以立即感应到,并关闭当前接口(select+close实现
来看一下实现后的运行效果:


 
步骤:
  1. 服务器连接了第一个客户,并收发消息“hello world”
  2. 服务器连接了第二个客户,并收发消息“hello select”
  3. 服务器从第一个客户收发消息“hello world again”
  4. 服务器从第二个客户收发消息“hello select again”
  5. 第二个客户关闭连接
  6. 第一个客户关闭连接

在了解select实现之前,先复习一下之前了解的IO模型

五种IO模型
  • 阻塞式IO
  • 非阻塞式IO
  • IO复用
  • 信号驱动式IO
  • 异步IO
对比:




一个输入操作通常包括两个不同的阶段:
  • 等待数据准备好
  • 从内核向进程复制数据
对于TCP来说,这两步分别为:
  • 等待数据从网络中到达,当所等待分组到达时,它被复制到内核中的某个缓冲区
  • 把数据从内核缓冲区复制到应用进程缓冲区


select

流程:
 
  1. select主要通过维护两个数组,来实现端口的轮询:
  2. client[]数组,记录有哪些连接已经建立
  3. rset[]数组,记录有注册哪些端口,需要监听
  4. 当rset数组中注册的端口被激活,这时将端口号放到client数组中,稍后遍历client[]数组,处理连接上的数据


代码实现:

服务器端:
  1 #include "mtserver.h"
  2 
  3 int main(int argc, char* argv[])
  4 {
  5     checkArgc(argc, 2);
  6     
  7     const char* ip = argv[1];
  8     int port = atoi( argv[2] );
  9 
 10     /* declare socket*/
 11     int listenfd, connfd, sockfd;
 12     int ret;
 13     
 14     /* initialize listen socket*/
 15     mySocket(listenfd);
 16     
 17     /* server address */
 18     struct sockaddr_in servaddr;
 19     initSockAddr(servaddr, ip, port);
 20     
 21     /* bind */
 22     myBind(listenfd,
 23             (struct sockaddr*)&servaddr,
 24            sizeof(servaddr));
 25     
 26     /* listen */
 27     myListen(listenfd, 5);
 28     
 29     /* handle SIGCHLD signal*/
 30     //signal(SIGCHLD, handle_sigchild);
 31     
 32     /* waiting for connecting */
 33     pid_t chipid;
 34     socklen_t clilen;
 35     struct sockaddr_in cliaddr;
 36 
 37     /* select initialize */
 38     int maxfd, maxi, i;
 39     bool toclose;
 40     int nready, client[FD_SETSIZE];
 41     fd_set rset, allset;
 42     
 43     maxfd = listenfd;
 44     maxi = -1;
 45     for ( i=0; i < FD_SETSIZE; i++ )
 46         client[i] = -1;
 47 
 48     FD_ZERO(&allset);
 49     FD_SET(listenfd, &allset);
 50     
 51     printf("Waiting for connecting...\n");
 52     
 53     for(;;) {
 54         rset = allset;
 55         if ( (nready=select(maxfd+1, &rset, NULL, NULL, NULL)) < 0 ) {
 56             fprintf(stderr,
 57                     "select failed.%s\n",
 58                     strerror(errno));
 59             continue;
 60         }
 61         
 62         /* handle listen fd and no recv or respond */
 63         if (FD_ISSET(listenfd, &rset)) {
 64             clilen = sizeof(cliaddr);
 65             connfd = myAccept(listenfd,
 66                               (struct sockaddr*)&cliaddr,
 67                               &clilen);
 68             printf("Connection is established with sockfd: %d\n",
 69                    connfd);
 70             for ( i = 0; i < FD_SETSIZE; i++) {
 71                 if ( client[i] < 0 ) {
 72                     client[i] = connfd;
 73                     break;
 74                 }
 75             }
 76             
 77             if (i == FD_SETSIZE) {
 78                 fprintf(stderr,
 79                         "too many clients\n"
 80                         );
 81                 break;
 82             }
 83             
 84             FD_SET( connfd, &allset );
 85             if ( connfd > maxfd ) {
 86                 maxfd = connfd;
 87             }
 88             if ( i > maxi) {
 89                 maxi = i;
 90             }
 91             
 92             if (--nready <= 0) {
 93                 continue;
 94             }
 95         }
 96         
 97         /* handle accept fds(client[]) and handle recv or respond msg */
 98         for ( i = 0; i <= maxi; i++) {
 99             if ( (sockfd = client[i]) < 0 )
100                 continue;
101             if ( FD_ISSET(sockfd, &rset) ) {
102                 if( (toclose = handle_recv(sockfd))) {
103                     printf("Client close this connection: %d\n" ,
104                            sockfd);
105                     close(sockfd);
106                     FD_CLR(sockfd, &allset);
107                     client[i] = -1;
108                 }
109                 
110                 if (--nready <= 0) 
111                     break;
112             }
113         }
114     }
115 }
116 
117 
118 bool handle_recv(int connfd) {
119      
120     char recvbuf[BUFSIZE];
121 
122     memset( recvbuf, '\0', BUFSIZE );
123     if ( recv(connfd, recvbuf,BUFSIZE,0) != 0) {
124         if (!strcmp(recvbuf, "exit"))
125             return true;
126         fprintf(stderr,"recv msg: \"%s\" from connfd:%d\n", recvbuf, connfd);
127         send(connfd, recvbuf, strlen(recvbuf), 0);
128         fprintf(stderr,"send back: \"%s\" to connfd:%d\n\n", recvbuf, connfd);
129     }
130     else
131         return true;
132     return false;
133 }

 


客户端:
#include "mtclient.h"

int main(int argc, char* argv[])
{   
    checkArgc(argc, 2);
    
    int port = atoi(argv[2]);
    char* ip = argv[1];
    

    int sockfd;
    struct sockaddr_in servaddr;

    mySocket(sockfd);

    initSockAddr(servaddr,ip, port);
    
    myConnect(sockfd,
              (struct sockaddr*)&servaddr,
              sizeof(servaddr));
    
    handle_msg(sockfd);
    exit(0);
    
}


void handle_msg(int sockfd) {

    char sendbuf[BUFSIZE];
    char recvbuf[BUFSIZE];
    
    int maxfdpl, ret;
    fd_set rset;
    int normalTermi = 0;
    
    FD_ZERO(&rset);

    while(1) {
         memset( sendbuf, '\0', BUFSIZE );
        memset( recvbuf, '\0', BUFSIZE );
        
        if (normalTermi == 0)
            FD_SET( 0, &rset );

        FD_SET( sockfd, &rset );        
        maxfdpl = sockfd + 1;

        if(DEBUG)
            printf("Debug: waiting in select\n");
        if ( select( maxfdpl, &rset, NULL, NULL, NULL) < 0 ) {
            fprintf(stderr,
                    "select failed.%s\n",
                    strerror(errno));
        }
        if(DEBUG)
            printf("Debug: after select\n");

        if (FD_ISSET( sockfd, &rset )) {
            if (recv(sockfd, recvbuf, BUFSIZE, 0) == 0) {

                if(DEBUG)
                    printf("Debug: ready to quit, normalTermi: %d\n" ,
                           normalTermi);

                if (normalTermi == 1) {
                    printf("handle_msg: normal terminated.\n");
                    return;
                }
                else {
                    printf("handle_msg: server terminated.\n");
                    exit(0);
                }
            }
            fprintf(stderr,
                    "recv back: %s\n",
                    recvbuf);
        }
        else if ( FD_ISSET( 0, &rset ) ) {
            gets(sendbuf);
            if (strlen(sendbuf) > 0) {
                send(sockfd, sendbuf, strlen(sendbuf), 0);
                if ( !strcmp(sendbuf, "exit") ) {
                    normalTermi = 1;
                    shutdown(sockfd, SHUT_WR);
                    FD_CLR(0, &rset);
                    continue;
                }
            }
        }
    }
    close( sockfd );
    return;
}

 



问题:
1 监听标准输入的描述符?
解决:标准输入描述符:0

2 当客户端发送所有消息,即可关闭连接,但是如果这时候调用close方法,会导致接收不到仍在传送过来的信息。
方案:需要一种关闭TCP连接其中一半的方法,也即是说,我们想给服务器发送一个FIN,告诉它我们已经完成了数据发送,但是仍然保持套接字描述符打开以便读取。
完成这个功能的函数为shutdown。
shutdown函数可以不管描述符的引用计数,就激发TCP的正常连接终止序列。
关闭一半的图示:
 

函数声明:
#include <sys/socket.h>
int shutdown(int sockfd, int howto);
howto:取值SHUT_RD(关闭这一端的读,不再读取连接上的数据) 
            SHUT_WR(关闭这一端的写,不再往连接上写数据) 
            SHUT_RDWR(关闭这一端的读和写)


3 套接字描述符的第一个可用描述符是多少?
答案:3。0 1 2分别为标准输入,标准输出,标准错误输出。

4 服务器进程终止后的动作?
这里需要知道的一点是,当服务器进程一终止,就会对客户进程发送一个FIN信号,这时套接字连接可读,read返回0


参考资料:
《Linux高性能服务器编程》
《UNIX网络编程 卷1:套接字联网API(第3版)》


 

posted @ 2014-03-31 11:58  suzhou  阅读(452)  评论(0编辑  收藏  举报