IO 多路复用之 poll 总结
在网络编程中,I/O 操作是非常常见且重要的。当需要处理多个 I/O 流时,如果采用传统的阻塞 I/O 方式,程序会在一个 I/O 操作上阻塞,无法同时处理其他 I/O 事件,这显然效率低下。为了解决这个问题,出现了 I/O 多路复用技术。poll 就是其中一种常用的 I/O 多路复用机制,它允许程序同时监视多个文件描述符的 I/O 事件,从而提高程序的并发处理能力。本文将详细介绍 poll 的工作原理、使用方法、优缺点等内容。
目录#
poll 简介#
poll 是 Unix 系统下常用的 I/O 多路复用机制之一,它和 select 类似,但在某些方面进行了改进。poll 可以处理的文件描述符数量没有 select 的限制(select 一般受限于 FD_SETSIZE),并且使用更方便。poll 可以同时监视多个文件描述符上的读、写和异常事件,当这些事件发生时,poll 函数会返回,通知程序进行相应的处理。
poll 函数的原型和参数#
poll 函数的原型如下:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);参数说明:#
fds:一个指向struct pollfd数组的指针,struct pollfd结构体定义如下:
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 监视的事件 */
short revents; /* 发生的事件 */
};- `fd`:需要监视的文件描述符。
- `events`:要监视的事件集合,可以是以下值的按位或组合:
- `POLLIN`:有数据可读。
- `POLLOUT`:可以进行写操作。
- `POLLERR`:发生错误。
- `POLLHUP`:发生挂起。
- `revents`:由内核填充,指示实际发生的事件。
-
nfds:fds数组中元素的个数。 -
timeout:超时时间,以毫秒为单位。-1:永远等待,直到有事件发生。0:立即返回,不等待。- 大于 0:等待指定的毫秒数。
返回值:#
- 大于 0:表示有事件发生的文件描述符的数量。
- 0:表示超时,没有事件发生。
- -1:表示出错,错误信息存储在
errno中。
poll 的工作原理#
poll 的工作原理可以简单概括为:程序将需要监视的文件描述符和对应的事件信息填充到 struct pollfd 数组中,然后调用 poll 函数。poll 函数会将这个数组传递给内核,内核会对这些文件描述符进行监视。当有文件描述符上的指定事件发生时,内核会将发生的事件信息填充到 struct pollfd 数组的 revents 字段中,并返回发生事件的文件描述符的数量。程序通过检查 revents 字段来确定具体发生了哪些事件,并进行相应的处理。
poll 的使用示例#
以下是一个简单的使用 poll 实现的 TCP 服务器示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <poll.h>
#include <unistd.h>
#define MAX_EVENTS 10
#define BUFFER_SIZE 1024
int main() {
int listen_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
// 创建监听套接字
if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
return 1;
}
// 初始化服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8888);
// 绑定套接字
if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
close(listen_fd);
return 1;
}
// 监听连接
if (listen(listen_fd, 5) == -1) {
perror("listen");
close(listen_fd);
return 1;
}
struct pollfd fds[MAX_EVENTS];
int nfds = 1;
// 初始化 pollfd 数组
fds[0].fd = listen_fd;
fds[0].events = POLLIN;
fds[0].revents = 0;
while (1) {
int ready = poll(fds, nfds, -1);
if (ready == -1) {
perror("poll");
break;
}
for (int i = 0; i < nfds; i++) {
if (fds[i].revents & POLLIN) {
if (fds[i].fd == listen_fd) {
// 有新的连接
if ((client_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &client_addr_len)) == -1) {
perror("accept");
continue;
}
// 将新的客户端套接字添加到 pollfd 数组中
if (nfds < MAX_EVENTS) {
fds[nfds].fd = client_fd;
fds[nfds].events = POLLIN;
fds[nfds].revents = 0;
nfds++;
} else {
close(client_fd);
}
} else {
// 有数据可读
char buffer[BUFFER_SIZE];
ssize_t n = recv(fds[i].fd, buffer, sizeof(buffer), 0);
if (n <= 0) {
// 客户端关闭连接
close(fds[i].fd);
// 移除该文件描述符
for (int j = i; j < nfds - 1; j++) {
fds[j] = fds[j + 1];
}
nfds--;
i--;
} else {
// 处理数据
buffer[n] = '\0';
printf("Received: %s\n", buffer);
// 回显数据
send(fds[i].fd, buffer, n, 0);
}
}
}
}
}
// 关闭监听套接字
close(listen_fd);
return 0;
}代码说明:#
- 创建监听套接字并绑定地址,开始监听连接。
- 初始化
struct pollfd数组,将监听套接字添加到数组中,并设置要监视的事件为POLLIN。 - 进入循环,调用
poll函数等待事件发生。 - 当有事件发生时,遍历
struct pollfd数组,检查revents字段。- 如果是监听套接字有事件发生,说明有新的连接,接受连接并将新的客户端套接字添加到
pollfd数组中。 - 如果是客户端套接字有事件发生,说明有数据可读,读取数据并进行处理。如果客户端关闭连接,关闭对应的套接字并从
pollfd数组中移除。
- 如果是监听套接字有事件发生,说明有新的连接,接受连接并将新的客户端套接字添加到
- 最后关闭监听套接字。
poll 的优缺点#
优点#
- 没有文件描述符数量限制:
poll不像select那样受限于FD_SETSIZE,可以处理更多的文件描述符。 - 使用方便:
poll使用struct pollfd数组来管理文件描述符和事件,比select的位操作更直观。
缺点#
- 性能问题:和
select一样,poll每次调用都需要将struct pollfd数组从用户空间复制到内核空间,处理大量文件描述符时会有性能开销。 - 内核循环遍历:内核需要遍历
struct pollfd数组来检查哪些文件描述符有事件发生,时间复杂度为 ,当文件描述符数量很大时,性能会下降。
常见实践和最佳实践#
常见实践#
- 合理设置超时时间:根据实际需求设置
poll函数的超时时间,避免长时间阻塞。 - 及时处理事件:当
poll函数返回后,应及时处理发生的事件,避免事件积压。
最佳实践#
- 结合其他技术:可以将
poll与多线程或多进程结合使用,提高程序的并发处理能力。 - 动态管理文件描述符:根据实际情况动态添加或移除
struct pollfd数组中的元素,避免不必要的资源浪费。
总结#
poll 是一种常用的 I/O 多路复用机制,它在处理多个文件描述符的 I/O 事件时比传统的阻塞 I/O 方式更高效。poll 没有文件描述符数量的限制,使用也比较方便。但它也存在一些性能问题,特别是在处理大量文件描述符时。在实际应用中,我们可以根据具体情况选择合适的 I/O 多路复用机制,同时结合一些最佳实践来提高程序的性能和可靠性。
参考资料#
- 《Unix 网络编程》(第 3 版)
- Linux 系统手册:
man poll - The Linux Documentation Project - poll