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);
- 使用