IO多路复用(IO multiplexing)是一种通过单一的系统调用来监视多个文件描述符(通常是套接字),以确定是否有数据可读或可写的技术。它允许单个线程处理多个IO操作,提高了程序的并发性能和效率。
select 函数:
select
函数是比较早期的多路复用技术,在多个文件描述符上等待数据可读、可写或异常情况。#include <iostream> #include <vector> #include <sys/select.h> #include <unistd.h> int main() { fd_set read_fds; struct timeval timeout; // Initialize file descriptor set and timeout FD_ZERO(&read_fds); FD_SET(STDIN_FILENO, &read_fds); timeout.tv_sec = 5; timeout.tv_usec = 0; // Wait for input on stdin or timeout int ret = select(STDIN_FILENO + 1, &read_fds, NULL, NULL, &timeout); if (ret == -1) { perror("select"); return 1; } else if (ret == 0) { std::cout << "Timeout occurred!\n"; } else { if (FD_ISSET(STDIN_FILENO, &read_fds)) { std::cout << "Data is available on stdin!\n"; } } return 0; }
程序通过
select
函数监听标准输入(stdin),如果5秒内有输入则打印消息,否则打印超时消息-
poll 函数:
poll
函数与select
类似,但是提供了更灵活的事件管理和更高的性能#include <iostream> #include <vector> #include <poll.h> #include <unistd.h> int main() { struct pollfd fds[1]; int timeout = 5000; // timeout in milliseconds // Initialize pollfd structure fds[0].fd = STDIN_FILENO; fds[0].events = POLLIN; // Wait for input on stdin or timeout int ret = poll(fds, 1, timeout); if (ret == -1) { perror("poll"); return 1; } else if (ret == 0) { std::cout << "Timeout occurred!\n"; } else { if (fds[0].revents & POLLIN) { std::cout << "Data is available on stdin!\n"; } } return 0; }
epoll 函数:
epoll
是Linux特有的IO多路复用机制,性能比select
和poll
更好,特别是在处理大量并发连接时效果显著。#include <iostream> #include <sys/epoll.h> #include <unistd.h> #include <fcntl.h> #include <cstring> int main() { int epoll_fd = epoll_create1(0); if (epoll_fd == -1) { perror("epoll_create1"); return 1; } struct epoll_event event; event.events = EPOLLIN; event.data.fd = STDIN_FILENO; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) { perror("epoll_ctl"); close(epoll_fd); return 1; } struct epoll_event events[1]; int timeout = 5000; // timeout in milliseconds int ret = epoll_wait(epoll_fd, events, 1, timeout); if (ret == -1) { perror("epoll_wait"); close(epoll_fd); return 1; } else if (ret == 0) { std::cout << "Timeout occurred!\n"; } else { if (events[0].events & EPOLLIN) { std::cout << "Data is available on stdin!\n"; } } close(epoll_fd); return 0; }
当使用
epoll
函数来实现IO多路复用时,需要按照以下步骤进行:步骤分析:
-
创建 epoll 实例:
- 使用
epoll_create1
函数创建一个 epoll 实例,它会返回一个文件描述符,该描述符用于后续的 epoll 操作。int epoll_fd = epoll_create1(0); if (epoll_fd == -1) { perror("epoll_create1"); return 1; }
- 使用
-
注册文件描述符:
- 创建一个
struct epoll_event
结构体实例,用于指定需要监听的事件类型和与之关联的文件描述符。这里我们关注的是读事件EPOLLIN
,即数据可读。struct epoll_event event; event.events = EPOLLIN; // 监听可读事件 event.data.fd = STDIN_FILENO; // 监听标准输入的文件描述符
- 创建一个
-
将文件描述符添加到 epoll 实例中:
- 使用
epoll_ctl
函数将要监听的文件描述符注册到 epoll 实例中。epoll_ctl
的第一个参数是 epoll 实例的文件描述符,第二个参数是操作类型(这里是添加),第三个参数是要添加或修改的文件描述符,第四个参数是一个指向epoll_event
结构体的指针,用于指定事件类型和数据。if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) { perror("epoll_ctl"); close(epoll_fd); return 1; }
- 使用
-
等待事件发生:
- 创建一个数组用于存放事件的结构体,并调用
epoll_wait
函数等待事件发生。epoll_wait
的第一个参数是 epoll 实例的文件描述符,第二个参数是一个数组,用于存放发生事件的结构体,第三个参数是数组的大小,即最多处理的事件数,第四个参数是超时时间。struct epoll_event events[1]; // 这里指定监听一个事件 int timeout = 5000; // 超时时间,单位是毫秒 int ret = epoll_wait(epoll_fd, events, 1, timeout); if (ret == -1) { perror("epoll_wait"); close(epoll_fd); return 1; } else if (ret == 0) { std::cout << "Timeout occurred!\n"; } else { // 处理事件 if (events[0].events & EPOLLIN) { std::cout << "Data is available on stdin!\n"; } }
- 创建一个数组用于存放事件的结构体,并调用
-
处理事件:
- 在
epoll_wait
返回后,检查事件结构体数组中的事件类型。这里主要关注EPOLLIN
事件,表示有数据可读。if (events[0].events & EPOLLIN) { std::cout << "Data is available on stdin!\n"; // 在这里可以读取标准输入的数据,进行相应处理 }
- 在
-
关闭 epoll 实例:
- 使用
close
函数关闭 epoll 实例的文件描述符,释放资源。close(epoll_fd);
- 使用
注意:
在 epoll 中,可以通过设置 EPOLLONESHOT
来指定 ET 触发方式,否则默认是 LT 触发方式。
-
Edge Triggered (ET) 触发方式:
- 使用
EPOLLONESHOT
标志将事件设置为 ET 模式。 - ET 模式下,当文件描述符上有事件发生时,只会通知一次,直到下一次调用
epoll_wait
时才会再次通知。 - 为了继续监听该文件描述符上的事件,需要重新使用
epoll_ctl
添加该文件描述符到 epoll 实例中。
- 使用
-
Level Triggered (LT) 触发方式:
- LT 是 epoll 的默认模式,不需要额外的标志。
- LT 模式下,当文件描述符上有事件发生时,如果事件没有被处理完,下次调用
epoll_wait
仍然会通知。
在示例中,如果希望使用 ET 模式,可以在使用 epoll_ctl
函数注册文件描述符时,添加 EPOLLONESHOT
标志。
event.events = EPOLLIN | EPOLLONESHOT; // 设置 ET 模式
event.data.fd = STDIN_FILENO;
然后,在处理完事件后,需要重新添加文件描述符到 epoll 实例中:
if (events[0].events & EPOLLIN) {
std::cout << "Data is available on stdin!\n";
// 处理事件后,重新添加文件描述符到 epoll 实例
event.events = EPOLLIN | EPOLLONESHOT;
event.data.fd = STDIN_FILENO;
if (epoll_ctl(epoll_fd, EPOLL_CTL_MOD, STDIN_FILENO, &event) == -1) {
perror("epoll_ctl");
close(epoll_fd);
return 1;
}
}