Write FD set 的用途

一段時間,我其實都不知道 select 和 epoll 這一類 function 裏面的 writefds 到底有什麼用。read fd set很容易理解,那就是有東西要讀,或者白話一點,收到封包了。那 write fd set 到底要幹嘛呢?直到今天遇到了一個情境我才使用了它 ...

在寫 TCP socket 程式的時候,會使用到 connect 這個函式。一般來說, connect 這個函式是 blocking 的,也就是會一直到事情做完才會返回。但如果將 socket 設定成 non-blocking 模式呢?這時候 connect 會立刻返回並將 error no 設定為 EINPROGRESS。下面是"男人"的解釋:

EINPROGRESS
              The  socket  is  nonblocking and the connection cannot be completed immediately. It is possible to select(2) or poll(2) for completion by  selecting  the  socket  for  writing.   After select(2) indicates writability, use getsockopt(2) to read  the SO_ERROR option at level SOL_SOCKET to determine whether connect() completed successfully  (SO_ERROR is zero) or unsuccessfully (SO_ERROR is one of the usual  error codes listed here, explaining the reason for the failure).


好了,這時候我們可以看到 select 可以用來判斷 connect 是否完成。範例程式碼如下:

#include       // for printf() and fprintf() 
#include  // for socket(), connect(), send(), and recv() 
#include   // for sockaddr_in and inet_addr() 
#include      // for atoi() and exit() 
#include      // for memset() 
#include      // for close() 
#include       // for fcntl() 

static int make_socket_non_blocking (int sfd)
{
  int flags, s;

  flags = fcntl (sfd, F_GETFL, 0);
  if (flags == -1)
    {
      perror ("fcntl");
      return -1;
    }

  flags |= O_NONBLOCK;
  s = fcntl (sfd, F_SETFL, flags);
  if (s == -1)
    {
      perror ("fcntl");
      return -1;
    }

  return 0;
}

int main(int argc, char *argv[])
{
    int sock;                        // Socket descriptor 
    struct sockaddr_in servAddr;     // Server address 
    unsigned short servPort;         // Server port 
    char *servIP;                    // Server IP address (dotted quad) 
    
    struct timeval tv;
    fd_set write_fds;
    int ready;

    if ( ( argc < 2 ) || (argc > 3 ) )    // Test for correct number of arguments 
    {
       fprintf( stderr, "Usage: %s  []\n", argv[0] );
       exit(1);
    }

    servIP = argv[1];             // First arg: server IP address (dotted quad) 

    if ( argc == 3 )
    {
        // Use given port, if any 
        servPort = atoi(argv[2]); 
    }
    else
    {
        // 7 is the well-known port for the echo service
        servPort = 50000;  
    } 

    // Create a reliable, stream socket using TCP 
    if ( ( sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP ) ) < 0 )
    {
        perror("socket() failed");
        exit(1);
    }
    
    make_socket_non_blocking( sock );
    
    // Construct the server address structure 
    memset(&servAddr, 0, sizeof(servAddr));         // Zero out structure 
    servAddr.sin_family      = AF_INET;             // Internet address family 
    servAddr.sin_addr.s_addr = inet_addr(servIP);   // Server IP address 
    servAddr.sin_port        = htons(servPort);     // Server port 

    connect( sock, (struct sockaddr *) &servAddr, sizeof( servAddr ) );
    
    while(1)
    {
        tv.tv_sec = 0;
        tv.tv_usec = 1000;
        FD_ZERO( &write_fds );
        FD_SET( sock, &write_fds );
        
        ready = select( sock + 1, NULL, &write_fds, NULL, &tv );
        
        if( FD_ISSET( sock, &write_fds ) )
        {
            printf( "Connection done.\n" );
            break;
        }
        else
        {
            printf( "Timeout.\n" );
        }
    }    
    
    printf("\n");    // Print a final linefeed 

    close(sock);
    return 0;
}

最後,別問我 exceptfds 是什麼 ...

留言

這個網誌中的熱門文章

如何將Linux打造成OpenFlow Switch:Openvswitch

我弟家的新居感恩禮拜分享:善頌善禱

Linux Virtual Interface: TUN/TAP