poll
poll使用相对较少,跨平台推荐select,高并发推荐epoll
一、poll 核心概念
poll 是 Linux 下另一种多路复用 I/O 模型,与你之前学习的 select 功能类似(监控多个文件描述符的事件),但解决了 select 的部分缺陷,是网络编程中实现多客户端并发的常用方案。
1.1 与 select 的核心区别(对比你的 select 代码)
| 特性 |
select |
poll |
| 文件描述符集合存储 |
固定大小的位图(fd_set) |
动态的结构体数组(pollfd) |
| 最大监控描述符限制 |
受限于 FD_SETSIZE(默认 1024) |
无硬限制,仅受系统资源约束 |
| 事件重置 |
每次需重新初始化集合 |
无需重置,事件结果存在 revents 中 |
| 参数传递 |
值传递(会修改原集合) |
结构体数组(输入输出分离) |
1.2 核心优势
- 突破
select 最大 1024 个文件描述符的限制;
- 无需像
select 那样每次循环重置文件描述符集合;
- 支持更多类型的事件监控(如普通读、带外数据读、出错等)。
二、poll 核心 API 详解
2.1 头文件
2.2 pollfd 结构体(核心)
poll 通过结构体数组管理要监控的文件描述符和事件,替代 select 的 fd_set:
1 2 3 4 5
| struct pollfd { int fd; short events; short revents; };
|
常用事件宏(events/revents 取值)
| 事件宏 |
含义 |
适用场景 |
| POLLIN |
有数据可读(读事件) |
监听新连接、客户端发数据 |
| POLLOUT |
可写数据(写事件) |
向客户端发送大量数据 |
| POLLERR |
出错事件 |
检测套接字错误 |
| POLLHUP |
挂起事件(客户端断开连接) |
检测客户端主动关闭 |
2.3 poll 函数
1
| int poll(struct pollfd *fds, nfds_t nfds, int timeout);
|
参数说明
fds:pollfd 结构体数组的首地址,存放要监控的所有文件描述符及事件;
nfds:数组中有效元素的个数(需指定监控的文件描述符数量);
timeout:超时时间(毫秒):
-1:永久阻塞,直到有事件触发(同 select 的 NULL);
0:非阻塞,立即返回,不管是否有事件;
>0:阻塞指定毫秒数,超时后返回 0。
返回值
>0:触发事件的文件描述符总数;
0:超时,无事件触发;
-1:调用失败(如被信号中断),设置 errno。
三、基于 poll 实现 TCP 回显服务器(对标你的 select 代码)
3.1 完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<arpa/inet.h> #include<string.h> #include<poll.h>
#define MAX_CLIENT 1024
int main() { int lfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in caddr; caddr.sin_family = AF_INET; caddr.sin_port = htons(10000); caddr.sin_addr.s_addr = INADDR_ANY;
int bind_res = bind(lfd, (struct sockaddr*)&caddr, sizeof(caddr)); if (bind_res == -1) { perror("bind failed"); close(lfd); return -1; }
listen(lfd, 128);
struct pollfd fds[MAX_CLIENT] = {0}; fds[0].fd = lfd; fds[0].events = POLLIN; for (int i = 1; i < MAX_CLIENT; i++) { fds[i].fd = -1; } int nfds = 0;
while(1) { int ret = poll(fds, MAX_CLIENT, -1); if (ret == -1) { perror("poll failed"); continue; }
if (fds[0].revents & POLLIN) { struct sockaddr_in caddr; int caddr_len = sizeof(caddr); int cfd = accept(lfd, (struct sockaddr*)&caddr, &caddr_len); int i; for (i = 1; i < MAX_CLIENT; i++) { if (fds[i].fd == -1) { fds[i].fd = cfd; fds[i].events = POLLIN; break; } } if (i > nfds) { nfds = i; } }
for(int i = 1; i <= nfds; ++i){ if (fds[i].fd == -1) { continue; } if (fds[i].revents & (POLLIN | POLLERR | POLLHUP)) { char buffer[10]; int len = read(fds[i].fd, buffer, sizeof(buffer)); if(len > 0){ write(fds[i].fd, buffer, len); } else if(len == 0 || (fds[i].revents & POLLHUP)){ printf("客户端关闭连接...\n"); close(fds[i].fd); fds[i].fd = -1; } else{ perror("read"); close(fds[i].fd); fds[i].fd = -1; } } } }
close(lfd); return 0; }
|
3.2 核心逻辑对比
| 步骤 |
select 实现 |
poll 实现 |
| 监控集合初始化 |
FD_ZERO/FD_SET 操作 fd_set |
初始化 pollfd 数组,fd=-1 标记未使用 |
| 事件监控 |
select(maxfd+1, &rdtemp, …) |
poll(fds, nfds, -1) |
| 新连接判断 |
FD_ISSET(lfd, &rdtemp) |
fds[0].revents & POLLIN |
| 客户端事件判断 |
FD_ISSET(i, &rdtemp) |
fds[i].revents & POLLIN |
| 资源清理 |
FD_CLR(i, &rdset) + close |
fds[i].fd=-1 + close |