随笔记录
epoll、select 和 poll优缺点详细介绍
2025-3-10 diaba



IO多路复用是一种高效的并发处理技术,通过内核级别的支持,可以显著提高网络服务的性能和并发能力。常见的IO多路复用技术包括select、poll、epoll、kqueue和IOCP,每种技术都有其适用场景和优缺点。在实际应用中,可以根据具体需求选择合适的IO多路复用机制 


epollselect 和 poll 都是用于实现 IO多路复用 的系统调用,它们允许程序同时监控多个文件描述符(如套接字、文件等),以确定哪些描述符已经准备好进行读写操作。这使得单个线程能够高效地管理多个并发连接,而无需为每个连接创建单独的线程或进程。





尽管它们的目标相同,但实现机制和性能特点有很大差异。以下是对这三种机制的详细介绍:





1. select



原理



select 是最早出现的IO多路复用系统调用,它通过维护三个文件描述符集合(fd_set)来监控文件描述符的状态变化:




调用 select 时,内核会检查这些集合中的所有文件描述符,返回已经准备好的描述符数量。程序可以通过遍历这些集合来找到具体的就绪描述符。



优点




缺点




示例代码


#include <stdio.h> #include <sys/select.h> #include <unistd.h> int main() {
fd_set readfds; struct timeval timeout; int maxfd; // 初始化文件描述符集合 FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds); // 监控标准输入 maxfd = STDIN_FILENO + 1; // 设置超时时间 timeout.tv_sec = 5;
timeout.tv_usec = 0; int result = select(maxfd, &readfds, NULL, NULL, &timeout); if (result == -1) {
perror("select");
} else if (result == 0) { printf("Timeout occurred! No data after 5 seconds.\n");
} else { if (FD_ISSET(STDIN_FILENO, &readfds)) { printf("Data is available now.\n"); // 读取数据 }
} return 0;
}




2. poll



原理



poll 通过一个 pollfd 结构数组来监控文件描述符的状态。每个 pollfd 结构包含一个文件描述符和其关注的事件类型(如可读、可写)。调用 poll 时,内核会检查数组中的所有文件描述符,返回已经准备好的描述符数量。



优点




缺点




示例代码


#include <stdio.h> #include <poll.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); // 5秒超时 if (ret == -1) {
perror("poll");
} else if (ret == 0) { printf("Timeout occurred! No data after 5 seconds.\n");
} else { if (fds[0].revents & POLLIN) { printf("Data is available now.\n"); // 读取数据 }
} return 0;
}




3. epoll



原理



epoll 是Linux特有的IO多路复用机制,通过内核维护一个事件表来监控文件描述符的状态变化。当有事件发生时,内核会直接通知应用程序,而无需像 select 或 poll 那样遍历所有文件描述符。



epoll 的主要操作包括:




  1. epoll_create:创建一个 epoll 实例。


  2. epoll_ctl:向 epoll 实例中添加、修改或删除文件描述符。


  3. epoll_wait:等待事件发生,返回就绪的文件描述符列表。



优点




缺点




示例代码


#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/epoll.h> #define MAX_EVENTS 10 int main() { int epoll_fd, listen_fd, conn_fd; struct epoll_event event, events[MAX_EVENTS]; int nfds; // 创建监听socket listen_fd = socket(AF_INET, SOCK_STREAM, 0); // 绑定地址、监听等操作... // 创建epoll实例 epoll_fd = epoll_create1(0); if (epoll_fd == -1) {
perror("epoll_create"); exit(EXIT_FAILURE);
} // 将监听socket添加到epoll event.data.fd = listen_fd;
event.events = EPOLLIN | EPOLLET; // 边缘触发 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) == -1) {
perror("epoll_ctl"); exit(EXIT_FAILURE);
} while (1) {
nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); for (int n = 0; n < nfds; ++n) { if (events[n].data.fd == listen_fd) { // 接收新连接 conn_fd = accept(listen_fd, NULL, NULL); // 将新连接添加到epoll event.data.fd = conn_fd;
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_fd, &event);
} else { // 处理客户端数据 char buffer[1024]; int bytes = read(events[n].data.fd, buffer, sizeof(buffer)); if (bytes > 0) { printf("Received data: %s\n", buffer);
} else {
close(events[n].data.fd);
}
}
}
}

close(listen_fd);
close(epoll_fd); return 0;
}




总结




在实际开发中,可以根据具体需求选择合适的IO多路复用机制。对于高并发场景,epoll 是首选;对于跨平台需求,select 或 poll 可能更适合。



发表评论:
昵称

邮件地址 (选填)

个人主页 (选填)

内容