Search code examples
csocketsioclient-servermultiplexing

My server program only answers the first request when a second one arrives?


I am trying to learn about socket programming by writing a server-client prototype program.

Both the server and the client need to be able to handle commands from stdin, so I am using the select function.

My problem is that the server program gets blocked and answers a client request only after the client sends another request.

server.c

while(1)
{
    if (select(maxfd + 1, &tmpfds, NULL, NULL, NULL) == -1)
    {
        error("Err in select");
    }
    for (i = 0; i < maxfd; i++) 
    {
        if(FD_ISSET(i,&tmpfds)
        {
            if (i == listenfd)
            {
                < add new client to list >
            }
        else if (i == 0)
        {
            < parse server commands >
        }
        else
        {
            /* This is where I think my problem is */
            recv(i, buffer, BUFLEN, 0);
            process(buffer);
            send(i, buffer, BUFLEN, 0);
        }
    }
}

client.c

while(1)
{
    if (select(fdmax + 1, &tmpfds, NULL, NULL, NULL) == -1)
    {
        error("Err in select");
    }
    if (FD_ISSET(0,&tmpfds))
    {
        fgets(buffer, BUFLEN, stdin);
        process_request(buffer);
        send(serverfd, buffer, BUFLEN, 0);
    }
    else if (FD_ISSET(serverfd,&tmpfds))
    {
        recv(serverfd, buffer, BUFLEN, 0);
        process_response(buffer); 
    }
}

Could you please point me in the right direction ? Am I missing something about how send and recv behave?


Solution

  • To use select as the correct IO multiplexing facility, you need maintain the FD_SET correctly. Since each time select returns, the FD_SET contains only the fds that are ready for the operation, that means you have to rearm the FD_SET before calling select each time.

    There is another problem in you code, you cannot just add new client in the FD_SET in the loop, you need to save it and then rearm them in the beginning.

    Also, you don't need to check each FD in the set, since select will return the number of fd that are ready for IO.

    Try the following changes:

    int clients[MAX_CLIENTS] = {0};
    int I;
    int maxfd;
    int server_sock = <the listening fd>;
    FD_SET readfds;
    int ret;
    while(1) {
        // Setup SD_SET each time calling select
        FD_ZERO(&readfds);
        FD_SET(STDIN_FILENO, &readfds);
        maxfd = STDIN_FILENO;
        FD_SET(server_sock, &readfds);
        maxfd = max(maxfd, server_sock);
        for (I = 0; I < MAX_CLIENTS; I++) {
            if (clients[I] >= 0) {
                FD_SET(clients[I], &readfds);
                maxfd = max(maxfd, clients[I]);
        }
    
        if ((ret = select(maxfd+1,&readfds,NULL,NULL,NULL)) == -1) {
            error("Err in select");
        }
        for(i = 0; i < maxfd && ret; i++, ret--) {
            if(FD_ISSET(i, &readfds) {
                if (i == listenfd) {
                     // < add new client to clients array
                }
                else if (i == STDIN_FILENO) { /* keyboard input */
                     //  < parse server commands >
                }
                else {
                      // one of the client is ready
                      int nread = recv(i,buffer,BUFLEN,0);
                      if (nread == 0) {
                           // client is closed, remove I from clients array
                           continue;
                      }
                      process(buffer);
                      send(i,buffer,BUFLEN,0);
                }
            }
        }
    }
    

    Last but not least, as an improvement over select, try something like epoll on Linux, which maintains the state for you, so that you don't need to rearm all the fds like select does.