Search code examples
winsockwinsock2

How to deal with SOCKET in select method?


I saw this example in IBM docs on how to use select method for a server program, I would like to something similar to that on windows without using vectors and unordered_map but the problem I am facing is that windows uses SOCKET for socket descriptors and linux uses int although I can cast the windows socket into an integer it is not recommended and the socket value is bigger than FD_SETSIZE, that being said the for loop ends before reaching the server socket descriptor and the function ends up being kind of useless

int Server::handler()
{
    int iResult;
    timeval timeout;
    fd_set activeFdSet;
    fd_set readFdSet;
    FD_ZERO(&activeFdSet);
    FD_SET(serv_sock, &activeFdSet);
    printf("FD_SETSIZE=%d\n", FD_SETSIZE);
    // listen for incoming connections
    iResult = listen(serv_sock, SOMAXCONN);
    timeout.tv_sec = 5;
    timeout.tv_usec= 0;

    while (1)
    {
        readFdSet = activeFdSet;
        printf("\tCopied activefdset to readfdset\n");
        int res = select(FD_SETSIZE, &readFdSet, NULL,NULL,&timeout);

        for (int i=0;i<FD_SETSIZE; i++)
        {
            if (FD_ISSET(i , &readFdSet)) // check socket descriptor 
            {
                if (i == (int)serv_sock)
                {
                    // accept connections to the server 
                }
                else // client socket 
                {
                    // receive from client 
                }
            }
        }
    }
    return 0;
}

what is the best way to deal with the server socket in a for loop without using vectors or any other similar concepts


Solution

  • On non-Windows platforms, sockets are represented with file descriptors, which are basically indexes into a files table. That is why you can use int sockets as loop counters.

    However, that is not the case with Windows sockets. A SOCKET is an opaque handle to an actual kernel object, so you can't use SOCKETs as loop counters, like you are trying to do. And do not cast them to int.

    You really have no choice but to store the accepted sockets in an array or other container and then iterate through that instead, especially if you want the code to be portable across platforms, and particularly if you want to handle more than FD_SETSIZE number of clients (in which case, you should be using (e)poll() or other asynchronous socket I/O mechanism instead of select()), eg:

    int Server::handler()
    {
        int iResult;
        timeval timeout;
        fd_set readFdSet;
        int maxFd;
    
        // listen for incoming connections
        iResult = listen(serv_sock, SOMAXCONN);
        timeout.tv_sec = 5;
        timeout.tv_usec= 0;
    
        while (1)
        {
            FD_ZERO(&readFdSet);
            FD_SET(serv_sock, &readFdSet);
    
            #ifdef WIN32
            maxFd = -1; // not used on Windows
            #else
            maxFd = serv_sock;
            #endif
    
            for (each client_sock in list)
            {
                FD_SET(client_sock, &readFdSet);
                #ifndef WIN32
                if (client_sock > maxFd) maxFd = client_sock;
                #endif
            }
            #endif
    
            int res = select(maxFd+1, &readFdSet, NULL, NULL, &timeout);
            if (res < 0) ... // error handling as needed...
    
            if (FD_ISSET(serv_sock, &readFdSet)) 
            {
                // accept connections to the server, add to clients list 
            }
    
            for (each client_sock in list)
            {
                if (FD_ISSET(client_sock, &readFdSet)) // check socket descriptor 
                {
                    // receive from client 
                }
            }
        }
    
        return 0;
    }
    

    That being said, on Windows only, you can rely on Microsoft's documented implementation detail that fd_set has fd_count and fd_array[] members, so you can just iterate through the fd_set's internal array directly, eg:

    int Server::handler()
    {
        int iResult;
        timeval timeout;
        fd_set readFdSet;
        int maxFd;
    
        // listen for incoming connections
        iResult = listen(serv_sock, SOMAXCONN);
        timeout.tv_sec = 5;
        timeout.tv_usec= 0;
    
        while (1)
        {
            FD_ZERO(&readFdSet);
            FD_SET(serv_sock, &readFdSet);
    
            #ifdef WIN32
            maxFd = -1; // not used on Windows
            #else
            maxFd = serv_sock;
            #endif
    
            for (each client_sock in list)
            {
                FD_SET(client_sock, &readFdSet);
                #ifndef WIN32
                if (client_sock > maxFd) maxFd = client_sock;
                #endif
            }
            #endif
    
            int res = select(maxFd+1, &readFdSet, NULL, NULL, &timeout);
            if (res < 0) ... // error handling as needed...
    
            #ifdef WIN32
            for (int i = 0; i < readFdSet.fd_count; ++i)
            #else
            for (int client_sock = 0; client_sock <= maxFd; ++client_sock)
            #endif
            {
                #ifdef WIN32
                SOCKET client_sock = readFdSet.fd_array[i];
                #else
                if (!FD_ISSET(client_sock, &readFdSet)) // check socket descriptor
                    continue;
                #endif
                if (client_sock == serv_sock)
                {
                    // accept connections to the server, add to clients list 
                }
                else // client socket 
                {
                    // receive from client 
                }
            }
        }
    
        return 0;
    }