I/O多路复用机制(二)

    在实际的开发中,我们经常会遇到这样的场景,我们需要接受多个端口的数据、多个终端的数据抑或是多个文件描述符对应的数据。那么,遇到这样的问题,你在程序中该怎么做呢?通常的做法,在程序中对数据交互的描述符进行轮询。那么问题来了,轮询的时间设置为多少呢?设置的太短,可以保证处理性能和速度,但是CPU的使用率太高,一旦处理的描述符数量多了起来,CPU可能就扛不住了。设置的时间太长,描述符处理的时间片太短,处于空闲的时间较长,性能和速度达不到要求。如果是服务器的话,面对多个用户的连接,处理速度和CPU使用性能是必须考虑的,而且最好要兼顾。这里就需要使用到I/O多路复用机制,这就是博主即将要和小伙伴们探讨的内容。

poll简介

# include <poll.h>

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

fds:一个结构数组,struct pollfd结构如下:

  structpollfd{

  intfd;               //文件描述符

  shortevents;      //请求的事件(即监控事件)

  shortrevents;   //返回的事件(即已就绪事件)

  };

  events和revents是通过对代表各种事件的标志进行逻辑或运算构建而成的。events包括要监视的事件,poll用已经发生的事件填充revents。poll函数通过在revents中设置标志POLLHUP、POLLERR和POLLNVAL来反映相关条件的存在。不需要在events中对于这些标志符相关的比特位进行设置。如果fd小于0, 则events字段被忽略,而revents被置为0。标准中没有说明如何处理文件结束。文件结束可以通过revents的标识符POLLHUN或返回0字节的常规读操作来传达。即使POLLIN或POLLRDNORM指出还有数据要读,POLLHUP也可能会被设置。因此,应该在错误检验之前处理正常的读操作。

poll函数的事件标志符值

常量

说明

POLLIN

普通或优先级带数据可读

POLLRDNORM

普通数据可读

POLLRDBAND

优先级带数据可读

POLLPRI

高优先级数据可读

POLLOUT

普通数据可写

POLLWRNORM

普通数据可写

POLLWRBAND

优先级带数据可写

POLLERR

发生错误

POLLHUP

发生挂起

POLLNVAL

描述字不是一个打开的文件

 此外,revents域中还可能返回下列事件:

  POLLERR     指定的文件描述符发生错误。

  POLLHUP   指定的文件描述符挂起事件。

  POLLNVAL  指定的文件描述符非法。

这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回。

  注意:后三个只能作为描述字的返回结果存储在revents中,而不能作为测试条件用于events中。

nfds:要监视的描述符的数目。

timeout:超时时间,是一个用毫秒表示的时间,是指定poll在返回前没有接收事件时应该等待的时间。如果 它的值为-1,poll就永远都不会超时。如果整数值为32个比特,那么最大的超时周期大约是30分钟。

timeout值

说明

INFTIM(<0)

永远等待

0

立即返回,不阻塞进程

>0

等待指定数目的毫秒数

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

  EBADF         一个或多个结构体中指定的文件描述符无效。

  EFAULTfds   指针指向的地址超出进程的地址空间。

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

  EINVALnfds  参数超出PLIMIT_NOFILE值。

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

 

程序实例

服务端代码

#include <stdio.h>

#include <arpa/inet.h>

#include <poll.h>

#include <time.h>

#include <unistd.h>

#include <assert.h>

#include <sys/socket.h>

#include <error.h>

#include <sys/types.h>

#include <stdlib.h>

#include <string.h>

 

const int MAXFD = FD_SETSIZE;//FD_SETSIZE1024 可以监控的最大描述符数量

const char *SERVERIP ="127.0.0.1";//服务端IP

const unsigned short PORT = 6666;//服务端端口号

 

typedef struct server_st

{

   int                 cli_num;        //已连接客户端数量

   struct pollfd      cli_fd[MAXFD];  //存放已连接客户端描述符

   int                 index;          //保存最大的索引号(已连接客户端数据下标)

   int                 ready;          //已就绪描述符数量(poll返回值)

}server_st_t;

 

//打印出错信息并推出程序

#define handle_error(msg)\

   do {perror(msg); exit(EXIT_FAILURE);}while(0)

 

//定义类

class pollsocket

{

   public:

       //构造函数

       pollsocket(const char *server_ip = SERVERIP, unsigned short port =PORT);

       //析构函数

       virtual ~pollsocket();

 

       //客户处理函数(对外接口)

       int handle_cli_proc();

 

 

   private:

       int server_init();//初始化server_st_t结构体

       int server_uninit();//释放server_st_t结构体

       int handle_create_proc();//创建服务端监听套接字

       int handle_accept_proc();//处理客户端连接

       int handle_recv_proc();//处理客户端数据发送

 

       pollsocket(const pollsocket &ref);

       pollsocket& operator=(const pollsocket &ref);

   private:

       server_st_t     *m_server_st;

       char            m_server_IP[16];

       unsigned short  m_port;

       int             m_server_fd;

};

 

pollsocket::pollsocket(const char*server_ip /*= SERVERIP*/, unsigned short port /*= PORT*/)

{

   bzero(m_server_IP, sizeof(m_server_IP));//将类成员地址空间清零

   memcpy(m_server_IP, server_ip, strlen(server_ip));//给类成员负值

   m_port = port;

   m_server_st = NULL;

 

   server_init();//初始化

   handle_create_proc();//创建监听套接字

}

 

pollsocket::~pollsocket()

{

   //close(m_server_fd);

 

   //关闭所有的描述符(包括监听描述符)

   for (int i = 0; i < MAXFD; ++i)

    {

       int cli_fd = m_server_st->cli_fd[i].fd;

 

       if (-1 != cli_fd)

       {

           close(cli_fd);

       }

    }

 

   server_uninit();//释放结构体

}

 

int pollsocket::server_init()

{

   m_server_st = (server_st_t*)malloc(sizeof(server_st_t));

 

   if (NULL == m_server_st) handle_error("malloc");

 

   //将结构体空间清零

   bzero(m_server_st, sizeof(server_st_t));

   

   //初始化描述符数组

   for (int i=0; i<MAXFD; ++i)

    {

       m_server_st->cli_fd[i].fd = -1;

    }

 

   return 0;

}

 

int pollsocket::server_uninit()

{

   if (NULL != m_server_st)

    {

       free(m_server_st);

    }

 

   m_server_st = NULL;

}

 

int pollsocket::handle_create_proc()

{

   //创建TCP套接字

   //参数1:协议族

   //参数2:套接字类型

   //参数3:使用的协议(0:使用套接字类型对应的默认协议)

   if (-1 == (m_server_fd = socket(AF_INET, SOCK_STREAM, 0)))handle_error("socket");

 

   //地址结构体

   struct sockaddr_in serveraddr;

   //将结构体变量空间清零

   bzero(&serveraddr, sizeof(serveraddr));

   serveraddr.sin_family = AF_INET;//协议族

   //注意,设置端口和IP时,要将主机字节序转换为网络字节序

   serveraddr.sin_port = htons(m_port);

   //serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);

   inet_pton(AF_INET, m_server_IP, &serveraddr.sin_addr);

   

   int op = true;

   ///*一个端口释放后会等待两分钟之后才能再被使用(TIME_WAIT状态),SO_REUSEADDR是让端口释放后立即就可以被再次使用*/

   if (-1 == setsockopt(m_server_fd, SOL_SOCKET, SO_REUSEADDR, &op,sizeof(op))) handle_error("setsockopt");

 

   //命名套接字

   if (-1 == bind(m_server_fd, (struct sockaddr*)&serveraddr,sizeof(serveraddr))) handle_error("serveraddr");

 

   //将套接字由主动变为被动(接受客户端连接状态)

   if (-1 == listen(m_server_fd, SOMAXCONN))handle_error("listen");

 

   return 0;

}

 

int pollsocket::handle_accept_proc()

{

   //地址结构体

   struct sockaddr_in cli_addr;

   socklen_t len = (socklen_t)sizeof(cli_addr);

   //将结构体变量空间清零

   bzero(&cli_addr, sizeof(cli_addr));

 

   //接受客户端的连接请求

   int cli_fd = accept(m_server_fd, (struct sockaddr*)&cli_addr,&len);

   

   if (-1 == cli_fd) handle_error("accept");

 

   ++m_server_st->cli_num;//已连接客户数加1

   fprintf(stdout, "#%d  %s:%d  connected server!\n",m_server_st->cli_num, inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));

 

   int i = 0;

   //将新链接的客户存入客户端数组中(下标0被监听描述符占用)

   for (i = 1; i < MAXFD; ++i)

    {

       if (-1 == m_server_st->cli_fd[i].fd)

       {

           //将已连接套接字描述符存入数组

           m_server_st->cli_fd[i].fd = cli_fd;

           //m_server_st->index保存客户数组中已连接套接字描述符的最大索引

           m_server_st->index = m_server_st->index > i ?m_server_st->index : i;

           break;

       }

    }

 

   //如果已连接套接字描述符超过了FD_SETSIZE报错

    if (i == MAXFD) handle_error("too manyconnect");

 

   //设置新连接描述符监听事件为POLLIN

   m_server_st->cli_fd[i].events = POLLIN;

}

 

int pollsocket::handle_recv_proc()

{

   //如果数组中不至当前一个用户,遍历数组

   for (int i = 0; i <= m_server_st->index; ++i)

    {

       //如果为空位置,continue

       if (-1 == m_server_st->cli_fd[i].fd)

       {

           continue;

       }

 

       //如果非空位置,cli_fd保存当前描述符

       int cli_fd = m_server_st->cli_fd[i].fd;

 

       //如果当前描述符监听事件到来,执行以下代码

       if (m_server_st->cli_fd[i].revents & POLLIN)

       {

           char buf[256] = {0};

 

           //从网络中读取数据

           int r = read(cli_fd, buf, sizeof(buf));

 

           //如果读取出错

           if (r <= 0)

           {

                //在数组中将当前位置设为空位置

                m_server_st->cli_fd[i].fd =-1;

 

                //关闭当前描述符(客户端断开)

                close(cli_fd);

 

                //已连接客户数减1

                --m_server_st->cli_num;

           }

 

           //如果接受成功,将数据写回网络

           write(cli_fd, buf, sizeof(buf));

 

           //清空缓存区

           memset(buf, 0x00, sizeof(buf));

       }

 

       //每处理一个描述符,read1

       //如果read0(所有就绪描述符都处理完毕),就不用继续向后扫描

       //if (--m_server_st->ready <= 0) break;

    }

 

   return 0;

}

 

int pollsocket::handle_cli_proc()

{

   //将监听描述符加入监听数组中,并设置监听事件

   m_server_st->cli_fd[0].fd = m_server_fd;

   m_server_st->cli_fd[0].events = POLLIN;

 

   while (1)

    {

       //poll阻塞等待描述符集合中是否有就绪描述符

       //参数1:描述符集合地址

       //参数2:监控的描述符数量

        //参数3:最长等待时间(-1:无限等待,直到有描述符就绪;0:立即返回;等待指定时间,单位毫秒)

       //一旦有描述符就绪则返回,返回值为以就绪描述符的个数

       m_server_st->ready = poll(m_server_st->cli_fd,m_server_st->index+1, 5000);

 

       //poll函数返回异常

       if (-1 == m_server_st->ready) break;

 

       //poll函数等待超时

       if (0 == m_server_st->ready)

       {

           fprintf(stdout, "poll timeout\n");

           continue;

       }

 

       //如果有客户连接服务器,监听套接字就绪,执行以下代码

       if (m_server_st->cli_fd[0].revents & POLLIN)

       {

           //fprintf(stdout, "connect\n");

           //处理客户端的连接

           handle_accept_proc();

           //已经处理过来sfd描述符,read1

           //如果此时read<=0,表示所有就绪描述符已处理完毕,返回poll处继续等待就绪描述符

           

           //没有客户端发送数据,退出本次循环

           if (--m_server_st->ready <= 0)

           {

                continue;

           } 

       }

       else

       {

           //处理客户端发送过来的数据

           handle_recv_proc();

       }

 

    }

 

   return 0;

}

 

int main()

{

   pollsocket pollsock;

   pollsock.handle_cli_proc();

}

 

客户端程序

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <sys/socket.h>

#include <string.h>

 

int main()

{

    //创建IPV4 TCP套接字

    int sfd =socket(AF_INET, SOCK_STREAM, 0);

 

    if (-1 ==sfd) perror("socket"), exit(EXIT_FAILURE);

 

    //地址结构体

    structsockaddr_in addr;

   addr.sin_family = AF_INET;//协议族

   addr.sin_port = htons(6666);//端口

 

    //将地址串转换为网络字节序,存储到addr.sin_addr中

   inet_aton("127.0.0.1", &addr.sin_addr);//连接的服务端IP地址

 

    //连接服务器

    if (-1 ==connect(sfd, (struct sockaddr *)&addr, sizeof(addr)))

    {

       perror("connect");

       exit(EXIT_FAILURE);

    }

 

    charbuf[256] = {};

 

    //从标准输入读取数据

    while (NULL!= fgets(buf, sizeof(buf), stdin))

    {

        //将数据发送给服务器

       write(sfd, buf, strlen(buf));

       

        //清空缓存区

       memset(buf, 0x00, sizeof(buf));

 

        //读取服务器放送过来的数据

        int r =read(sfd, buf, sizeof(buf));

 

        //接受失败

        if (r<= 0)

        {

           break;

        }

 

        //输出

       fprintf(stdout, buf, r);

 

        //清空缓存区

       memset(buf, 0x00, sizeof(buf));

    }

 

    //关闭套接字描述符

    close(sfd);

 

    return 0;

}

 

程序运行结果

  

       poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是selectfd_set结构,其他的都差不多。关于selectpollepoll的对比,请观看博主的另外一篇博文poll epoll select,在那里博主对他们之间的区别、性能和消息传递方式进行了总结,希望能对你有一点帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值