C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
216: Handling Multiple Connections with select()
Imagine you're a waiter in a busy diner. You have six different tables, and each one is undecided about their order. If you stood at Table 1 and refused to move until they told you what they wanted, the other five tables would starve, and your manager would probably fire you. That's essentially what happens when you call recv() on a single socket in a blocking manner—your entire program just freezes, waiting for one specific person to speak.
Instead, a professional waiter does something different: they scan the room. They glance at Table 1, then Table 2, then Table 3. They aren't taking orders yet; they're just looking for a "signal"—a raised hand or a closed menu. The moment someone signals, the waiter rushes over to that specific table, handles the request, and then immediately goes back to scanning the room. In C, select() is that scan of the room.
The Watchlist and the fd_set
To make this work, we can't just tell the OS "watch everything." We have to give it a specific list of file descriptors we care about. This is where fd_set comes in. Think of it as a checklist. You start with a blank sheet (FD_ZERO), and then you check off the sockets you're interested in (FD_SET).
Here is the tricky part that trips up almost everyone: select() is destructive. When you pass your checklist to the function, the OS scribbles all over it. It removes every socket that isn't ready and leaves only the ones that actually have data waiting for you. Because of this, you have to rebuild your checklist from scratch every single time you loop. I can't tell you how many hours I've wasted debugging "ghost" connections because I forgot to re-initialize my fd_set inside the loop.
Managing the Event Loop
When you call select(), the program pauses. But it doesn't pause on one socket; it pauses on the entire group. It wakes up the moment any one of those sockets becomes "ready" (meaning there's data in the buffer to read) or if a timeout occurs.
Once it wakes up, you don't know which socket triggered the event. You have to loop through your list and use FD_ISSET to check which "boxes" are still checked on your list. It's a bit tedious, but it's the only way to handle multiple clients without spawning a thousand threads and crashing your system.
Putting the Watcher to Work
Let's look at how this actually looks in code. We'll set up a server that can handle multiple clients simultaneously without using threads.
#include <stdio.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <unistd.h>>
#define MAX_CLIENTS 10
#define PORT 8080
int main() {
int master_socket, client_sockets[MAX_CLIENTS] = {0};
fd_set readfds;
struct sockaddr_in address;
int addrlen = sizeof(address);
// ... (Assume standard socket(), bind(), and listen() setup here) ...
master_socket = setup_server_socket(PORT);
while(1) {
FD_ZERO(&readfds);
// We add the master socket to watch for NEW connections
FD_SET(master_socket, &readfds);
// We also add every connected client to watch for DATA
for (int i = 0; i < MAX_CLIENTS; i++) {
if (client_sockets[i] > 0) FD_SET(client_sockets[i], &readfds);
}
// This is where the "scanning" happens.
// It blocks until something happens or the timeout (NULL) hits.
int activity = select(MAX_CLIENTS + 1, &readfds, NULL, NULL, NULL);
if (activity < 0) {
perror("select error");
break;
}
// Is it a new connection on the master socket?
if (FD_ISSET(master_socket, &readfds)) {
int new_socket = accept(master_socket, (struct sockaddr *)&address, (socklen_t*)&addrlen);
// Add new_socket to our client_sockets array...
printf("New connection established!\n");
}
// Or is it data from an existing client?
for (int i = 0; i < MAX_CLIENTS; i++) {
int sd = client_sockets[i];
if (sd > 0 && FD_ISSET(sd, &readfds)) {
char buffer[1024] = {0};
int valread = read(sd, buffer, 1024);
if (valread == 0) {
// Client disconnected
close(sd);
client_sockets[i] = 0;
} else {
printf("Client %d says: %s\n", i, buffer);
}
}
}
}
return 0;
}
Notice how the master_socket is treated differently than the client_sockets. The master socket is like the front door of the restaurant—when it's "ready," it means someone is walking in. The client sockets are like the tables—when they're "ready," it means someone is trying to order.
📋 Practical Task
Build a Multi-Source Log Monitor
Your task is to create a program that monitors two different sources of input simultaneously using select(): stdin (standard input) and a TCP server socket.
The program should behave as follows:
- If you type something into the console and hit Enter, the program should print:
"Local admin command received: [your text]". - If a remote client connects and sends a message, the program should print:
"Remote client message received: [client text]". - The program must not block on
read()orrecv(); it must useselect()to determine which source is ready. - If a remote client disconnects, the program should detect this and clean up the socket without crashing.
Constraint: You cannot use threads. Everything must happen in a single-threaded event loop using fd_set.
There are no comments for now.