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

Linux/C++ I/O多路复用——poll模型实现服务端Socket通信

程序员文章站 2022-06-06 09:19:50
...

目录

poll函数

参数说明

events相关

与select的不同

程序流程

程序实例


poll函数

     poll模型在实现服务端时思路是和select类似的,可以说poll是select的加强版,poll函数原型如下:

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

参数说明

fds:文件描述符结构体指针,实际传参时应传入该结构体数组。通过该结构体可以设置文件描述符及其对应的感兴趣事件以及最终返回的事件,该结构体定义如下:

struct pollfd {
               int   fd;         /* file descriptor */      
               short events;     /* requested events */
               short revents;    /* returned events */
           };

nfds: fds所指结构体数组的元素个数,即需要监听的描述符个数;

timeout:设置超时事件。

返回值:超时返回0;失败返回-1;成功返回大于0的整数,这个整数表示就绪描述符的数目。

events相关

POLLIN:有数据可读;

POLLPRI:有优先级数据可读;

POLLOUT:数据可写;

POLLERR:发生错误;

POLLHUP:发生挂起;

POLLNVAL:描述符未打开;

POLLRDNORM:同POLLIN

POLLRDBAND:优先级数据可读;

POLLWRNORM:同 POLLOUT;

 POLLWRBAND:优先级数据可写。

与select的不同

       poll与select的不同之处在于:

1.监听描述符的个数由结构体数组决定,因此没有select的限制;

2.对于select函数,需要监控的描述集合与成功监控的描述集合是同一个参数,而poll则将二者分离到了events与revents中;

3.select不同类型的描述符需要放到不同的集合中,而poll则直接以events和revents来分辩不同类型的文件描述符。

      poll依旧保留了select的一些缺点:大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。并且返回之后需要对fds数组的元素进行遍历然后再处理。

程序流程

1.绑定、监听.....

2.创建fds数组,数组每个元素都是一个结构体,结构体中存放描述符,感兴趣事件以及返回事件并将所有元素的fd成员初始化为-1,意为无效;

3.将fds数组第一个元素fds[0]结构体设置为监听描述符fds[0].fd = lfd,感兴趣事件为fds[0].events = POLLIN,使用变量fdcount记录当前需要监控的描述符数;

4.创建while循环,调用poll函数开始阻塞等待;

5.poll函数返回后,先判断监听描述符fds[0]的返回情况,查看是否发生了POLLIN事件,用if(fds[0].revents&POLLIN)判断;

6.如果判断为真,说明有新连接,则调用accept函数新连接的文件描述符并存在变量connfd中,然后再在fds数组中找到第一个“有效”的元素,将connfd作为该元素的fd,然后设置events为POLLIN,用来监听是否收到数据;

7.然后处理除监听描述符以外的描述符。遍历fds数组,判断数组中的元素是否发送了感兴趣事件,用if(fds[0].revents&POLLIN),如果发生了,就用read和write进行数据读取;

8.继续下一次循环....

程序实例

#include <iostream>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <poll.h>

using namespace std;

#define SERV_IP "127.1.2.3"
#define SERV_PORT 8888
#define MAX_CONN 1024

int main()
{
    sockaddr_in servaddr,clitaddr;
    sockaddr_in clit_info[MAX_CONN];  //存放成功连接的客户端地址信息

    char buf[1024];  //读写缓冲区
    int lfd;      //用于监听
    int connfd;   //连接描述符
    int readyfd;  //保存select返回值
    int fdcount = 0;  //fds数组中有效的文件描述符个数
    int maxi = 0;  //maxi反映了fds中最后一个成功连接的文件描述符的索引
    socklen_t addr_len = sizeof(clitaddr);

    struct pollfd fds[MAX_CONN];  //创建描述符结构体,其中events用于设置监控事件,revents用于返回发生的事件
    
    for(int i=0;i<MAX_CONN;i++)fds[i].fd = -1;  //将所有描述符都置为失效状态    


    if((lfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
    {
        cout<<"creat socket fault : "<<strerror(errno)<<endl;
        return 0;
    }

    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    servaddr.sin_addr.s_addr = inet_addr(SERV_IP);

    if(bind(lfd,(sockaddr *)&servaddr,sizeof(servaddr)) == -1)
    {
        cout<<"bind fault : "<<strerror(errno)<<endl;
        return 0;
    }

    if(listen(lfd,128) == -1)
    {
        cout<<"listen fault : "<<strerror(errno)<<endl;
        return 0;
    }

    fds[0].fd = lfd ; //添加监听连接描述符到数组中
    fds[0].events = POLLIN ;  //监控事件为数据可读

    fdcount++;  //因为添加了lfd,因此加1;

    cout<<"Init Success ! "<<endl;
    cout<<"host ip : "<<inet_ntoa(servaddr.sin_addr)<<"  port : "<<ntohs(servaddr.sin_port)<<endl;

    cout<<"Waiting for connections ... "<<endl;

    while(1)
    {
        readyfd = poll(fds,fdcount,-1); 
        //执行到这里,说明poll返回,返回值保存在readyfd中,表示有多少个文件描述符被监控成功
        if(readyfd == -1)
        {
            cout<<"poll fault : "<<strerror(errno)<<endl;
            return 0;
        }

        if(fds[0].revents&POLLIN)  //如果监听描述符上发生的事件是POLLIN,说明有连接请求
        {
            int i=1;
            connfd = accept(lfd,(sockaddr *)&clitaddr,&addr_len);
            if(connfd == -1)
            {
                cout<<"accept fault : "<<strerror(errno)<<endl;
                continue ;
            }
            cout<<inet_ntoa(clitaddr.sin_addr)<<":"<<ntohs(clitaddr.sin_port)<<" connected ...  "<<endl;
            //成功连接后,就将connfd加入监控描述符数组中

            for(;i<MAX_CONN;i++)
            {
                if(fds[i].fd == -1)
                {
                    fds[i].fd = connfd;
                    fds[i].events = POLLIN;   //继续设置为读事件监控
		    clit_info[i] = clitaddr;  //保存新连接的客户端地址信息
                    break;
                }
            }
		
            fdcount++;  //有效描述符计数加1
            if(i>maxi)maxi = i;  //更新maxi

            readyfd --;
            if(readyfd == 0)continue;  //如果只有lfd被监控成功,那么就重新poll
        }
        //处理lfd之外监控成功的文件描述符
        for(int i=1;i<=maxi;i++)
        {
            if(fds[i].fd == -1)continue; //等于-1说明这个描述符已经无效
            if(fds[i].revents&POLLIN)   //在fds数组中寻找是否有被监控成功的文件描述符
            {
                int readcount = read(fds[i].fd,buf,sizeof(buf));
                if(readcount == 0)  //对方客户端关闭
                {
                    close(fds[i].fd);
                    fds[i].fd = -1;
		    fdcount--;
                    cout<<inet_ntoa(clit_info[i].sin_addr)<<":"<<ntohs(clit_info[i].sin_port)<<" exit ... "<<endl;
                }
                else if(readcount == -1)
                {
                    cout<<"read fault : "<<strerror(errno)<<endl;
                    continue;
                }
                else
                {
                    cout<<"(From "<<inet_ntoa(clit_info[i].sin_addr)<<":"<<ntohs(clit_info[i].sin_port)<<")";
		    for(int j=0;j<readcount;j++)cout<<buf[j];
		    cout<<endl;
                    for(int j=0;j<readcount;j++)buf[j] = toupper(buf[j]);
                    write(fds[i].fd,buf,readcount);
                }
                readyfd--;
                if(readyfd == 0)break;
            }
        }
    }
    close(lfd);
    return 0;
}