I/O 多路复用是指在一个线程内同时监控多个文件描述符(File Descriptor, FD),以便高效地处理多个 I/O 事件。在 UNIX/Linux 和 BSD 系统中,select
、poll
、epoll
、kqueue
都是实现 I/O 多路复用的系统调用。它们各有特点,适合不同的应用场景。本文将详细介绍它们的用法、优缺点,并附上相应的代码示例。
1. select
概述
select
是最早的 I/O 多路复用系统调用之一,广泛支持于各类操作系统中。它允许程序同时监视多个文件描述符,但有最大数量的限制(通常是 1024 个文件描述符)。
优点
- 简单,适用性广泛。
- 支持几乎所有 UNIX 类操作系统。
缺点
- 文件描述符数量有限制。
- 每次调用都需要重新设置文件描述符集合,效率较低。
使用示例
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <unistd.h>
int main() {
fd_set readfds;
struct timeval timeout;
int ret;
// 初始化文件描述符集合
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
// 设置超时时间
timeout.tv_sec = 5;
timeout.tv_usec = 0;
printf("Waiting for input, timeout in 5 seconds...\n");
// 调用 select 函数,监控文件描述符
ret = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &timeout);
if (ret == -1) {
perror("select");
exit(EXIT_FAILURE);
} else if (ret == 0) {
printf("Timeout occurred! No data after 5 seconds.\n");
} else {
if (FD_ISSET(STDIN_FILENO, &readfds)) {
char buffer[1024];
read(STDIN_FILENO, buffer, sizeof(buffer));
printf("Data read: %s\n", buffer);
}
}
return 0;
}
解释
在上述代码中,我们使用 select
函数监听标准输入 (STDIN_FILENO
) 的可读性。如果用户在 5 秒内没有输入,程序会超时并退出。
2. poll
概述
poll
作为 select
的改进版本,消除了文件描述符数量的限制。它通过一个 pollfd
数组来管理多个文件描述符,解决了 select
的一些局限性。
优点
- 没有文件描述符数量限制。
- API 比较简洁,避免了
select
需要重置文件描述符集合的问题。
缺点
- 和
select
一样,每次调用仍需遍历整个文件描述符集合。
使用示例
#include <stdio.h>
#include <stdlib.h>
#include <poll.h>
#include <unistd.h>
int main() {
struct pollfd fds[1];
int ret;
// 设置文件描述符和事件
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
printf("Waiting for input, timeout in 5 seconds...\n");
// 调用 poll 函数,设置 5 秒超时
ret = poll(fds, 1, 5000);
if (ret == -1) {
perror("poll");
exit(EXIT_FAILURE);
} else if (ret == 0) {
printf("Timeout occurred! No data after 5 seconds.\n");
} else {
if (fds[0].revents & POLLIN) {
char buffer[1024];
read(STDIN_FILENO, buffer, sizeof(buffer));
printf("Data read: %s\n", buffer);
}
}
return 0;
}
解释
这里的代码使用 poll
来监控标准输入的可读性。与 select
类似,它设置了一个 5 秒的超时时间,但使用 poll
可以处理更多的文件描述符。
3. epoll
概述
epoll
是 Linux 特有的系统调用,它专门为处理大量文件描述符而设计,性能远优于 select
和 poll
。epoll
使用一个事件通知机制,避免了每次调用时遍历整个文件描述符集合。
优点
- 高效,适合处理大量文件描述符。
- 不需要每次遍历整个文件描述符集合。
缺点
- 仅支持 Linux 系统。
使用示例
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <unistd.h>
int main() {
int epfd = epoll_create1(0);
struct epoll_event event, events[1];
int ret;
if (epfd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
// 设置事件
event.events = EPOLLIN;
event.data.fd = STDIN_FILENO;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) {
perror("epoll_ctl");
exit(EXIT_FAILURE);
}
printf("Waiting for input, timeout in 5 seconds...\n");
// 调用 epoll_wait,等待事件
ret = epoll_wait(epfd, events, 1, 5000);
if (ret == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
} else if (ret == 0) {
printf("Timeout occurred! No data after 5 seconds.\n");
} else {
if (events[0].data.fd == STDIN_FILENO) {
char buffer[1024];
read(STDIN_FILENO, buffer, sizeof(buffer));
printf("Data read: %s\n", buffer);
}
}
close(epfd);
return 0;
}
解释
在这个示例中,我们使用 epoll
来监控标准输入。epoll_create1
创建了一个 epoll
实例,随后通过 epoll_ctl
添加文件描述符。epoll_wait
用来等待事件发生,效率远高于 select
和 poll
。
4. kqueue
概述
kqueue
是 BSD 系统(包括 macOS)中的高效 I/O 事件通知机制。与 epoll
类似,kqueue
使用事件通知的机制来避免每次遍历整个文件描述符集合。
优点
- 高效,适合处理大量 I/O 事件。
- 支持 BSD 系统。
缺点
- 仅支持 BSD 系统(包括 macOS)。
使用示例
#include <stdio.h>
#include <stdlib.h>
#include <sys/event.h>
#include <sys/time.h>
#include <unistd.h>
int main() {
int kq = kqueue();
struct kevent change, event;
int ret;
if (kq == -1) {
perror("kqueue");
exit(EXIT_FAILURE);
}
// 设置事件
EV_SET(&change, STDIN_FILENO, EVFILT_READ, EV_ADD, 0, 0, NULL);
printf("Waiting for input, timeout in 5 seconds...\n");
// 调用 kevent,设置 5 秒超时
struct timespec timeout = {5, 0};
ret = kevent(kq, &change, 1, &event, 1, &timeout);
if (ret == -1) {
perror("kevent");
exit(EXIT_FAILURE);
} else if (ret == 0) {
printf("Timeout occurred! No data after 5 seconds.\n");
} else {
if (event.ident == STDIN_FILENO) {
char buffer[1024];
read(STDIN_FILENO, buffer, sizeof(buffer));
printf("Data read: %s\n", buffer);
}
}
close(kq);
return 0;
}
解释
在 BSD 系统中,kqueue
提供了一种高效的 I/O 事件通知机制。该代码监控标准输入,超时时间为 5 秒,使用 kevent
等待事件发生。
5. 总结
特性 | select | poll | epoll | kqueue |
---|---|---|---|---|
支持的平台 | Unix/Linux/BSD | Unix/Linux/BSD | Linux | BSD/macOS |
文件描述符限制 | 有限制(1024) | 无限制 | 无限制 | 无限制 |
效率 | 较低 | 较低 | 高 | 高 |
扩展性 |