【计算机网络】poll | epoll

文章目录

  • 1. poll
    • poll函数参数解析
    • 代码解析
      • PollServer代码
    • poll 特点
  • 2. epoll
    • 认识接口
      • epoll_create
      • epoll_ctl
      • epoll_wait
    • 基本原理
      • 红黑树
      • 就绪队列

1. poll

poll函数参数解析

输入 man poll

【计算机网络】poll | epoll_第1张图片

poll的第一个参数是文件描述符
poll的第二个参数为 等待的多个文件描述符(fd)数字层面 最大的+1

poll函数中的最后一个参数 timeout 是一个 纯输入型参数,单位是毫秒
若 timeout 为-1,则表示永久阻塞,直到文件描述符就绪
若 timeout为0,则表示 非阻塞
若timeout 大于0,则表示 在timeout事件以内 以阻塞等待,超时则进行非阻塞等待


poll的返回值的含义与select 相同
第一种 大于0表示有几个文件描述符 是就绪的
第二种 等于0进入timeout状态 ,即 5s以内没有任何一个文件描述符 就绪
第三种 小于0等待失败 返回-1如:想要等待下标为1 和2的文件描述符,但是下标为2的文件描述符根本不存在,就会等待失败


【计算机网络】poll | epoll_第2张图片

在pollfd 结构体 中
fd 表示 文件描述符
events: 用户告诉内核,需要关心那些文件描述符上的那些事件
revents :内核会告诉用户,关心的那些文件描述符上的那些事件已经就绪

poll将 输入参数 和输出参数进行分离


poll就有对应的事件
常用的有
POLLIN 表示 有数据可以读
POLLOUT 表示 当前写的时候不会被阻塞


【计算机网络】poll | epoll_第3张图片

POLLIN 表示第一个比特位为1
POLLOUT 表示 第三个比特位为1

代码解析

主要将第一个初始版本的select代码进行修改

由于poll 自带结构体,内部包含
fd (文件描述符)
events (用户告诉操作系统 那些文件描述符上的事件需要关心)
revents (操作系统告诉用户 关心的那些文件描述符上的事件已经就绪)


【计算机网络】poll | epoll_第4张图片

此时的fdaaray作为结构体指针,可以通过该指针 去指向 pollfd结构体成员


【计算机网络】poll | epoll_第5张图片

当想要使用 数组当前元素表示对应的文件描述符时,需指向对应的fd成员

想要表示 (用户告诉操作系统 那些文件描述符上的事件需要关心)
需要通过指针去指向对应的成员 events
想要表示 (操作系统告诉用户 关心的那些文件描述符上的事件已经就绪)
需要通过指针去指向对应的成员 revents

PollServer代码

#include
#include
#include
#include
#include"Sock.hpp"
#include"Log.hpp"
#include"Err.hpp"
using namespace std;


  const static int gport=8888;    

  const static int N=4096;

  const static short defaultevent=0;

typedef pollfd type_t;

class PollServer
{
 public:
      PollServer(uint16_t port=gport)
      :port_(port),fdarray_(nullptr)
      {}

      void InitServer()//初始化
      {
           listensock_.Socket();//创建套接字
           listensock_.Bind(port_);//绑定
           listensock_.Listen();//设置监听状态
           
           fdarray_=new type_t[N];
           //对fdarray数组进行初始化
           for(int i=0;i<N;i++)
           {
             fdarray_[i].fd= defaultfd;
             fdarray_[i].events= defaultevent;
             fdarray_[i].revents=defaultevent;
           }
      }


     void Accepter()//获取新连接的动作
     {
             //这里再使用accept 就不会阻塞了
             //listen套接字底层一定有就绪的事件 即连接已经到来了
             string clientip;
             uint16_t  clientport;
            int sock=listensock_.Accept(&clientip,&clientport);//获取客户端IP和端口号
            if(sock<0)
            {
              return;
            }
            
            //当得到对应新连接的sock套接字,是不能进行read/recv
            //并不知道sock上的数据是否就绪的
            //所以需要将sock交给select,由select进行管理
            logMessage(Debug,"[%s:%d],sock:%d",clientip.c_str(),clientport,sock );
             //只需把新获取的sock 添加到 数组中
             int pos=1;
             for(;pos<N;pos++)
             {
                if(fdarray_[pos].fd==defaultfd)//说明没有被占用
                {
                   break;
                }
             }
             if(pos>=N)//整个数组中的位置全被占用了
             {
              //由于fdarray_是动态开辟空间的,所以可以动态扩容

              //若扩容失败,则close
                close(sock);
                logMessage(Warning,"sockfd[] array full");
             }
             else //找到了对应的位置
             {
                fdarray_[pos].fd=sock;
                fdarray_[pos].events=POLLIN;
                fdarray_[pos].revents=defaultevent;
             }
     }
     
      void  HandlerEvent()//处理就绪事件
      { 
             for(int i=0;i<N;i++)
             {
              int fd=fdarray_[i].fd;
              int revent= fdarray_[i].revents;

               if( (fd==defaultfd)&&(revent &POLLIN))//读事件就绪
               { 
                  continue;
               }
               //合法fd

               //若套接字为listensock套接字,并且读事件就绪
               if(fd==listensock_.Fd() &&(revent &POLLIN))
               {
                  Accepter();
               }
               //若套接字不是listensock套接字,并且读事件就绪 即普通的读取数据就绪
                else if ((fd != listensock_.Fd()) && (revent &POLLIN)) 
               {
                   char buffer[1024];
                   ssize_t s=recv(fd,buffer,sizeof(buffer)-1,0);
                   //读取不会被阻塞
                   if(s>0)//读取成功
                   {
                     buffer[s-1]=0;
                     cout<<"client# "<<buffer<<endl;

                     //发送回去 也要被select管理
                      string echo=buffer ;
                      echo+= "[select server echo ]";
                      send(fd,echo.c_str(),echo.size(),0);//发送消息 将echo内的数据 交给fd
                   }
                   else 
                   {
                     if(s==0)//读到文件结尾
                     {
                       logMessage(Info,"client quit...,fdarray_[i] -> defaultfd:%d->%d",fd,defaultfd);
                     }
                     else //读取失败 
                     {
                       logMessage(Warning,"recv error,client quit...,fdarray_[i] -> defaultfd:%d->%d",fd,defaultfd);
                     }  
                      close(fd);
                      fdarray_[i].fd=defaultfd;
                      fdarray_[i].events=defaultevent;
                      fdarray_[i].revents=defaultevent;
                   }   
                } 
             } 
      }
      

        void DebugPrint()
       {
         cout<<"fdarray_[]:"<<endl;
         for(int i=0;i<N;i++)
         {
          if(fdarray_[i].fd==defaultfd)
          {
            continue;
          }
          cout<<fdarray_[i].fd<<" ";
         }
         cout<<"\n";
       }

      void Start() //启动
      {
        //在网络中,新连接到来被当作 读事件就绪
        //对应不同的事件就绪,做出不同的动作
        
        fdarray_[0].fd=listensock_.Fd();
        fdarray_[0].events=POLLIN;//数据可读
        while(true) 
        {
          int timeout= -1;//永久阻塞  
            int n= poll(fdarray_,N,timeout);
            //timeout 设为nullptr后,全部为阻塞等待

            switch(n)
            {
              case 0:   //表示没有任何一个文件描述符就绪 
               logMessage(Debug,"timeout,%d: %s",errno,strerror(errno));
               break;

             case -1:  //等待失败 返回-1
              logMessage(Warning,"%d: %s",errno,strerror(errno));
              break;

             default:  //大于0 ,则表示成功 返回有多少文件描述符就绪
                logMessage(Debug,"有一个就绪事件发生了:%d",n);
                 HandlerEvent();//处理就绪事件
                 DebugPrint();//打印数组内容
               break;
            }
        }
      }
       
      ~PollServer()
       {
        listensock_.Close();
        if(fdarray_)
        {
          delete[]fdarray_;
        }
       }

 private:
     uint16_t port_;//端口号
      Sock listensock_;//创建Sock对象
      type_t* fdarray_;//自己定义一个数组,与位图大小相同,来进行已经获得的sock进行管理
};

poll 特点

poll 就相当于在 select 的基础上进行优化
poll自带结构体,只需将读写 异常 放入 events 事件即可

poll 跟 select 一样 也是以数组的形式 传递多个文件描述符,传进去后,需要操作系统继续遍历

  • 每次调用poll,都需要把fd集合从用户态拷贝到内核态,在fd很多时开销会很大
    (每次都需要用户需要告诉内核,那些文件描述符的那些事件需要关心)
  • 每次调用poll,都需要在内核遍历传递过来的所有fd,在fd很多时 开销会很大
  • (每次都需要内核需要告诉用户,关心的文件文件描述符上的那些事件就绪)

poll 解决了文件描述符 有上限的问题

【计算机网络】poll | epoll_第6张图片

(定义的数组是在堆上开辟的,若空间满了,还可以动态扩容)
select由于定义的是一个固定长度的数组大小,当到达整个数组长度时,就只能打印信息

2. epoll

epoll 是为处理大批句柄而作改进的poll

认识接口

epoll_create

输入 man epoll_create

【计算机网络】poll | epoll_第7张图片

参数size可以被忽略,但是必须大于0

返回值 :
若返回epoll文件描述符,则表示返回成功
若返回-1,则表示返回失败

epoll_create 作用:创建出epoll模型


epoll_ctl

输入 man epoll_ctl

第一个参数 epfd 为 epoll_create 的返回值
第二个参数 op 表示你想作什么样的操作
一般常见设置为三个值

【计算机网络】poll | epoll_第8张图片

EPOLL_CTL_ADD 添加
EPOLL_CTL_MOD 修改
EPOLL_CTL_DEL 删除


第三个参数 fd 表示 哪一个文件描述符

【计算机网络】poll | epoll_第9张图片

最后一个参数 event 表示关心什么事件
events 表示 输入
fd表示 输入时 表示那些文件描述符上的什么样事件要关心
epoll_ctl 作用: 用户告诉内核,帮我关心 增加/修改/删除那个文件描述符上的那一个事件

epoll_wait

输入 man epoll_wait

【计算机网络】poll | epoll_第10张图片

返回值含义 与select和poll相同
第一种 大于0表示有几个文件描述符 是就绪的
第二种 等于0进入timeout状态 ,即 5s以内没有任何一个文件描述符 就绪
第三种 小于0等待失败 返回-1如:想要等待下标为1 和2的文件描述符,但是下标为2的文件描述符根本不存在,就会等待失败


第一个参数 epfd v 为 epoll_create的返回值
最后一个参数 timeout 与poll中含义相同

第二个参数 events 为 返回的就绪事件
第三个参数 maxevents为 epoll模型的最大个数

epoll_wait作用:内核告诉 用户 那些文件描述符上的那些事件就绪


【计算机网络】poll | epoll_第11张图片

与poll的宏基本一致
主要使用 EPOLLIN 和 EPOLLOUT
EPOLLIN 表示 有数据可以读
EPOLLOUT 表示 当前写的时候不会被阻塞


基本原理

红黑树

创建epoll时,在底层就会创建一颗红黑树
使用红黑树 使用户告诉操作系统 来关心 增加/修改/删除那个文件描述符上的那一个事件

【计算机网络】poll | epoll_第12张图片

点击查看:红黑树概念


【计算机网络】poll | epoll_第13张图片

红黑树的节点假设为 sruct rb_node
内部包含 文件描述符fd 和 对应事件 event


【计算机网络】poll | epoll_第14张图片

eopll_ctl 本质 为 通过epoll模型来对红黑树操作
向红黑树中新增 删除 修改 某一个节点
而每一个节点 都对应的是文件描述符和对应的事件
epoll_ctl 用来对红黑树 进行增删改 操作


【计算机网络】poll | epoll_第15张图片

在内核中,一个结构体对象,既可以属于结构A,又可以属于结构B
所以struct rb_node 既可以属于红黑树,又可以属于其他结构


就绪队列

创建epoll时,同时也会创建一个就绪队列

【计算机网络】poll | epoll_第16张图片

当特定的文件描述符上有对应的事件发生了,就可以将对应已经发生事件的节点 链入就绪队列中
(所以struct rb_node 既可以属于红黑树,又可以属于就绪队列)

就绪队列中只保存已经准备好的文件描述符上的对应事件


【计算机网络】poll | epoll_第17张图片

作为就绪队列的节点,需要包含文件描述符fd 以及 revent (操作系统告诉用户 关心的文件描述符的那些事件就绪)


epoll_wait 以事件复杂度为O(1)的方式,检测有没有事件就绪 即检测就绪队列是否为空

【计算机网络】poll | epoll_第18张图片
【计算机网络】poll | epoll_第19张图片

数据就绪 形成节点放入就绪队列中 ,将红黑树中节点关系 也添加到就绪队列中
这样一个结构体对象就可以既属于红黑树 ,又属于就绪队列了


【计算机网络】poll | epoll_第20张图片

整体称为 epoll
当调用 epoll_create 时,就是创建epoll模型


epoll避免使用 遍历,而是通过回调函数的方式,将就绪的文件描述符加入 就绪队列中
epoll_wait 返回直接访问 就绪队列 就知道那些文件描述符就绪

你可能感兴趣的:(计算机网络,计算机网络,数据库)