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

目录

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的多并发服务器的流程图

  • 服务器实现代码

  • 头文件

    #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);
                        }
                    }
                }
            }
    
        }
    
    }
    

     

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

  • 单个客户端连接

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

​​​​​​​

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

XiaoCheng'Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值