千家信息网

IO复用之——poll

发表于:2026-10-31 作者:千家信息网编辑
千家信息网最后更新 2026年10月31日,一. 关于poll对于IO复用模型,其优点无疑是免去了对一个个IO事件就绪的等待,转而代之的是同时对多个IO数据的检测,当检测等待的事件中至少有一个就绪的时候,就会返回告诉用户进程"已经有数据准备好了
千家信息网最后更新 2026年10月31日IO复用之——poll

一. 关于poll

对于IO复用模型,其优点无疑是免去了对一个个IO事件就绪的等待,转而代之的是同时对多个IO数据的检测,当检测等待的事件中至少有一个就绪的时候,就会返回告诉用户进程"已经有数据准备好了,快看看是哪个赶紧处理",而对于IO复用的实现,除了可以用select函数,另外一个函数仍然支持这种复用IO模型,就是poll函数;



二. poll函数的用法

虽然同样是对多个IO事件进行检测等待,但poll和select多少还是有些不同的:

函数参数中,

先来说nfds,这个是指当前需要关心的文件描述符的个数;

timeout同样是设置超时时间,只是和select的timeout是一个结构体不一样,这里只是一个整型类型,且含义是毫秒;

而fds是一个结构体指针,如下:

结构体中,

fd表示所要关心的文件描述符;

events表示该文件描述符所关心的事件,这是一个输入型参数,要告诉操作系统这个文件描述符对应的事件所关心的操作事件是什么,比如读或写;

revents是一个输出型参数,表示当poll返回时告诉用户什么操作事件是就绪的,比如如果POLLIN是就绪的,那么返回时revent的值就是POLLIN,告诉用户fd事件的POLLIN是就绪的;

events和revents的值可以为如下:

这里要说明选项其实不止这三个,只是这里的讨论中这三个选项是最常用的;

events设置为POLLIN表示fd所需要读取数据,而revents若返回POLLIN则表示data已经ready可以读取了;

同样,events设置为POLLOUT表示fd所关心数据的写入,而revents返回POLLOUT则表示写事件就绪可以进行数据的写入;

至于POLLPRI,后面的解释是作为紧急选项来设置的,在TCP协议报文中有个URG的紧急指针是表示先从紧急数据的地方开始读取,这里也是这个意思;



三. 栗子时间

同样的,这里可以用poll来编写基于TCP协议的server端和client端的数据通信,和select一样,避免使用多进程和多线程的方式转而使用对多个IO接口的等待:


server服务器端:

#include #include #include #include #include #include #include #include #include #define _BACKLOG_ 5//网络中连接请求队列中的最大等待数目#define _NUM_ 10//IO事件结构体数组大小void usage(const char *argv)//命令行参数的差错判断{    printf("%s   [ip]   [port]\n", argv);    exit(0);}static int CreateListenSocket(int ip, int port)//创建监听套接字{    int sock = socket(AF_INET, SOCK_STREAM, 0);     if(sock < 0)    {           perror("socket");        exit(1);    }       struct sockaddr_in server;//设置本地网络地址信息    server.sin_family = AF_INET;    server.sin_port = htons(port);    server.sin_addr.s_addr = ip;     if(bind(sock, (struct sockaddr*)&server, sizeof(server)) < 0)//绑定    {           perror("bind");        exit(2);    }        if(listen(sock, _BACKLOG_) < 0)//监听    {        perror("listen");        exit(3);    }    return sock;}int main(int argc, char *argv[]){    if(argc != 3)        usage(argv[0]);    int port = atoi(argv[2]);    int ip = inet_addr(argv[1]);    int listen_sock = CreateListenSocket(ip, port);//获取监听socket    struct sockaddr_in client;//用于存放client端的网络地址信息    socklen_t client_len = sizeof(client);    struct pollfd fds[_NUM_];//用一个结构体数组来存放各种IO事件    fds[0].fd = listen_sock;//将监听套接字放入数组中    fds[0].events = POLLIN;//将监听事件需要的event设置为读取数据POLLIN    fds[0].revents = 0;    size_t i = 1;    for(; i < _NUM_; ++i)//初始化结构体数组中成员    {        fds[i].fd = -1;        fds[i].events = 0;        fds[i].revents = 0;    }    int max_fd = 1;//设置最大的文件描述符个数    int timeout = 5000;//设置超时时间        while(1)    {        switch(poll(fds, max_fd, timeout))        {            case -1://出错                perror("poll");                break;            case 0://超时                printf("timeout...\n");                break;            default://至少有一个事件已经就绪,可以进行IO数据的操作                {                    size_t i = 0;                    for(; i < _NUM_; ++i)                    {                            //判断是否为监听事件就绪,如果是代表有连接请求需要处理                        if((fds[i].fd == listen_sock) && (fds[i].revents == POLLIN))                        {                                //处理连接                            int accept_sock = accept(listen_sock, (struct sockaddr*)&client, &client_len);                            if(accept_sock < 0)                            {                                perror("accept");                                continue;                            }                            printf("connect with a client... [ip]:%s  [port]:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));                            size_t i = 0;                            //将创建出新的数据传输socket文件描述符设置进poll结构体数组                            for(; i < _NUM_; ++i)                            {                                if(fds[i].fd == -1)                                {                                    fds[i].fd = accept_sock;                                    fds[i].events = POLLIN;//将事件设置为需要读取数据                                    max_fd++;//更新最大文件描述符个数                                    break;                                }                                                            }                            if(i == _NUM_)                                close(accept_sock);//若超出数组个数则表明当前无法处理,关闭掉                        }//表示为其他除了listen socket之外的要进行数据传输的socket                        else if((fds[i].fd > 0) && (fds[i].revents == POLLIN))                        {                            char buf[1024];                            //进行数据的读取                            ssize_t size = read(fds[i].fd, buf, sizeof(buf)-1);                            if(size < 0)//读取出错                                perror("read");                            else if(size == 0)//客户端关闭,将结构体数组中相应位置清空                            {                                 printf("client closed...\n");                                 //将要关闭的文件描述符和结构体数组中最后一个有效的文件描述符进行交换                                 //确保当前有效的文件描述符在数组内都是连续的                                                                 struct pollfd tmp = fds[i];                                                                 fds[i] = fds[max_fd-1];                                                                 fds[max_fd-1] = tmp;                                                                                                                                  //交换后要删除的就在数组的最后,关闭且将数组内相应位置为无效值                                                                 close(fds[max_fd-1].fd);                                                                 fds[max_fd-1].fd = -1;                                                                  fds[max_fd-1].events = 0;                                                                 fds[max_fd-1].revents = 0;                                                                 --max_fd;//更新当前有效文件描述符的个数                            }                            else//读取成功,输出数据                            {                                buf[size]  ='\0';                                printf("client# %s\n", buf);                            }                        }                        else                        {}                    }                }                break;        }    }    return 0;}

client客户端这里就不写了,和基于TCP协议的socket编程中的client是差不多的;


运行程序:


这里需要说明的是:

  1. 和select不同,select是用一个fd_set数据类型来存放各个需要操作的IO文件描述符,这里的poll是用一个结构体来存放文件描述符和锁关心的事件类型,因此,poll并没有处理文件描述符的上限,但相同的是,每一次poll返回都仍然需要遍历来获取事件就绪的位置以此来进行相应的处理,还是一样有复制和系统遍历带来的额外开销,当处理事件比较多的时候仍然是低效的;

  2. 在每一次循环到select之前都需要将对应事件的fd_set的集合调用FD_ZERO函数来进行重新的初始化清零,因为不是会有文件描述符的新建和关闭,需要进行初始化然后再将事件重新一一设置进相应的fd_set里面;而poll并不需要,因为使用一个结构体数组来管理相当于结合了select中的设置数组存放文件描述符和添加设置这两步,每一次进行poll的时候都会将发生就绪的事件对应的revents置位,当处理完毕就会被系统自动归为0,并不需要进行手动初始化清零;

  3. 在结构体数组还未使用之前,和被刚定义的变量一样,结构体成员fd、events和revents都是随机值,虽然后来使用的时候都会被赋对应的有效值,但为了避免判断时的二义性问题最好还是在循环使用前都将其初始化为统一的可识别的无效值;



《完》

事件 文件 数据 数组 结构 处理 函数 监听 有效 参数 时候 复用 个数 位置 只是 多个 时间 用户 类型 系统 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网是第几次科技革命的成果 互联网与科技发展 深圳艾佳互联网科技 成都狮子座互联网科技 盛创互联网科技有限公司 开启互联网科技时代小说 北京华百购互联网科技有限公司 互联网软件和科技 黑科技闪耀互联网之光 南博望互联网科技有限公司 锡山区互联网智能科技产品供应 启辰互联网科技 从那以后互联网科技有限公司 版纳互联网科技选哪家 美国五大互联网科技公司市值多少 三亚科技创新互联网基地 第三届互联网大会安防黑科技 山东蛋芒互联网科技有限公司 河南商务圈互联网科技有限公司 银川新华互联网科技学校招聘 百单互联网科技有限公司 润盈互联网科技 互联网 新科技 科技互联网的估值 中化能源互联网科技招聘 受益于互联网科技的高速发展 佛山市翊博互联网络科技有限公司 科技互联网的核心竞争力 黑科技 体验场景 互联网大会 珠海东科互联网科技有限公司
0