Let’s get straight to the point without further ado, read on~The select function is a polling-based multiplexing technique used to monitor the state changes of multiple file descriptors. With select, a program can simultaneously monitor multiple input/output streams and be notified when data is ready for read or write operations. It is one of the earliest I/O multiplexing techniques in UNIX/Linux systems.1. Working Principle of selectThe core function of the select function is to allow a program to monitor multiple file descriptors (such as network sockets, pipes, files, etc.) simultaneously. It blocks until there is data to read or write on a file descriptor, or another event (such as an exception state) occurs, thus avoiding wasting CPU resources while waiting for I/O operations. The specific working principle is as follows:select uses three sets to monitor different types of file descriptors:
- Readable set (readfds): used to check which file descriptors can read data.
- Writable set (writefds): used to check which file descriptors can write data.
- Exception set (exceptfds): used to check which file descriptors have exceptions (e.g., out-of-band data).
Each set is a bitmap, and the program indicates the status of file descriptors by setting bits in these sets.Timeout: select can set a timeout period; if no events occur after the timeout, select will return.2. Prototype of select function
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- nfds: the maximum number of file descriptors to monitor, usually the maximum value of all file descriptors + 1.
- readfds: the set of file descriptors to monitor for readable events.
- writefds: the set of file descriptors to monitor for writable events.
- exceptfds: the set of file descriptors to monitor for exceptional events.
- timeout: sets the timeout period for select. If NULL, it means wait indefinitely; if {0,0}, it means return immediately.
3. Steps to use select
- Initialize the file descriptor set: Use FD_ZERO() to clear the set, and FD_SET() to add the file descriptors to be monitored.
- Call the select function: Use select() for I/O multiplexing wait operation, which blocks until there is data to read or write on the file descriptors, or a timeout occurs.
- Check the status of file descriptors: After calling select, check which file descriptors have events of interest (such as readable, writable, or exceptional).
- Handle the ready file descriptors: Perform the corresponding read/write operations on the ready file descriptors.
4. Example code for selectBelow is a simple example of using select, demonstrating how to monitor multiple client connection requests on the server side.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/select.h>
int main() {
int server_sock, client_sock;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
fd_set readfds;
struct timeval timeout;
// Create server socket
server_sock = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock < 0) {
perror("socket");
exit(1);
}
// Set server address
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8080);
// Bind server socket
if (bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
exit(1);
}
// Listen on port
listen(server_sock, 5);
// Initialize file descriptor set
FD_ZERO(&readfds);
FD_SET(server_sock, &readfds);
timeout.tv_sec = 5;
timeout.tv_usec = 0;
// Enter select blocking wait
int ret = select(server_sock + 1, &readfds, NULL, NULL, &timeout);
if (ret == -1) {
perror("select");
exit(1);
} else if (ret == 0) {
printf("timeout occurred!\n");
} else {
if (FD_ISSET(server_sock, &readfds)) {
client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &addr_len);
if (client_sock < 0) {
perror("accept");
exit(1);
}
printf("Client connected!\n");
// Close client connection
close(client_sock);
}
}
// Close server socket
close(server_sock);
return 0;
}
5. Advantages and Disadvantages of selectAdvantages:
- Suitable for scenarios with a small number of file descriptors, simple code implementation, and cross-platform.
- Supports synchronous I/O operations, avoiding CPU resource waste while waiting for I/O.
Disadvantages:
- Performance bottleneck: When the number of file descriptors is large, select becomes inefficient due to the need to scan the entire file descriptor set. The efficiency of select significantly decreases as the number of file descriptors increases.
- File descriptor limit: select has a limit on the number of file descriptors, usually 1024 (can be checked with ulimit), and exceeding this limit requires using other methods (such as poll or epoll).
- Memory usage issue: Each time select is called, all file descriptor sets are copied to the kernel, which can lead to significant memory consumption.
6. Use Cases and AlternativesApplicable scenarios: Suitable for handling a small number of file descriptors (e.g., a few hundred). Due to its simple implementation and cross-platform nature, it is commonly used in early network programming and multi-threaded I/O models.Alternatives:
- poll: Solves the file descriptor limit issue of select, but still has the problem of scanning the file descriptor set.
- epoll: Suitable for handling a large number of concurrent connections, more efficient, does not require traversing all file descriptors each time, and is a high-performance multiplexing method in the Linux environment.Note: Detailed explanations of poll and epoll will be provided in the next two articles, stay tuned for the author~
From the detailed explanation above, we can see that select is a fundamental multiplexing technique suitable for scenarios with fewer concurrent connections. However, due to its performance bottlenecks and file descriptor limits, it is less efficient when handling a large number of connections. Of course, there are other methods to replace select, namely poll and epoll.