Select example

Relatively simple protocol:

  1. Client connects.

  2. Server sends a greeting. (Not recommended / necessary for your lab!)

  3. Client sends a string.

  4. Server echoes string back.

Draw state machine.

Lab4StateMachine

Client state

typedef struct {
    int connected;   // 0 - Not connected.   1 - Connected.

    int greeted;     // 0 - Not greeted.     1 - Greeted.

    char data[1024]; // The data to echo.
    int echo_offset; // Where we are in the data array.
    int echo_size;   // How much of the data we need to echo. 0 means no data to echo.
} client;

// ...

client clients[MAX_CLIENTS];
memset(clients, 0, MAX_CLIENTS * sizeof(client));

// Bind and listen on a server socket.  Let's say it's socket number 3

// Main event loop
while (1) {
...

Phase 1: populate sets

  • Question: Under what conditions (client states) do we want to check if they client has sent us data? If we can send them data?

// Main event loop
while (1) {

    fd_set rfds, wfds;
    int i;
    int maxfd = server_socket;

    FD_ZERO(&rfds);
    FD_ZERO(&wfds);

    FD_SET(server_socket, &rfds);

    for (i = 0; i < MAX_CLIENTS; i++) {
        // We want to receive from a client if...
        //   - The client is connected
        //   - The client has already been greeted (rules of this simple, weird protocol)
        //   - The client has not already given us data to echo. (If so, finish that first)
        if (clients[i].connected && clients[i].greeted && clients[i].echo_size == 0) {
            FD_SET(i, &rfds);
            if (i > maxfd)
                maxfd = i;
        }

        // We want to send to a client if...
        //   - The client is connected AND either of the following is true:
        //   - The client has not yet been greeted
        //   - The client has outstanding data we need to echo to them
        if (clients[i].connected && (!clients[i].greeted || clients[i].echo_size > 0)) {
            FD_SET(i, &wfds);
            if (i > maxfd)
                maxfd = i;
        }
    }

    maxfd += 1;

    ...

In this example, a client only ever gets added to the read set or write set, but not both. This just happens to be a result of the simple protocol we have in the example, and it will not be true in general.

In your lab, you might need to put a client in both sets. For example: if the client has asked you to send song data (but hasn’t yet asked for list/info), you need to put them in the write set (because you want to send them song data) and you also need to put them in the read set (to see if they make any other requests while you’re sending them song chunks).

Phase 2: call select

    ...

    maxfd += 1;

    int result = select(maxfd, &rfds, &wfds, NULL, NULL);

    ...
  • If none of the requested operations are ready, select will block.

  • When select returns, it will modify these sets.

Phase 3: Check the sets

  • Check the sets. For each socket, if it’s still in the set after select returns, it’s safe to read/write ONE TIME.

    ...

    for (i = 0; i < maxfd; i++) {
        if (FD_ISSET(i, &rfds)) {
            if (i == server_socket) {
                accept_new_client(i);
            } else {
                read_client(i);
            }
        }

        if (FD_ISSET(i, &wfds)) {
            if (!clients[i].greeted) {
                send_greeting(i);
            } else {
                send_data(i);
            }
        }
    }

} // End of main event loop

Detecting that clients disconnected

  1. Socket becomes available for reading, recv returns 0.

  2. You call send with the MSG_NOSIGNAL flag, and send returns negative value (error).

Example

  • New client (accept gets socket 7)

  • New client (8) + client 7 sends data at the same time.

  • Client 7 disconnects

Ignoring the client

If you already have too much work to do for the client, you can choose NOT to put them in the read set until you’re done sending them. Anything they send you in the meantime will get queued up in socket buffer.

  • Doing this might help to simplify your state, so you don’t have to worry about all combinations of requests.

  • For the above example, we use this to ignore a client while we already have data to echo to it.