目录

一、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 时使用位与操作(&)
    • 在多线程环境中注意线程安全问题
    • 某些系统可能不支持所有事件类型
    Logo

    DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

    更多推荐