Select example
Relatively simple protocol:
-
Client connects.
-
Server sends a greeting. (Not recommended / necessary for your lab!)
-
Client sends a string.
-
Server echoes string back.
Draw state machine.
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
-
Socket becomes available for reading,
recv
returns 0. -
You call
send
with theMSG_NOSIGNAL
flag, andsend
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.