Search code examples
tcpunix-socket

TCP Socket: How to use FD_ISSET() on server to detect client's sending data?


I have a server which has several sockets connected with counterpart clients, I want to use select() to detect whether a client is sending data to the counterpart socket. My code is:

fd_set player_fd_set;
  FD_ZERO(&player_fd_set);
  int max_fd = players[0].connected_socket_on_master;
  for (int i = 0; i < num_players; i++) {
    FD_SET(players[i].connected_socket_on_master, &player_fd_set);
    if (players[i].connected_socket_on_master > max_fd) {
      max_fd = players[i].connected_socket_on_master;
    }
  }

  select(max_fd + 1, &player_fd_set, NULL, NULL, NULL);

  for (int i = 0; i < num_players; i++) {
    printf("Check fd # %d\n", i);
    if (FD_ISSET(players[i].connected_socket_on_master, &player_fd_set)) {
      printf("Coming from player # %d\n", i);
      ssize_t recvStatus = recv(players[i].connected_socket_on_master,
                                potato.trace, sizeof(potato.trace), 0);
      if (recvStatus == -1) {
        printf("Error: Could not recv final potato from player # %d\n", i);
        exit(EXIT_FAILURE);
      }
      break;
    }
  }

It seems that FD_ISSET() returns immediately after first time entering the for-loop. I read from other question in stackoverflow that select() is level-triggered instead of edge-triggered, then how could I detect the data received from a socket?

Thanks!


Solution

  • Your fd_set setup is fine, but you are not checking the return value of select() for > 0 before entering the reading loop.

    Your reading loop is calling break in the wrong place, in fact it should not be calling break at all. You are exiting the loop as soon as any client sends data, ignoring other clients which may also have sent data that is waiting to be read. You need to loop through the entire fd_set reading from every socket that is in a readable state.

    Your reading loop should not be calling exit() if recv() fails. Just close() the failed socket, remove that socket from players[], and move on to the next loop iteration.

    If recv() returns -1, be sure to check errno for EWOULDBLOCK and EAGAIN, as they are not fatal errors. Also, if recv() returns 0, the client has disconnected.

    Try something more like this:

    fd_set player_fd_set;
    FD_ZERO(&player_fd_set);
    
    int max_fd = players[0].connected_socket_on_master;
    for (int i = 0; i < num_players; ++i)
    {
        FD_SET(players[i].connected_socket_on_master, &player_fd_set);
        if (players[i].connected_socket_on_master > max_fd)
            max_fd = players[i].connected_socket_on_master;
    }
    
    if (select(max_fd + 1, &player_fd_set, NULL, NULL, NULL) > 0)
    {
        int offset = 0;
        for (int i = 0; i < num_players; ++i)
        {
            printf("Check fd # %d\n", i);
    
            if (!FD_ISSET(players[offset].connected_socket_on_master, &player_fd_set))
                continue;
    
            printf("Coming from player # %d\n", i);
    
            ssize_t recvStatus = recv(players[offset].connected_socket_on_master, potato.trace, sizeof(potato.trace), 0);
            if (recvStatus > 0)
            {
                // process potato.trace up to recvStatus bytes as needed...
                ++offset;
                continue;
            }
    
            if (recvStatus < 0)
            {
                if ((errno == EWOULDBLOCK) || (errno == EAGAIN))
                {
                    // no more data to read right now...
                    ++offset;
                    continue;
                }
    
                printf("Error: Could not recv final potato from player # %d\n", i);
            }
            else
                printf("Player # %d disconnected\n", i);
    
            close(players[offset].connected_socket_on_master);
    
            for(int j = i+1; j < num_players; ++j)
                players[j-1] = players[j];
    
            --num_players;
        }
    }