欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Unix环境高级编程多路复用之poll的基本实现

程序员文章站 2022-06-14 11:05:53
...

目录

poll函数简介

参数说明

poll的不足之处

与select相比poll的优点

select的多路复用实现网络socket的多并发服务器的流程图

服务器实现代码

头文件

源文件

运行结果

单个客户端连接​

多客户端连接


​​​​​​​

  • poll函数简介

  select()和poll()系统调用的本质一样,前者在BSD UNIX中引入的,后者在System V中引入的。poll()的机制与 select() 类 似,与 select() 在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理。

 #include <poll.h>
struct pollfd { 
    int     fd;         /* 文件描述符 */  
    short   events;     /* 等待的事件 */   
    short   revents;    /* 实际发生了的事件 */
 } ;

 int poll(struct pollfd *fds, nfds_t nfds, int timeout);

 

  • 参数说明

  1. 第一个参数:用来指向一个struct pollfd类型的数组,每一个pollfd结构体指定了一个被监视的文件描述符,指示poll()监视多个文 件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果 事件掩码,内核在调用返回时设置这个域,events域中请求的任何事件都可能在revents域中返回。下表列出指定 events 标志以 及测试 revents 标志的一些常值:
    常量   说明 是否能作为 events 的输入 是否能作为revents的返回结果
    POLLIN  普通或者优先级带数据可读
    POLLRDNORM  普通数据可读 
    POLLRDBAND  优先级带数据可读 
    POLLPRI  高优先级数据可读 
    POLLOUT  普通数据可写 
    POLLWRNORM  普通数据可写 
    POLLWRBAND  优先级带数据可写
    POLLERR  发生错误 
    POLLHUP  发生挂起 
    POLLNVAL 描述字不是一个打开的文件 

    POLLIN | POLLPRI等价于select()的读事件,POLLOUT |POLLWRBAND等价于select()的写事件。POLLIN等价于 POLLRDNORM |POLLRDBAND,而POLLOUT则等价于POLLWRNORM。例如,要同时监视一个文件描述符是否可读和可 写,我们可以设置 events为POLLIN |POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的 events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而 不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。

  2.  第二个参数 :nfds 指定数组中监听的元素个数;
  3.   第三个参数:timeout指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll() 一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。 这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。

 该函数成功调用时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0; 失败时,poll()返回-1,并设置errno为下列值之一:

  • EBADF           一个或多个结构体中指定的文件描述符无效。  
  • EFAULTfds    指针指向的地址超出进程的地址空间。

  • EINTR       请求的事件之前产生一个信号,调用可以重新发起。  

  • EINVALnfds    参数超出PLIMIT_NOFILE值。

  • ENOMEM       可用内存不足,无法完成请求。


 

  • poll的不足之处

poll() 和 select() 同样存在一个缺点就是,包含大量文件描述符的数组 被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
 

  • 与select相比poll的优点

但是 poll() 没有最大文件描述符数量的限制(但是数量过大后性能也是会下降)。

  • poll的多路复用实现网络socket的多并发服务器的流程图

Unix环境高级编程多路复用之poll的基本实现

  • 服务器实现代码

  • 头文件

    #ifndef __SOCKET_POLL_SERVER_H__
    #define __SOCKET_POLL_SERVER_H__ 
    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <errno.h> 
    #include <ctype.h>
    #include <time.h>
    #include <pthread.h> 
    #include <getopt.h>
    #include <libgen.h>
    #include <sys/types.h>   
    #include <sys/socket.h>
    #include <arpa/inet.h> 
    #include <netinet/in.h>
    #include <poll.h>
    #define ARR_SIZE(x)       (sizeof(x)/sizeof(x[0]))
    #define BUF_SIZE            1024
    
    int get_opt(int argc, char * const argv[],const char *optstring);
    int socket_server_init(int listen_port, char *msg);
    void poll_start(int   listenfd, char *msg);
    void print_usage(char *prograname)
    {
        printf("%s usage : \n", prograname);
        printf("-p(--port): specify sever listen port.\n");
        printf("-m(--msg): specify sever write msg to client.\n");
        printf("-d(--daemon): specify sever will go to run with daemon.\n");
        printf("-h(--help): print this help information.\n");
    
        return  ;
    
    }
    #endif

     

  • 源文件

    
    #include "socket_poll_server.h"
    
    
    int main(int argc, char *argv[])
    {
    
        get_opt(argc, argv,"p:dm:h");
        return 0;
    }
    
    int get_opt(int argc, char * const argv[],const char *optstring)
    {
        int     port = 0;
        int     ch;
        char    *msg ;
    
        struct option        opts[] = {
            {"port", required_argument, NULL, 'p'},
            {"write_msg", required_argument, NULL, 'm'},
            {"daemon", no_argument, NULL, 'd'},
            {"help", no_argument, NULL, 'h'},
            {NULL, 0, NULL, 0}
    
        };
    
        while((ch=getopt_long(argc, argv, "p:m:dh", opts, NULL)) != -1 )
        {
            switch(ch)
            {
                case 'p':
                    port=atoi(optarg);
                    break;
                case 'm':
                    msg = optarg;
                    break;
                case 'd':
                    daemon(0,0);
                    break;
                case 'h':
                    print_usage(argv[0]);
                    return 0;
            }
        }
    
        if( !port||!msg)
        {
            print_usage(argv[0]);
    
            return 0;
        }
    
        socket_server_init(port, msg);
    }
    int socket_server_init(int listen_port, char *msg)
    {
        int                   lisfd = 0;
        int                   clifd = 0;
        int                   on = 1;
        int                   rv = 0;
        char                  buf[BUF_SIZE];
        struct sockaddr_in    serv_addr, cli_addr;
        socklen_t             len = sizeof(serv_addr);
    
        if ((lisfd = socket(AF_INET,SOCK_STREAM, 0))< 0) //服务器第一步socket()
        {
            printf("Socket error:%s\n", strerror(errno));
            return -1;
        }
    
        printf("socket[%d] successfuly!\n", lisfd);
    
        setsockopt(lisfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); //端口短时间可以重复使用
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        serv_addr.sin_port = htons(listen_port);
    
        if ((rv = bind(lisfd, (struct sockaddr *)&serv_addr, len)) < 0)  //服务器第二步,bind()
        {
            printf("Bind error %s\n", strerror(errno));
            goto EXIT;
        }
    
        if ((rv = listen(lisfd, 13)) < 0)  //服务器第三步,listen()
        {
            printf("Listen error:%s\n", strerror(errno));
            goto EXIT;
        }
    
        poll_start(lisfd, msg);  //调用poll_start();
    	
        return clifd;
    EXIT:
        close(lisfd );
        close(clifd );
        return -1;
    }
    
    
    void poll_start(int   listenfd, char *msg)
    {
        int                  max = 0;
        char                 buf[BUF_SIZE];
        struct pollfd        fds_array[1024];
        int                  i;
        int                  rv ;
        int                  found;
        int                  connfd;
    
    
        for (i = 0; i < ARR_SIZE(fds_array); ++i) //将fds_array();置空,因为fd可能=0即初始化为 - 1;
        {
            fds_array[i].fd = -1;
        }
        fds_array[0].fd = listenfd;  //listenfd 进入数组
        fds_array[0].events = POLLIN;  //设置写的事件
    
        for ( ; ; )
        {
            rv = poll(fds_array, max +1, -1);  //poll开始,第三个参数为-1,表示永不超时;
            if (rv < 0)
            {
                 printf("select failure: %s\n", strerror(errno));            
                 break; 
            }
            else if (rv == 0)
            {
                printf("poll get time out.\n");
    
            }
    
            if (fds_array[0].revents & POLLIN)  //判断事件.
            {
                if ( ( connfd = accept(listenfd, (struct sockaddr *)NULL, NULL)) < 0)  //服务器第四步,accept
                {
                    printf("Accept new client error: %s\n", strerror(errno));
                    continue;
                }
    
                found = 0;
    
                for (i = 0; i <ARR_SIZE(fds_array); i++)
                {
                    if (fds_array[i].fd < 0)
                    {
                        printf("Accept new client[%d] and it into ayyar.\n", connfd);
                        fds_array[i].fd = connfd;     //将clifd加入array;
                        fds_array[i].events = POLLIN; //设置事件
                        found = 1;
                        break;
                    }
                }
    
                if (!found)  //判断数组是否已满
                {
                    printf("Accept new client[%d] sueecssful but array is full, so refuse it.\n", connfd);
                    close(connfd);
                }
                max  = i>max?i:max;
    			
                if (--rv <= 0)  //判断已有的事件是否处理完
                {
                    continue;
                }
    
            }
            else 
            {
                for (i = 1; i<ARR_SIZE(fds_array); i++)
                {
                    if (fds_array[i].fd < 0 || fds_array[i].events != POLLIN)
                        continue;
                    if ((rv = read(fds_array[i].fd, buf, BUF_SIZE)) <= 0)  //服务器第五步,read/write
                    {
                        printf("Socket[%d] read failure or get disconnected.\n", fds_array[i].fd);
                        close(fds_array[i].fd);
                        fds_array[i].fd = -1;
                    }
                    else 
                    {
                        printf("socket[%d] read get %d bytes data\n", fds_array[i].fd, rv);
    
                        printf("write start[%d] \n", fds_array[i].fd);
                        if (write(fds_array[i].fd, msg, BUF_SIZE) < 0)
                        {
    
                            printf("socket[%d] write failure: %s\n", fds_array[i].fd, strerror(errno));
                            close(fds_array[i].fd);
                            fds_array[i].fd = -1;
                        }
                        else 
                        {
                            printf("write to client[%d]%s\n", fds_array[i].fd, msg);
                        }
                    }
                }
            }
    
        }
    
    }
    

     

  • 运行结果​​​​​​​

  • 单个客户端连接Unix环境高级编程多路复用之poll的基本实现

  • ​​​​​​​多客户端连接

​​​​​​​Unix环境高级编程多路复用之poll的基本实现

Unix环境高级编程多路复用之poll的基本实现

注:学识尚浅,如有不足地方敬请指出。谢谢!