【计算机网络】IO复用方法(三)——poll
目录
一、poll 的基本概念
二、 poll 函数原型
三、pollfd 结构详解
四、poll 的使用步骤
五、poll 示例代码
六、poll 的优缺点
七、注意事项
一、poll 的基本概念
poll 是一种 I/O 多路复用技术,用于监视多个文件描述符的状态(可读、可写或异常)。与 select 类似,在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。但 poll 解决了 select 的一些限制,如文件描述符数量的限制。
poll 通过一个 pollfd 结构数组来管理文件描述符,支持更多的事件类型,且没有固定数量的文件描述符限制(仅受系统资源限制)。
二、 poll 函数原型
poll 函数原型:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数说明:
- fds:pollfd 结构数组,指定我们感兴趣的所有文件描述符的可读、可写和异常事件。
- nfds:数组长度
- timeout:超时时间(毫秒),-1 表示无限等待
返回值:
- 正数:就绪的文件描述符数量
- 0:超时
- -1:出错
三、pollfd 结构详解
pollfd 结构定义如下:
struct pollfd {int fd; // 文件描述符short events; // 监视的事件short revents; // 实际发生的事件
};
fd:指定文件描述符,
events:告诉poll监听fd上的哪些事件,是一系列的事件的按位或
revents:由内核修改,通知程序fd实际发生的哪些事件
可以在堆区malloc
常用事件标志:
- POLLIN:数据可读
- POLLOUT:可写
- POLLERR:错误发生
- POLLHUP:挂起
- POLLNVAL:无效请求
四、poll 的使用步骤
定义一个 pollfd 结构数组,每个元素对应一个需要监视的文件描述符。结构包含文件描述符 fd、感兴趣的事件 events 和实际发生的事件 revents。
调用 poll 函数,传入 pollfd 数组、数组长度和超时时间(毫秒)。函数会阻塞直到有事件发生或超时。
检查每个 pollfd 的 revents 字段,确定哪些文件描述符就绪,并进行相应操作。
五、poll 示例代码
以下是一个简单的 poll 服务器示例,监听标准输入和套接字:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <poll.h>
#define MAXFD 10
int socket_init();
void fds_init(struct pollfd fds[]){for(int i=0;i<MAXFD;i++){fds[i].fd=-1;fds[i].events=0;fds[i].revents=0;}
}
//添加元素
void fds_add(struct pollfd fds[],int fd)
{for(int i=0;i<MAXFD;i++){if(fds[i].fd=-1){fds[i].fd=fd;fds[i].events=POLLIN;//读事件fds[i].revents=0;break;//只找一个空位}}
}
//删除元素
void fds_del(struct pollfd fds[],int fd)
{for(int i=0;i<MAXFD;i++){if(fds[i].fd=fd){fds[i].fd=-1;fds[i].events=0;fds[i].revents=0;break;//只找一个空位}}
}
//监听套接字
void accept_client(int sockfd,struct pollfd fds[]){int c=accept(sockfd,NULL,NULL);if(c<0){return;}printf("accpect c=%d\n",c);fds_add(fds,c);//将c添加
}
//连接套接字
void recv_data(int c,struct pollfd fds[]){char buff[128];int n=recv(c,buff,127,0);if(n<=0){fds_del(fds,c);close(c);printf("client close\n");return;}printf("buff (c=%d)=%s\n",c,buff);send(c,"ok",2,0);
}
int main(){int sockfd=socket_init();//创建监听套接字,当客户端执行connect连接服务器时,读事件就绪if(sockfd==-1){exit(1);}struct pollfd fds[MAXFD];fds_init(fds);//空fds_add(fds,sockfd);while (1){int n=poll(fds,MAXFD,5000);//阻塞最长等待5s,当n>0,则if(n==-1){printf("poll errpr");}else if(n==0){printf("time out");}else{for(int i=0;i<MAXFD;i++){if(fds[i].fd==-1){continue;}if(fds[i].revents&POLLIN){if(fds[i].fd==sockfd){accept_client(sockfd,fds);}else{recv_data(fds[i].fd,fds);}}}}}}
int socket_init(){//创建监听套接字int sockfd = socket(AF_INET,SOCK_STREAM,0);if( sockfd == -1){//创建套接字失败return -1;}struct sockaddr_in saddr;//ipv4专用,制定ip端口memset(&saddr,0,sizeof(saddr));//清空saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("127.0.0.1");int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//绑定,通用套接字if( res == -1){printf("bind err\n");return -1;}res = listen(sockfd,5);//监听队列if( res == -1){return -1;}return sockfd;
}
主函数:创建套接字,该部分主要用于指定ip端口,实时监听;定义一个数组,该数组用于创建文件描述符;首先情况该数组,将所有的值置为-1;添加,将sockfd添加到数组fds中;添加文件描述符到fds数组中,此时fds拥有的用户关心的描述符socket fd和事件(该实例只关心读事件)。此时调用库,将数组以及结构体大小传进来,得到返回值。(正数则为找到就绪的)。判断是哪一种套接字,如果是监听套接字则accpet,反之recv。
总结:找到哪个就绪的数据去处理。
六、poll 的优缺点
优点:
- 没有文件描述符数量限制(select 通常限制为 1024)
- 更高效,尤其在大量空闲文件描述符时
- 更灵活的事件类型支持
缺点:
- 每次调用仍需遍历整个文件描述符集合
- 在 Linux 上不支持文件描述符的动态增减(epoll 支持)
- 性能在极高并发时可能不如 epol
七、注意事项
使用 poll 时应注意:
- 确保 pollfd 数组中的 fd 字段有效
- 正确处理 timeout 参数,避免 CPU 空转
- 检查 revents 时使用位与操作(&)
- 在多线程环境中注意线程安全问题
- 某些系统可能不支持所有事件类型
