The "Are We There Yet?" Era
select() and poll() were the first tools for monitoring multiple file descriptors simultaneously. The programming model is straightforward:
// Simplified select() usage
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(fd1, &readfds);
FD_SET(fd2, &readfds);
// Block until one of the fds is ready
select(max_fd + 1, &readfds, NULL, NULL, &timeout);
// Now iterate through ALL fds to find which ones are ready
for (int i = 0; i <= max_fd; i++) {
if (FD_ISSET(i, &readfds)) {
// This fd has data — handle it
}
}
The critical problem is visible in the loop: you must check every file descriptor every time, even if only one of 10,000 connections has data. The kernel also copies the entire fd_set between user space and kernel space on every call.
Complexity: O(N) per call, where N is the number of monitored file descriptors.
select limitations:
- Maximum of 1,024 file descriptors (hardcoded FD_SETSIZE limit).
- The fd_set is destroyed after each call — you must rebuild it.
poll improvements:
- No 1,024 fd limit (uses a dynamic array).
- The pollfd array is not destroyed after each call.
- Still O(N) — still iterates through every fd.
Neither select nor poll scales beyond a few thousand connections. This was the "C10K problem" — how do you handle 10,000 concurrent connections on a single server?