1. select()
函数原型
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数说明
nfds
:指定文件描述符的范围。这个值应设置为所有文件描述符中最大值加一。例如,如果监视的文件描述符是 0 到 5,则nfds
应设置为 6。readfds
:指向一个fd_set
结构体的指针,该结构体表示要监视的可读文件描述符集合。调用select()
之前,需要使用FD_ZERO()
初始化该集合,并用FD_SET()
添加文件描述符。调用select()
后,集合中的文件描述符将更新为实际发生可读事件的文件描述符。writefds
:指向一个fd_set
结构体的指针,该结构体表示要监视的可写文件描述符集合。用法与readfds
类似。exceptfds
:指向一个fd_set
结构体的指针,该结构体表示要监视的异常条件集合。用法与readfds
类似。timeout
:指向一个struct timeval
结构体的指针,表示select()
的超时时间。如果超时前没有文件描述符状态变化,select()
将返回。如果设置为NULL
,则表示select()
将无限期等待直到有事件发生。
返回值
优势
- 成功:返回发生事件的文件描述符数量。
- 失败:返回 -1,并设置
errno
。#include <sys/select.h> #include <sys/types.h> #include <unistd.h> #include <stdio.h> int main() { fd_set read_fds; struct timeval timeout; int max_fd = 0; // 最大文件描述符编号 // 初始化文件描述符集 FD_ZERO(&read_fds); FD_SET(STDIN_FILENO, &read_fds); // 监视标准输入 timeout.tv_sec = 5; // 设置超时时间为5秒 timeout.tv_usec = 0; int ret = select(max_fd + 1, &read_fds, NULL, NULL, &timeout); if (ret == -1) { perror("select"); return 1; } 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; }
2.
poll()
-
介绍
poll()
是另一种 I/O 多路复用机制,允许应用程序监视多个文件描述符,等待它们的状态发生变化。与select()
不同,poll()
使用一个数组来表示文件描述符。工作原理
- 结构体:
poll()
使用pollfd
结构体数组,其中包含要监视的文件描述符及其事件类型。 - 事件类型:
pollfd
结构体中的events
成员表示感兴趣的事件(如POLLIN
、POLLOUT
)。 - 返回值:返回值表示活动的文件描述符数量。可以通过检查
revents
成员来判断哪个文件描述符有事件发生。 - 文件描述符数量限制:没有像
select()
那样的文件描述符数量限制,只受限于系统的内存。 - 性能改进:对大量文件描述符的处理性能较
select()
更好。
#include <poll.h>
#include <stdio.h>
#include <unistd.h>
int main() {
struct pollfd fds[1];
int ret;
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN; // 关注可读事件
ret = poll(fds, 1, 5000); // 超时时间为5000毫秒
if
3. epoll()
介绍
epoll()
是 Linux 特有的 I/O 多路复用机制,设计用于高效处理大量文件描述符,尤其适合需要高性能的网络服务和其他 I/O 密集型应用。epoll
提供了更好的扩展性和性能,尤其在处理大量文件描述符时优于 select()
和 poll()
epoll_create()
函数原型
int epoll_create(int size);
size
:指定epoll
实例的初始大小,但在现代 Linux 内核中,这个参数已被忽略。你可以传递任意值,但通常设置为 1。
返回值
- 成功:返回一个新的
epoll
文件描述符。 - 失败:返回 -1,并设置
errno
。
epoll_ctl()
函数原型
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数说明
epfd
:由epoll_create()
返回的epoll
文件描述符。op
:操作类型,取值为EPOLL_CTL_ADD
(添加文件描述符)、EPOLL_CTL_MOD
(修改文件描述符)或EPOLL_CTL_DEL
(删除文件描述符)。fd
:要添加、修改或删除的文件描述符。event
:指向epoll_event
结构体的指针,指定感兴趣的事件及事件处理的用户数据。
返回值
- 成功:返回 0。
- 失败:返回 -1,并设置
errno
。
epoll_wait()
函数原型
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
参数说明
epfd
:由epoll_create()
返回的epoll
文件描述符。events
:指向epoll_event
结构体数组的指针。epoll_wait()
将把发生事件的文件描述符及其事件填充到这个数组中。maxevents
:events
数组的大小,即最大事件数量。timeout
:超时时间(以毫秒为单位)。设置为 0 表示非阻塞模式,设置为 -1 表示无限期等待。
返回值
- 成功:返回发生事件的文件描述符数量。
- 失败:返回 -1,并设置
errno
。
struct epoll_event {
uint32_t events; // 事件类型,例如 EPOLLIN、EPOLLOUT
epoll_data_t data; // 用户数据,通常用于保存文件描述符
};
常用事件标志
EPOLLIN
:文件描述符可读。EPOLLOUT
:文件描述符可写。EPOLLERR
:发生错误。EPOLLET
:边沿触发模式(ET),文件描述符会在事件到达时通知一次
#include <sys/epoll.h>
#include <stdio.h>
#include <unistd.h>
#define MAX_EVENTS 10
int main() {
int epfd = epoll_create(1);
if (epfd == -1) {
perror("epoll_create");
return 1;
}
struct epoll_event ev, events[MAX_EVENTS];
ev.events = EPOLLIN; // 关注可读事件
ev.data.fd = STDIN_FILENO;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) == -1) {
perror("epoll_ctl");
return 1;
}
int nfds = epoll_wait(epfd, events, MAX_EVENTS, 5000); // 等待最多5000毫秒
if (nfds == -1) {
perror("epoll_wait");
return 1;
} else if (nfds == 0) {
printf("Timeout occurred!\n");
} else {
for (int i = 0; i < nfds; i++) {
if (events[i].events & EPOLLIN) {
printf("Data is available on stdin\n");
}
}
}
close(epfd);
return 0;
}