C: 使用 Select 监控两个套接字

C: Using Select to monitor two sockets

我正在编写一个连接到多个客户端的服务器。目前,我正在 SOCK_STREAM 套接字上使用 accept 来连接新客户端。我希望能够接受来自客户端的查询,这些客户端将数据报发送到服务器的 SOCK_DGRAM 套接字。由于接受只是挂起,直到客户端发送连接到该套接字的请求,我需要一种方法来监视两个套接字,直到一个接收到来自客户端的请求,然后才能处理该请求并 return 进行监视插座。我不需要立即处理请求(即,如果客户端在另一个客户端请求连接后发送查询,则查询客户端将不得不等待服务器完成连接客户端的请求,反之亦然)。我知道套接字可以提醒服务器有多少套接字准备好 read/write 但它如何告诉服务器哪个套接字准备好了? IE。哪个套接字准备好读取,哪个准备好写入?这是我的尝试,我知道侦听器在连接后总是需要写入,而 udpSock 总是需要读取。

 while(1){
        retval = select(max,&readSockSet, &writeSockSet, NULL, &timeout);
        if(retval){
            if(FD_ISSET(listener, &writeSockSet))
                printf("Client requesting to connect...\n");
            if(FD_ISSET(udpSock, &readSockSet))
                printf("Client query incoming...\n");
        }
    }

Select 套接字函数通常有一个可选参数来限制您在套接字或套接字列表上阻塞的时间。

在php和ruby中select()可以查看套接字列表

我想你想要这样的东西......(因为你使用的是 C 而不是 C++,你可以用数组或其他一些基于 C 的整数集数据结构替换向量;我是为了更清楚起见,在示例中将其保留为矢量)

int listenSock = /* the socket that you want to call accept() on */
int udpSock  = /* your UDP socket */
std::vector<int> clientSocks;  /* list of connected client sockets, initially empty */

while(1){
    int maxSock = -1;
    fd_set readSockSet;
    FD_ZERO(&readSockSet);
    FD_SET(listenSock, &readSockSet);  // listenSock will be ready-for-ready when it's time to accept()
    if (listenSock > maxSock) maxSock = listenSock;

    FD_SET(udpSock, &readSockSet);  // let us know when a UDP packet arrives
    if (udpSock > maxSock) maxSock = udpSock;

    for (int i=0; i<clientSocks.size(); i++)
    {
       FD_SET(clientSocks[i], &readSockSet);
       if (clientSocks[i] > maxSock) maxSock = clientSocks[i];
    }

    struct timeout = {5, 0};  // five-second timeout, just to demonstrate
    retval = select(maxReadSock+1, &readSockSet, NULL, NULL, &timeout);
    if(retval >= 0)
    {
        if(FD_ISSET(listenSock, &writeSockSet))
        {
            printf("Client requesting to connect...\n");
            struct sockaddr_in sai;
            socklen_t saiSize = sizeof(sai);
            int newClientSock = accept(listenSock, &sai, &saiSize);
            if (newClientSock >= 0) clientSocks.push_back(newClientSock);
                               else perror("accept");
        }

        if (FD_ISSET(udpSock, &readSockSet))
        {
            printf("UDP packet incoming...\n");
            char buf[2000];
            int numBytesReceived = recv(udpSock, buf, sizeof(buf), 0);
            if (numBytesReceived >= 0) 
            {
               printf("Received %i-byte UDP packet!\n", numBytesReceived);
            }
            else perror("recv(UDP)");
        }

        // iterate backwards in case we need to remove a disconnected client socket
        for (int i=clientSocks.size()-1; i>=0; i--)
        {
           if (FD_ISSET(clientSocks[i], &readSockSet))
           {
               printf("TCP data incoming from socket #%i...\n", clientSocks[i]);
               char buf[2000];
               int numBytesReceived = recv(clientSocks[i], buf, sizeof(buf), 0);
               if (numBytesReceived > 0) 
               {
                  printf("Received %i bytes of TCP data from client socket #%i!\n", numBytesReceived, clientSocks[i]);
               }
               else 
               {
                  if (numBytesReceived == 0) printf("Client with socket #%i disconnected.\n", clientSocks[i]);
                                        else perror("recv(TCP)");
                  close(clientSocks[i]);
                  clientSocks.erase(i); // remove closed socket at position i
               }
           }
        }
    }
    else perror("select");
}

不是在 accept() 上阻塞,而是使用 select() 来告诉您客户端何时挂起,这样您就可以在不阻塞的情况下调用 accept()。然后就可以同时监控TCP和UDP套接字了。您拥有的代码已经在正确的轨道上完成该任务,但是您正在使用 writing fdset 来检测何时调用 accept() 何时应该使用 读取 fdset,并且您不会在每次循环迭代时重置 fdset 或超时,因为 select() 修改了它们。

试试这个:

fd_set readSockSet;
timeval timeout;
int smax = max(listener, udpSock);

while (1)
{
    FD_ZERO(&readSockSet);
    FD_SET(listener, &readSockSet);
    FD_SET(udpSock, &readSockSet);
    // add connected TCP clients, if needed...

    timeout.tv_sec = ...;
    timeout.tv_usec = ...;

    retval = select(smax+1, &readSockSet, NULL, NULL, &timeout);
    if (retval > 0)
    {
        if (FD_ISSET(listener, &readSockSet))
        {
            printf("TCP Client requesting to connect...\n");
            // call accept() and do something with the client socket ...
        }

        if (FD_ISSET(udpSock, &readSockSet))
        {
            printf("UDP Client query incoming...\n");
            // call recv()/read() to read the datagram ...
        }

        // check connected TCP clients, if needed...
    }
    else if (retval < 0)
    {
        // check errno/WSAGetLastError(), call perror(), etc ...
    }
}