poll函数
- poll 是 Linux 下 I/O 多路复用的实现函数之一,解决了 select文件描述符数量受限的核心问题,采用结构体数组管理待监听的文件描述符,无需计算最大 fd+1,使用更灵活,是 select 的优化版本
函数原型
c
复制代码
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数
| 参数 |
类型 |
含义与使用说明 |
fds |
struct pollfd * |
指向pollfd 结构体数组的指针,存放待监听的文件描述符及对应的事件(输入 / 输出) |
nfds |
nfds_t |
fds数组的有效元素个数(即待监听的文件描述符数量),无 fd+1 的要求 |
timeout |
int |
超时时间,单位为毫秒 负数:永久阻塞,直到有事件发生 0:非阻塞模式,立即返回检测结果 正数:阻塞指定毫秒数,超时无事件则返回 0 |
结构体pollfd
- poll 通过独立的结构体分离请求事件(events)和返回事件(revents),无需像 select 那样每次调用重新初始化事件集合,是核心设计优化点
c
复制代码
struct pollfd {
int fd; /* 待监听的文件描述符;设为-1时,内核会忽略该结构体 */
short events; /* 【入参】注册的、需要监听的事件(由宏定义,可组合) */
short revents; /* 【出参】内核返回的、实际发生的事件(内核自动赋值,只读) */
};
事件类型(宏定义)
- events/revents 均通过以下宏指定事件,多个事件可通过 按位或(|)组合,常用核心事件:
| 事件宏 |
含义 |
POLLIN |
对应文件描述符有数据可读(核心读事件,如套接字收到数据、监听套接字有新连接) |
POLLPRI |
有紧急数据需要读取(如 TCP 带外数据 OOB) |
POLLOUT |
对应文件描述符可写(核心写事件,如套接字发送缓冲区有空闲空间) |
POLLERR |
对应文件描述符发生错误(由内核自动设置,仅出现在 revents 中) |
POLLHUP |
对应文件描述符被挂断(如 TCP 连接对端关闭,仅出现在 revents 中) |
返回值
| 返回值 |
含义 |
处理逻辑 |
| > 0 |
成功 |
实际发生事件的文件描述符总数 ;需遍历fds数组,通过revents判断具体哪个 fd 发生了什么事件 |
| 0 |
超时 |
阻塞时间内无任何文件描述符触发事件,revents无有效赋值 |
| -1 |
失败 |
错误码存入errno;若为EINTR(被信号中断),通常需重启 poll 循环;其他错误(如参数无效)需退出处理 |
特点
优点
- 无 fd 数量硬限制:select 受FD_SETSIZE(默认 1024)限制,poll 通过数组管理,仅受系统最大文件描述符数(ulimit -n)限制,支持监听更多连接
- 事件集合无需重复初始化:select 的 fd_set 每次调用前需重新置零、添加 fd;poll 的events是入参,revents是出参,内核不会修改events,只需一次注册,后续可复用
- fd 管理更灵活:将某个 pollfd 的fd设为 - 1,内核会直接忽略,无需从数组中删除,适合动态添加 / 移除监听fd
- 返回值更直观:直接返回发生事件的 fd 总数,无需像 select 那样遍历所有 fd 判断是否就绪
缺点
- 效率仍有瓶颈:内核仍需轮询整个 fds 数组判断事件是否发生,当监听 fd 数量极多(如上万)时,轮询开销大,性能下降
- 仅支持水平触发(LT):与 select 一致,只要 fd 的事件未处理完成(如数据未读完),每次调用 poll 都会重复返回该事件
- 跨平台性差:仅支持 Linux/Unix 类系统,无 Windows 原生支持(select 是跨平台的)
demo
c
复制代码
#ifndef _NET_H_
#define _NET_H_
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
#include <errno.h>
typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
#define BACKLOG 5
#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)
void Argment(int argc, char *argv[]);
int CreateSocket(char *argv[]);
int DataHandle(int fd);
#endif
c
复制代码
#include "net.h"
void Argment(int argc, char *argv[]){
if(argc < 3){
fprintf(stderr, "%s<addr><port>\n", argv[0]);
exit(0);
}
}
int CreateSocket(char *argv[]){
/*创建套接字*/
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0)
ErrExit("socket");
/*允许地址快速重用*/
int flag = 1;
if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag) ) )
perror("setsockopt");
/*设置通信结构体*/
Addr_in addr;
bzero(&addr, sizeof(addr) );
addr.sin_family = AF_INET;
addr.sin_port = htons( atoi(argv[2]) );
/*绑定通信结构体*/
if( bind(fd, (Addr *)&addr, sizeof(Addr_in) ) )
ErrExit("bind");
/*设置套接字为监听模式*/
if( listen(fd, BACKLOG) )
ErrExit("listen");
return fd;
}
int DataHandle(int fd){
char buf[BUFSIZ] = {};
Addr_in peeraddr;
socklen_t peerlen = sizeof(Addr_in);
if( getpeername(fd, (Addr *)&peeraddr, &peerlen) )
perror("getpeername");
int ret = recv(fd, buf, BUFSIZ, 0);
if(ret < 0)
perror("recv");
if(ret > 0){
printf("[%s:%d]data: %s\n",
inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);
}
return ret;
}
c
复制代码
#include "net.h"
#include <poll.h>
#define MAX_SOCK_FD 1024
int main(int argc, char *argv[])
{
int i, j, fd, newfd;
nfds_t nfds = 1;
struct pollfd fds[MAX_SOCK_FD] = {};
Addr_in addr;
socklen_t addrlen = sizeof(Addr_in);
/*检查参数,小于3个 直接退出进程*/
Argment(argc, argv);
/*创建已设置监听模式的套接字*/
fd = CreateSocket(argv);
fds[0].fd = fd;
fds[0].events = POLLIN;
while(1){
if( poll(fds, nfds, -1) < 0)
ErrExit("poll");
for(i = 0; i < nfds; i++){
/*接收客户端连接,并生成新的文件描述符*/
if(fds[i].fd == fd && fds[i].revents & POLLIN){
if( (newfd = accept(fd, (Addr *)&addr, &addrlen) ) < 0)
perror("accept");
fds[nfds].fd = newfd;
fds[nfds++].events = POLLIN;
printf("[%s:%d][nfds=%lu] connection successful.\n",
inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), nfds);
}
/*处理客户端数据*/
if(i > 0 && fds[i].revents & POLLIN){
if(DataHandle(fds[i].fd) <= 0){
if( getpeername(fds[i].fd, (Addr *)&addr, &addrlen) < 0)
perror("getpeername");
printf("[%s:%d][fd=%d] exited.\n",
inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), fds[i].fd);
close(fds[i].fd);
for(j=i; j<nfds-1; j++)
fds[j] = fds[j+1];
nfds--;
i--;
}
}
}
}
close(fd);
return 0;
}