前言
select、poll 和 epoll 是 Linux 下用于多路复用 I/O(Input/Output)的系统调用,它们用于监视多个文件描述符,以查看哪个文件描述符上有可读、可写或发生了异常的事件。
1、select
select 是最早的多路复用 I/O 机制之一。它允许你监控多个文件描述符,以检测哪些文件描述符有事件发生。它通过修改一个位掩码来表示每个文件描述符的状态。
- 使用方法
#include <sys/select.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
fd_set read_fds;
int max_fd;
struct timeval timeout;
// 设置文件描述符集合
FD_ZERO(&read_fds);
FD_SET(STDIN_FILENO, &read_fds);
max_fd = STDIN_FILENO;
// 设置超时时间
timeout.tv_sec = 5;
timeout.tv_usec = 0;
int ret = select(max_fd + 1, &read_fds, NULL, NULL, &timeout);
if (ret == -1) {
perror("select");
exit(EXIT_FAILURE);
} else if (ret == 0) {
printf("Timeout occurred!\n");
} else {
if (FD_ISSET(STDIN_FILENO, &read_fds)) {
printf("Data is available on stdin.\n");
}
}
return 0;
}
- select的几个宏
/*
用于将文件描述符添加到文件描述符集合中
fd:要添加的文件描述符。
fdset:要修改的文件描述符集合。
*/
void FD_SET(int fd, fd_set *fdset);
/*
用于从文件描述符集合中删除指定的文件描述符
fd:要删除的文件描述符。
fdset:要修改的文件描述符集合。
*/
void FD_CLR(int fd, fd_set *fdset);
/*
用于检查文件描述符集合中是否包含指定的文件描述符
fd:要检查的文件描述符。
fdset:要检查的文件描述符集合。
*/
int FD_ISSET(int fd, fd_set *fdset);
/*
用于清空文件描述符集合
fdset:要清空的文件描述符集合。
*/
void FD_ZERO(fd_set *fdset);
- select函数的五个参数含义
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds: 监控的最大文件描述符值加1。
readfds: 监控可读事件的文件描述符集合。
writefds: 监控可写事件的文件描述符集合。
exceptfds: 监控异常事件的文件描述符集合。
timeout: 超时时间,指定 select 需要等待的时间。如果为 NULL,则 select 会一直等待直到有事件发生。
- 优缺点分析
(1)优点
简单: select 的接口简单易用,广泛支持。
兼容性: 几乎所有 UNIX-like 操作系统都支持 select。
(2)缺点
文件描述符限制: 默认情况下,select 的文件描述符数量限制为 1024(可以通过重新编译内核或修改宏 FD_SETSIZE 增加,但不建议)。
性能瓶颈: 每次调用 select 都需要将文件描述符集合从用户态复制到内核态,这在文件描述符数量很大时可能会带来性能问题。
线性扫描: select 需要线性扫描文件描述符集合,效率较低。
2、poll
poll 是 select 的改进版,功能和 select 类似,但解决了一些 select 的局限性。poll 使用一个结构体数组来表示文件描述符及其相关的事件,而不是使用位掩码。
- 使用方法
#include <poll.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
struct pollfd fds;
int timeout = 5000; // 超时时间:5秒
fds.fd = STDIN_FILENO;
fds.events = POLLIN;
fds.revents = 0;
int ret = poll(&fds, 1, timeout);
if (ret == -1) {
perror("poll");
exit(EXIT_FAILURE);
} else if (ret == 0) {
printf("Timeout occurred!\n");
} else {
if (fds.revents & POLLIN) {
printf("Data is available on stdin.\n");
}
}
return 0;
}
- poll函数的参数
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds: pollfd 结构体数组,每个结构体表示一个文件描述符及其事件。
nfds: pollfd 结构体数组的大小。
timeout: 超时时间,单位是毫秒。如果设置为 -1,poll 将无限期等待直到有事件发生;如果设置为 0,poll 将立即返回。
- pollfd结构体
struct pollfd {
int fd; // 文件描述符
short events; // 监控的事件类型
short revents; // 实际发生的事件
};
- 事件常量
POLLIN: 文件描述符可读(例如,有数据可读或连接可接受)。
POLLOUT: 文件描述符可写(例如,缓冲区有足够空间)。
POLLERR: 文件描述符发生错误。
POLLHUP: 文件描述符挂起(通常用于检测连接的关闭)。
POLLNVAL: 文件描述符无效(例如,关闭了的文件描述符)。
- 优缺点分析
(1)优点
文件描述符数量无上限: 没有像 select 那样的文件描述符数量限制。
灵活性: 可以同时监控多个文件描述符及其事件。
(2)缺点
性能瓶颈: 每次调用 poll 时都需要遍历 pollfd 数组,这在监控大量文件描述符时会带来性能开销。
3、epoll
4、总结
select 适合处理少量文件描述符和需要跨平台支持的场景。它简单易用,但有文件描述符数量限制,并且在文件描述符数量多时性能较差。
poll 适合需要监控大量文件描述符的场景。它没有文件描述符数量限制,但在大量文件描述符时性能依然较差。