csocketstcpnonblockingposix-select

How to program non-blocking socket on connect and select?


I am trying to write a C code that connects using non-blocking TCP socket along with select(). When I read the man page about EINPROGRESS, I feel a little bit confused.

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).

Is there any sample code I can refer to? Although it is a pretty old question, I don't see anyone post a complete working code. Some suggest to use connect twice but I don't know exactly how.


Solution

  • Sure, below is a little C program that uses a non-blocking TCP connect to connect to www.google.com's port 80, send it a nonsense string, and print out the response it gets back:

    #include <stdio.h>
    #include <netdb.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/select.h>
    #include <sys/socket.h>
    
    static void SendNonsenseCommand(int sock)
    {
       const char sendString[] = "Hello Google!  How are you!\r\n\r\n";
       if (send(sock, sendString, sizeof(sendString), 0) != sizeof(sendString)) perror("send()");
    }
    
    int main(int argc, char ** argv)
    {
       // Create a TCP socket
       const int sock = socket(AF_INET, SOCK_STREAM, 0);
       if (sock < 0) {perror("socket"); return 10;}
    
       // Set the TCP socket to non-blocking mode
       const int flags = fcntl(sock, F_GETFL, 0);
       if (flags < 0) {perror("fcntl(F_GETFL)"); return 10;}
       if (fcntl(sock, F_SETFL, flags|O_NONBLOCK) < 0) {perror("fcntl(F_SETFL)"); return 10;}
    
       // Get the IP address of www.google.com
       struct hostent * he = gethostbyname("www.google.com");
       if (he == NULL) {printf("Couldn't get a hostent for www.google.com\n"); return 10;}
    
       // Start a non-blocking/asynchronous TCP connetion to port 80
       struct sockaddr_in saAddr;
       memset(&saAddr, 0, sizeof(saAddr));
       saAddr.sin_family = AF_INET;
       saAddr.sin_addr   = *(struct in_addr*)he->h_addr;
       saAddr.sin_port   = htons(80);
    
       const int connectResult = connect(sock, (const struct sockaddr *) &saAddr, sizeof(saAddr));
       int isTCPConnectInProgress = ((connectResult == -1)&&(errno == EINPROGRESS));
       if ((connectResult == 0)||(isTCPConnectInProgress))
       {
          if (isTCPConnectInProgress == 0) SendNonsenseCommand(sock);
    
          // TCP connection is happening in the background; our event-loop calls select() to block until it is ready
          while(1)
          {
             fd_set socketsToWatchForReadReady, socketsToWatchForWriteReady;
             FD_ZERO(&socketsToWatchForReadReady);
             FD_ZERO(&socketsToWatchForWriteReady);
    
             // While connecting, we'll watch the socket for ready-for-write as that will tell us when the
             // TCP connection process has completed.  After it's connected, we'll watch it for ready-for-read
             // to see what Google's web server has to say to us.
             if (isTCPConnectInProgress) FD_SET(sock, &socketsToWatchForWriteReady);
                                    else FD_SET(sock, &socketsToWatchForReadReady);
    
             int maxFD = sock;  // if we were watching multiple sockets, we'd compute this to be the max value of all of them
    
             const int selectResult = select(maxFD+1, &socketsToWatchForReadReady, &socketsToWatchForWriteReady, NULL, NULL);
             if (selectResult >= 0)
             {
                if ((FD_ISSET(sock, &socketsToWatchForWriteReady))&&(isTCPConnectInProgress))
                {
                   printf("Socket is ready for write!  Let's find out if the connection succeeded or not...\n");
    
                   struct sockaddr_in junk;
                   socklen_t length = sizeof(junk);
                   memset(&junk, 0, sizeof(junk));
                   if (getpeername(sock, (struct sockaddr *)&junk, &length) == 0)
                   {
                      printf("TCP Connection succeeded, socket is ready for use!\n");
                      isTCPConnectInProgress = 0;
    
                      SendNonsenseCommand(sock);
                   }
                   else
                   {
                      printf("TCP Connection failed!\n");
                      break;
                   }
                }
    
                if (FD_ISSET(sock, &socketsToWatchForReadReady))
                {
                   char buf[512];
                   const int numBytesReceived = recv(sock, buf, sizeof(buf)-1, 0);
                   if (numBytesReceived > 0)
                   {
                      buf[numBytesReceived] = '\0';  // ensure NUL-termination before we call printf()
                      printf("recv() returned %i:  [%s]\n", numBytesReceived, buf);
                   }
                   else if (numBytesReceived == 0)
                   {
                      printf("TCP Connection severed!\n");
                      break;
                   }
                   else perror("recv()");
                }
             }
             else {perror("select()"); return 10;}
          }
       }
       else perror("connect()");
    
       close(sock);  // just to be tidy
       return 0;
    }