Linux系统编程--网络socket编程 之 I/O多路复用(poll)服务器编程

我发现,当天复习的知识点,当天写博客印象会很深刻,我昨天复习了poll模型的服务器,打算学了epoll之后再来写poll的博客,可是正在梳理poll的时候,却回想了很久才梳理通顺,写博客还是要趁热打铁,每天都回顾一下自己以前写的一些博客,能让知识点更加牢靠。

poll简介

poll模型与select模型相差其实并不大,代码只用改一点就可以了,他们的区别在于,poll突破了select对文件描述符的限制,但是对于poll来说,当客户端连接数过大时,还是会导致其性能大打折扣,因为,select与poll在接收到客户端的连接申请后,后会将其存储在一个数组中,对于poll,这个数组是一个结构体数组,这也是他不同于select的其中一点,但是,当有客户端准备就绪时,他们都需要遍历整个数组(对于select是遍历fd_set),无论时间是否发生。这也是他们再后来慢慢被淘汰的原因;

poll函数

#include <poll.h>

struct pollfd
{
    int fd; /* 文件描述符 */
    short events; /* 等待的事件 */
    short revents; /* 实际发生了的事件 */
} ;

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

struct pollfd *fds : poll函数的第一个参数,是一个结构体数组,他的成员如代码所示,需要注意的是,通select服务器中用来存储文件描述符的数组一样,在使用该结构体数组前,我们同样需要将结构体里的 fd 成员全部置为 -1;这样就便是这个房间是空的,可以用来存储描述符了;第二个参数是我们等待文件发生的事件,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被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。

nfds_t nfds :指定数组中监听的文件个数。

第三个参数:
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服务器

/*********************************************************************************
 *      Copyright:  (C) 2020 Xiao yang IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  poll_server.c
 *    Description:  This file server of poll
 *                 
 *        Version:  1.0.0(03/10/2020)
 *         Author:  Lu Xiaoyang <[email protected]>
 *      ChangeLog:  1, Release initial version on "03/10/2020 06:30:39 PM"
 *                 
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.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   ARRAY_SIZE(x)   (sizeof(x)/sizeof(x[0])) 

int socket_Be_ready(char *listen_ip,int listen_port);   //将socket(),bind(),listen() 打包
void print_usage(char *progname)   //执行程序的帮助信息
{
    printf("progname usage:\n");
    printf("-p(--port) input the port\n");
    printf("-d(--daemon) progranm will running at background\n");
    printf("-h(--help) get help massage\n");
    return;
}

int main(int argc, char *argv[])
{
    int                  clifd,listenfd;
    char                *listen_ip;
    char                *progname;
    char                 buf[1024];
    int                  i,j;
    int                  max = 0;
    int                  listen_port;
    int                  rv;
    int                  found = 0;
    int                  ch;
    int                  daemon_run = 0;   //后台运行
    struct pollfd        fds_array[1024];   //存储感兴趣的描述符以及他们待发生的事件
    struct option        opts[] = {
        {"port",required_argument,NULL,'p'},
        {"daemon",no_argument,NULL,'d'},
        {"help",no_argument,NULL,'h'},
        {NULL,0,NULL,0}
    };   //注册命令行命令,当用户执行命令时,可从命令行获取参数
    
    progname = basename(argv[0]);
    while((ch = getopt_long(argc,argv,"p:dh",opts,NULL)) != -1)
    {
        switch(ch)
        {
            case 'p':
                listen_port = atoi(optarg);
                break;

            case 'd':
                daemon_run = 1;
                break;

            case 'h':
                print_usage(progname);
                break;

            default:
                break;
        }
    }                                // 获取命令行参数

    if(!listen_port)
    {
        print_usage(progname);
        printf("Get no argument,Please try again\n");
        return -1;
    }

    if((listenfd = socket_Be_ready(NULL,listen_port)) < 0)
    {
        printf("Create server failure:%s\n",strerror(errno));
        return -2;
    }
    printf("Create server ok\n");

    if(daemon_run)  
    {
        daemon(0,0);   //将程序调到后台(background)运行
    }

    for(i = 0;i < ARRAY_SIZE(fds_array);i++)   //遍历结构体数组,将每个结构体的成员 fd 设置为  -1,即这个位置时空位
    {
        fds_array[i].fd = -1;
    }
    fds_array[0].fd = listenfd;   //将监听文件描述符存入数组
    fds_array[0].events = POLLIN;   //设置可读
    max = 0;


    for( ; ; )
    {
        printf("Waiting client connect or massage come......\n");
        rv = poll(fds_array,max+1,-1);   //poll将会阻塞在此,直到数组中有一个或多个描述符准备就绪,并返回该值
        if(rv < 0)   //poll 失败
        {
            perror("poll failure");
            close(listenfd);
            return -3;
        }

        else if(0 == rv)
        {
            perror("Time out");
            continue;
        }

        if(fds_array[0].revents & POLLIN)   //如果数组的第一个文件描述符(listenfd)的返回事件是 POLLIN,则说明有新客户端请求连接
        {
            if((clifd = accept(listenfd,(struct sockaddr *)NULL,NULL)) < 0)   //接受客户端,返回一个文件描述符
            {
                perror("accept failure");
                continue;
            }
            for(i = 1;i < ARRAY_SIZE(fds_array);i++)   //找出空房间,让客人入住
            {
                if(fds_array[i].fd < 0)
                {
                    fds_array[i].fd = clifd;
                    fds_array[i].events = POLLIN;   //设置等待事件
                    printf("Put new client[%d] into fds_array[%d].fd\n",clifd1,i);
                    found = 1;
                    break;
                }
            }
            if(!found)
            {
                printf("Put clifd into array failure:FULL\n");
                close(clifd);
                continue;
            }

            max = i>max ? i : max;  //找到最大值

             if (--rv <= 0)
                  continue;
        }
        else   //已连接的客户端发送数据可读
        {
            for(i = 1;i < ARRAY_SIZE(fds_array);i++)   //遍历整个数组,找到调皮的客户端收拾
            {
                if(fds_array[i].fd < 0 || !(fds_array[i].revents & POLLIN)) //两个条件
                    continue;
                if((rv = read(fds_array[i].fd,buf,sizeof(buf))) <= 0)
                {
                    printf("Read client[%d] failure\n",fds_array[i].fd);
                    fds_array[i].fd = -1;
                    break;
                }
                printf("Read %d bytes data from client[%d]:%s\n",rv,fds_array[i].fd,buf);
                
                for(j = 0;j < rv;j++)
                {
                    buf[j] = toupper(buf[j]);
                }

                if((rv = write(fds_array[i].fd,buf,sizeof(buf))) <= 0)
                {
                    printf("Write to client[%d] failure:%s\n",fds_array[i].fd,strerror(errno));
                    close(fds_array[i].fd);
                    fds_array[i].fd = -1;
                    break;
                }
                close(fds_array[i].fd);
                printf("client[%d] exit\n",fds_array[i].fd);
                fds_array[i].fd = -1;
            }
        }
    }

      return 0;
}
    

int socket_Be_ready(char *listen_ip,int listen_port)
{
    int                   on = 1;
    int                   listenfd = -1;
    int                   rv;
    struct sockaddr_in    servaddr;
    socklen_t             addrlen = sizeof(servaddr);

    if((listenfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
    {
        perror("Socket failure");
        return -1;
    }

    bzero(&servaddr,addrlen);

    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(listen_port);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    if(bind(listenfd,(struct sockaddr *)&servaddr,addrlen) < 0)
    {
        perror("Bind on port failure");
        close(listenfd);
        return -1;
    }

    listen(listenfd,13);

    return listenfd;
}

代码已经描述的非常清楚了,如果想知道多个客户端具体的工作流程,可以先了解select模型服务器的工作原理,
参考:select服务器精讲;吃饭;

发布了18 篇原创文章 · 获赞 37 · 访问量 5002

猜你喜欢

转载自blog.csdn.net/weixin_45121946/article/details/104808874