如何在连接和 select 上编程非阻塞套接字?

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

我正在尝试编写一个使用非阻塞 TCP 套接字和 select() 连接的 C 代码。当我阅读有关 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).

有没有示例代码可以参考?虽然这是一个很老的问题,但我没有看到任何人 post 完整的工作代码。有人建议使用 connect 两次,但我不知道具体如何。

当然,下面是一个小 C 程序,它使用 non-blocking TCP 连接连接到 www.google.com 的端口 80,向它发送一个无意义的字符串,然后打印出它返回的响应:

#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] = '[=10=]';  // 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;
}