Search code examples
c++clinuxsocketsposix-select

linux C++ socket select loop


I'm having a little trouble with sockets, when looping I'm not receiving data except for the first loop, it's timing out each time. If I close and reopen the socket each loop though I seem to be getting the data correctly. Any ideas as to why?

Example of looping without closing:

int socketHandle = socket(AF_INET,SOCK_DGRAM,0);

sockaddr_in serverAddr;

serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr(/*UDP IP ADDRESS*/);
serverAddr.sin_port = htons(/*UDP PORT*/);

struct timeval tv;
fd_set rfds;

FD_ZERO(&rfds);
FD_SET(socketHandle, &rfds);

tv.tv_usec = 0.0;
int recVal = 0;
int sockLen = sizeof(serverAddr);
bind(socketHandle, (struct sockaddr*)&serverAddr, (socklen_t)sockLen);

bool timePassed = false;
time_t startListenTime = time(NULL);

tv.tv_sec = maxUpdateTime;

while(true)
{
    recVal = select(socketHandle + 1, &rfds, NULL, NULL, &tv);
    switch(recVal)
    {
        case(0):
        {
            //Timeout
            break;
        }
        case(-1):
        {
            //Error
             break;
        }
        default:
        {
            /*Packet Data Type*/ pkt;
            if(recvfrom(socketHandle, &pkt, sizeof(/*Packet Data Type*/), 0, (sockaddr*)&serverAddr, (socklen_t*)&sockLen) < 0)
            {
                //Failed to Recieve Data
                break;
            }
            else
            {
                //Recieved Data!!
            }
            break;
        }
    }
}

Example of looping with closing:

while(true)
{
    int socketHandle = socket(AF_INET,SOCK_DGRAM,0);

    sockaddr_in serverAddr;

    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = inet_addr(/*UDP IP ADDRESS*/);
    serverAddr.sin_port = htons(/*UDP PORT*/);

    struct timeval tv;
    fd_set rfds;

    FD_ZERO(&rfds);
    FD_SET(socketHandle, &rfds);

    tv.tv_usec = 0.0;
    int recVal = 0;
    int sockLen = sizeof(serverAddr);
    bind(socketHandle, (struct sockaddr*)&serverAddr, (socklen_t)sockLen);


    bool timePassed = false;
    time_t startListenTime = time(NULL);

    tv.tv_sec = maxUpdateTime;

    recVal = select(socketHandle + 1, &rfds, NULL, NULL, &tv);
    switch(recVal)
    {
        case(0):
        {
            //Timeout
            break;
        }
        case(-1):
        {
            //Error
             break;
        }
        default:
        {
            /*Packet Datastructure*/ pkt;
            if(recvfrom(socketHandle, &pkt, sizeof(/*Packet Datastructure*/), 0, (sockaddr*)&serverAddr, (socklen_t*)&sockLen) < 0)
            {
                //Failed to read packet
                break;
            }
            else
            {
                //Read Packet!!
            }
            break;
        }
    }
    close(socketHandle);
}

Solution

  • The select() function uses the specified file descriptor mask to determine which file descriptors to monitor for an event (read, write, etc.). When a file descriptor is available for an I/O activity (read, write) the select() function modifies the descriptors to indicate which of the files are ready for the given I/O action.

    See this article on the select function and the macros/functions used with the file descriptors.

    Old style Unix type programs often treated the file descriptor as a bit mask and just checked the bits. However the actual implementation of the file descriptor can vary by compiler so it is best to use the standard file descriptor macros/functions to set, clear, and test the various file descriptors.

    So when using the select() function you need to use FD_ZERO() and FD_SET() so that you will set the specific file descriptors that you want for this particular call to the select() function. When select() returns, it will indicate which of the file descriptors designated are actually ready to be used for the I/O action (read, write, etc.).

    So your code will actually be something like:

    while(true)
    {
        fd_set rfds;
    
        FD_ZERO(&rfds);
        FD_SET(socketHandle, &rfds);
        recVal = select(socketHandle + 1, &rfds, NULL, NULL, &tv);
        switch(recVal)
        {
            case(0):
            {
                //Timeout
                break;
            }
            case(-1):
            {
                //Error
                 break;
            }
            default:
            {
                /*Packet Data Type*/ pkt;
                if(recvfrom(socketHandle, &pkt, sizeof(/*Packet Data Type*/), 0, (sockaddr*)&serverAddr, (socklen_t*)&sockLen) < 0)
                {
                    //Failed to Recieve Data
                    break;
                }
                else
                {
                    //Recieved Data!!
                }
                break;
            }
        }
    

    However what you really should do is to use the FD_ISSET() function to check which particular file descriptors are ready for use. In your case you have only the one but in a situation where there were multiple descriptors you would want to use the FD_ISSET() function as well.