LinuxI/O多路复用转接服务器——poll模型实现

LinuxI/O多路复用转接服务器——poll模型实现

  • poll函数
    • 函数原型
    • 参数和返回值
  • poll实现实现I/O多路复用服务器
    • 实现流程
    • 程序实现
      • 服务端程序
      • 客户端程序
      • 运行结果
  • poll优缺点
  • select、poll、epoll对比分析

poll函数

poll 系统调用和select类似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。

函数原型

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

参数和返回值

参数:
  fds:是一个pollfd结构类型的数组,它指定所有我们感兴趣的文件描述符上发生的可读、可写和异常等事件。

struct pollfd {
      int fd;         /* file descriptor  待监听的文件描述符 */
      short events;     /* requested events   待监听文件描述符对应事件*/
      short revents;    /* returned events   文件描述符实际发生事件*/
           };

  nfds:实际有效监听个数(被监听事件集合fds大小)

​  timeout:指定poll超时值(单位毫秒)

​     -1:永久阻塞,#define INFTIM -1

​     0:立即返回,不阻塞进程

​     >0:等待指定毫秒数,如当前系统时间精度不够毫秒,向上取值

返回值:

  返回满足对应监听事件的文件描述符总个数

poll实现实现I/O多路复用服务器

实现流程

  poll实现流程与select相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多,管理多个描述符也是进行轮询。

程序实现

服务端程序

#include
#include
#include
#include
#include
#include
#include 
#include 
#include 
#include 
#include 
#include 
#include "wrap.h"
using namespace std;
//定义服务端端口号
#define SERVER_PORT  9527

#define OPEN_MAX 1024

int main (int argc ,char*argv[])
{
    int i,n;
    //int client[FD_SETSIZE];//自定义数组,大小为1024
    int nready=0;//保存poll函数返回值,记录满足监听事件的fd个数
    int maxfd=0;//记录最大的文件描述符
    int lfd=0;//用于监听的套接字
    int cfd=0;//用于通信的套接字
    int sockfd=0;
    int maxi;//用于检索客户端文件描述符的下标

    char buf[BUFSIZ],str[INET_ADDRSTRLEN];
    //创建pollfd结构体  fd:记录文件描述符  events:设置监听事件  revents:实际发生的事件
    struct pollfd client[OPEN_MAX];
    //创建地址结构
    struct sockaddr_in server_addr,client_addr;
    socklen_t client_addr_len;
    //创建套接字
    lfd=Socket(AF_INET,SOCK_STREAM,0);
    //设置端口复用
    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    //初始化
    //memset(&server_addr,0,sizeof(server_addr));//将地址结构清零
    bzero(&server_addr,sizeof(server_addr));

    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(SERVER_PORT);
    server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    //绑定地址结构
    Bind(lfd,(struct sockaddr*)&server_addr,sizeof(server_addr));
    //设置监听
    Listen(lfd,128);
   
    //将需要监听的第一个文件描述符存入到client数组中
    client[0].fd=lfd;  //存文件描述符
    client[0].events=POLLIN;//events用于设置监听事件
    //初始化结构体文件描述符
    for(i=0;i<OPEN_MAX;i++)
    {
        client[i].fd=-1;//将文件描述符初始化为-1,意味着为无效状态
    }
    maxi=0;//client数组有效元素最大下标值

    //需要循环设置监听
    while(1)
    {
        //调用poll函数阻塞监听是否有客户端连接请求
        nready=poll(client,maxi+1,-1);
        //检查是否成功返回
        if(nready<0)
        {
            sys_err("poll error");
        }
        //判断监听描述符上发生的事件是否为POLLIN
        //如返回为真则说明有新的客户端进行连接请求
        if(client[0].revents&POLLIN)
        {
            client_addr_len=sizeof(client_addr);
            //调用accept函数接收客户端请求
            cfd=Accept(lfd,(struct sockaddr*)&client_addr,&client_addr_len);
            cout<<"received from "<<inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str))
                <<"at PORT"<<ntohs(client_addr.sin_port)<<endl;
            
            //将返回的文件描述符存入client数组
            for(i=1;i<OPEN_MAX;i++)
            {
                if(client[i].fd<0)
                {
                    client[i].fd=cfd;//将返回的文件描述符存入到client数组(设置监听文件描述符)
                    break;
                }
            }
            //达到监听上限报错  1024
            if(i==OPEN_MAX)
            {
                cout<<"too many clients"<<endl;
            }
            client[i].events=POLLIN;//设置监听文件描述符对应的事件

            //保证maxi存的为数组的最后一个元素下标
            if(i>maxi)
            {
                maxi=i;
            }
            //判断是否只有lfd事件,若为真则只有lfd事件,后续代码不需要执行
            if(0==--nready)
                continue;
        }
        //循环检测哪个客户端数据就绪
        for(i=1;i<maxi;i++)
        {
            if((sockfd=client[i].fd)<0)//将文件描述符存到sockfd临时变量中
            {
                continue;
            }
            //判断事件是否满足为实际发生的事件(在这里为读事件)
            if(client[i].revents&POLLIN)
            {
                //调用read函数读取数据
                n =Read(sockfd,buf,sizeof(buf));
                if(n<0)
                {
                    if(errno==ECONNRESET)//收到RST标志
                    {
                        Close(sockfd);//关闭文件描述符
                        client[i].fd=-1;//将文件描述符置-1,意为无效
                    }
                    else
                        sys_err("read error");
                }
                //判断是否读到结尾,若是就将该文件描述符关闭
                else if(n==0)
                {
                    Close(sockfd);
                    client[i].fd=-1;
                }
                //没有读到结尾,则对读取到的数据完成大小写转换
                else if(n>0)
                {
                    for(int j=0;j<n;j++)
                    {
                        //大小写转换
                        buf[j]=toupper(buf[j]);
                    }
                //写回buf
                Write(sockfd,buf,n);
                //写到屏幕输出
                Write(STDOUT_FILENO, buf, n); 
                }
            }
        }
    }
    Close(lfd);
    return 0;
}

客户端程序

同多进程客户端程序

运行结果

poll优缺点

优点:
  1.没有最大文件描述符数量的限制

  2.传入,传出事件分离

缺点:
  1.大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。

  2.poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

select、poll、epoll对比分析

select、poll、epoll对比分析

你可能感兴趣的:(网络编程,Linux系统,linux)