io多路复用中的poll函数详解

poll函数

poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数

主旨思想:

  1. 首先要构造一个关于pollfd数组,将文件描述符和要检测的时间加入数组中
  2. 调用一个poll函数,监听pollfd数组中的文件描述符,直到这些描述符中的一个或者多个进行I/O操作时,该函数才返回。
    a.这个函数是阻塞
    b.函数对pollfd数组的文件描述符的检测的操作是由内核完成的
  3. 在返回时,它会告诉进程有多少个描述符要进行I/O操作。
    #include <poll.h>//头文件
    struct pollfd {
        int fd;  委托内核检测的文件描述符 
        short events;  委托内核检测文件描述符的什么事件 
        short revents;  文件描述符实际发生的事件 
    };
    //实例
    struct pollfd myfd;
    myfd.fd = 5;
    myfd.events = POLLIN | POLLOUT;

    int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    - 参数:
    - fds : 是一个struct pollfd 结构体数组,这是一个需要检测的文件描述符的集合
    - nfds : 这个是第一个参数数组中最后一个有效元素的下标 + 1
    - timeout : 阻塞时长
        0 : 不阻塞
        -1 : 阻塞,当检测到需要检测的文件描述符有变化,解除阻塞
        >0 : 阻塞的时长
    - 返回值:
        -1 : 失败
        >0(n) : 成功,n表示检测到集合中有n个文件描述符发生变化

使用poll函数监听多个文件描述符的服务端程序

#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#include<sys/poll.h>
int main(char* argc,int argv){
    //创建socket
    int lfd=socket(PF_INET,SOCK_STREAM,0);

    //绑定
    struct sockaddr_in saddr;
    saddr.sin_port=htons(9999);
    saddr.sin_family=AF_INET;
    inet_pton(AF_INET,"127.0.0.1",&saddr.sin_addr.s_addr);
    bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));

    //监听
    listen(lfd,8);//连接与未连接最多8个

    //创建pollfd数组
    struct pollfd fds[128];
    for(int i=0;i<128;++i){
        fds[i].fd=-1;
        fds[i].events=POLLIN;//检测是否有数据可读
    }
    fds[0].fd=lfd;
    int nfds=0;//数组的最大下标

    while(1){
        //调用poll系统函数,让内核帮忙检测文件描述符是否有数据可读
        int ret=poll(fds,nfds+1,-1);//阻塞
        if(ret==-1){
            perror("poll");
            exit(0);
        }else if(ret==0){
            //超时时间到了   没有检测到  就为0  
            //程序中设置为永久阻塞  所以不会出现这种情况
            continue;
        }else{
            // 说明检测到了有文件描述符的对应的缓冲区的数据发生了改变
            if(fds[0].revents&POLLIN) {
                // 表示有新的客户端连接进来了
                struct sockaddr_in cliaddr;
                int len = sizeof(cliaddr);
                int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);

                //获取客户端信息
                char clientIp[16];
                inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,clientIp,sizeof(clientIp));
                unsigned int clientPort=ntohs(cliaddr.sin_port);
                printf("客户端ip:%s,端口:%d\n",clientIp,clientPort);

                // 将新的文件描述符加入到集合中
                for(int i=1;i<128;++i){
                    if(fds[i].fd==-1){
                        fds[i].fd=cfd;
                        fds[i].events=POLLIN;
                        nfds=nfds>i?nfds:i;//更新最大的pollfd数组下标
                        break;
                    }
                }
                
            }
            //监听的文件描述符肯定在最前面
            for(int i=1;i<=nfds;++i){
                if(fds[i].revents&POLLIN){
                    //说明客户端发来的数据
                    char recvBuf[100]={0};
                    int len=read(fds[i].fd,recvBuf,sizeof(recvBuf));
                    if(len>0){
                        printf("read buf = %s\n", recvBuf);
                        write(fds[i].fd, recvBuf, strlen(recvBuf) + 1);
                    }else if(len==0){
                        printf("client close...\n");
                        close(fds[i].fd);
                        fds[i].fd=-1;
                    }else{
                        perror("read");
                        exit(-1);
                    }
                }
            }
        }
    }
    close(lfd);
    return 0;
}

poll函数的缺点:

1.每次调用poll,都需要把pollfd数组从用户态拷贝到内核态,这个开销在过很多时会很大
2.同时每次调用select都需要在内核遍历传递进来的所有pollfd,这个开销在很多时
也很大
3.只会返回有几个文件描述符可以读写 具体是哪几个 还需要在用户程序中遍历

poll与select的区别:

1.poll与select不同,通过一个pollfd数组向内核传递需要关注的事件,故没有描述符个数的限制
2.pollfd中的events字段和revents分别用于标示关注的事件和发生的事件,故pollfd数组只需要被初始化一次。


版权声明:本文为Science_Tec原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。