select+NONBLOCK

    技术2025-04-26  4

    补充一点:只有在使用epoll ET(Edge Trigger)模式的时候,才需要关注数据是否读取完毕了。使用select或者epoll的LT模式,其实根本不用关注数据是否读完了,select/epoll检测到有数据可读去读就OK了。

     

    这里有两种做法:

     

    1. 针对TCP,调用recv方法,根据recv方法的返回值,如果返回值小于我们指定的recv buffer的大小,则认为数据已经全部接收完成。在Linux epoll的manual中,也有类似的描述:

     

    For stream-oriented files (e.g., pipe, FIFO, stream socket), the condition that the read/write I/O space is exhausted can also be detected  by  checking the  amount  of data read from / written to the target file descriptor.  For example, if you call read(2) by asking to read a certain amount of data and read(2) returns a lower number of bytes, you can be sure of having exhausted the read I/O space for the file descriptor.  The same is true when  writing using write(2).  (Avoid this latter technique if you cannot guarantee that the monitored file descriptor always refers to a stream-oriented file.)

     

    2. TCP和UDP都适用。将socket设成NONBLOCK(使用fcntl函数),然后select到该socket可读之后,使用read/recv来读取数据。当函数返回-1,同时errno是EAGAIN或EWOULDBLOCK的时候,表示数据已经全部读取完毕。

     

    实验结论:

     

    第一种方法是错误的。简单来说,如果发送了4K字节,recv的时候使用一个2K的buffer,那么,recv两次之后就再也没有数据可以recv了,此时recv就会block。永远不会出现recv返回值小于2K的情况(注:recv/read返回0表示对端socket已经关闭)。

     

    所以推荐使用第二种方法,第二种方法正确而且对TCP和UDP都管用。事实上,不论什么平台编写网络程序,我认为都应该使用select+NONBLOCK socket的方式。这样可以保证你的程序至少不会在recv/send/accept/connect这些操作上发生block从而将整个网络服务都停下来。不好的地方就是不太利于Debug,如果是block的socket,那么GDB一跟就能知道阻塞在什么地方了。。。

     

    其实所谓读取完毕指的是kernel中该socket对应的input data queue中的数据全部被读取了出来,从而该socket在kernel中被设置成了unreadable的状态。所以如果比如在局域网内,sender一直不断发送数据,则select到recv socket可读之后,我们就可以一直不停的读取到数据。所以,如果一个网络程序接收端想一次把数据全部接收完并且将所有接收到的数据都保存在内存中的话,就需要考虑到这种情况,避免占用过多的内存。

     

    下面是测试代码,代码中client读取了4K了之后就退出了,因为sender每次发送4K,所以client select到一次readable之后,就只会读取到4K。

    Client.c:

    #include  < stdio.h > #include  < stdlib.h > #include  < errno.h > #include  < string .h > #include  < netdb.h > #include  < sys / types.h > #include  < netinet / in .h > #include  < sys / socket.h > #include  < fcntl.h > #include  < unistd.h > #include  < sys / select.h > #define  SERVPORT 3333 #define  RECV_BUF_SIZE 1024 void  setnonblocking( int  sock){     int  opts;    opts = fcntl(sock,F_GETFL);     if (opts < 0 )    {        perror( " fcntl(sock,GETFL) " );        exit( 1 );    }    opts  =  opts | O_NONBLOCK;     if (fcntl(sock,F_SETFL,opts) < 0 )    {        perror( " fcntl(sock,SETFL,opts) " );        exit( 1 );    }} int  main( int  argc,  char   * argv[]){     int  sockfd, iResult;     char  buf[RECV_BUF_SIZE];     struct  sockaddr_in serv_addr;    fd_set readset, testset;    sockfd  =  socket(AF_INET, SOCK_STREAM,  0 );    setnonblocking(sockfd);    memset( & serv_addr,  0 sizeof (serv_addr));    serv_addr.sin_family = AF_INET;    serv_addr.sin_port = htons(SERVPORT);    serv_addr.sin_addr.s_addr  =  inet_addr( " 127.0.0.1 " );    connect(sockfd, ( struct  sockaddr  * ) & serv_addr,  sizeof (serv_addr));    FD_ZERO( & readset);    FD_SET(sockfd,  & readset);    testset  =  readset;    iResult  =  select(sockfd  +   1 & testset, NULL, NULL, NULL);     while  ( 1 ) {        iResult  =  recv(sockfd, buf, RECV_BUF_SIZE,  0 );         if  (iResult  ==   - 1 ) {             if  (errno  ==  EAGAIN  ||  errno  ==  EWOULDBLOCK) {                printf( " recv finish detected, quit.../n " );                 break ;            }        }        printf( " Received %d bytes/n " , iResult);    }    printf( " Final iResult: %d/n " , iResult);     return   0 ;}

     

     

    Server.c:

    #include  < stdio.h > #include  < stdlib.h > #include  < errno.h > #include  < string .h > #include  < sys / types.h > #include  < netinet / in .h > #include  < sys / socket.h > #include  < sys / wait.h > #define  SERVPORT 3333 #define  BACKLOG 10 #define  SEND_BUF_SIZE 4096 int  main( int  argc,  char   * argv[]){     int  sockfd, client_fd, i;     struct  sockaddr_in my_addr;     char   * buffer  =  NULL;    sockfd  =  socket(AF_INET, SOCK_STREAM,  0 );    memset( & my_addr,  0 sizeof (my_addr));    my_addr.sin_family = AF_INET;    my_addr.sin_port = htons(SERVPORT);    my_addr.sin_addr.s_addr  =  inet_addr( " 127.0.0.1 " );    bind(sockfd, ( struct  sockaddr  * ) & my_addr,  sizeof ( struct  sockaddr));    listen(sockfd, BACKLOG);    client_fd  =  accept(sockfd, NULL, NULL);    buffer  =  malloc(SEND_BUF_SIZE);      for  (i  =   0 ; i  <   100 ; i ++ ) {        send(client_fd, buffer, SEND_BUF_SIZE,  0 );        sleep( 1 );    }    sleep( 10 );    close(client_fd);    close(sockfd);    free(buffer);     return   0 ;}
    最新回复(0)