IO多路转接 —— 认识 epoll模型 相关接口

虽然poll 模型没有最大数量限制,但是监视的文件描述符数量过多会导致效率降低。因此 epoll 可以看作是为了处理大量句柄而做了改进的poll。与select、poll模型一样,epoll只负责等待;不一样的是,epoll模型的实现需要依赖三个接口函数。


目录

一、认识相关接口

1、epoll_create

2、epoll_ctl

(1) 第一个参数epfd

(2) 第二个参数op

(3) 第三个参数fd

(4) 第四个参数 event

(5) 返回值

3、epoll_wait

(1) 参数和返回值解析 

(2) 判断事件是否就绪

二、epoll模型实现


一、认识相关接口

1、epoll_create

epoll_create的作用是创建一个 epoll模型,这个模型在被创建的时候,内部会新增三个东西:一棵红黑树、回调策略、就绪队列。红黑树用于管理被监视的文件描述符及其对应事件;回调策略是事件就绪时的相关策略;就绪队列用于告知用户哪些文件描述符的哪些事件就绪。

相关内容可以参考:epoll模型的作用过程

epoll_create系统调用的声明如下:

epoll_create的参数size早在2.6.8版本以后,就被无视了,因此在使用的时候,可以填任意整型。

返回值:返回的是一个文件描述符 epfd

2、epoll_ctl

epoll_ctl 的作用是 新增 / 修改 / 移除 某个被关注的文件描述符。(只要调用一次,内核就永远记住了,下一次循环无需重新设置)

epoll_ctl 的函数声明如下:

(1) 第一个参数epfd

epoll模型可以有很多个,选择你要操作的epoll模型,即epoll_create的返回值

(2) 第二个参数op

代表你要对上述文件描述符作什么操作(新增/修改/移除)。可选值如下:

宏解析
EPOLL_CTL_ADD注册新的fd到epfd中
EPOLL_CTL_MOD修改某个fd的监听事件
EPOLL_CTL_DEL从epfd中删除一个fd(取消对某个fd的监视)

(3) 第三个参数fd

代表你要对哪个文件描述符进行 新增/修改/移除 监视的操作。

(4) 第四个参数 event

这是一个输入型参数,代表需要添加 / 修改的监听事件。下面我们需要认识两个内容,第一个是第四个参数的结构体类型,第二个是如何添加监听事件。

结构体类型

结构体类型如下:

首先是events成员,这个成员用于告诉我们有哪些事件就绪了,但是需要我们自己去判断,判断方法放在epoll_wait中介绍;

其次是data成员,现在我们知道有哪些事件就绪了,但是到底是哪个文件描述符的事件就绪了呢?因此我们可以通过data.fd来获取文件描述符。

添加监听事件

无论是新增还是修改监听事件,这些事件站在C语言的角度来看都是宏,可以通过按位与的方式来添加事件

描述
EPOLLIN数据(普通数据或者优先数据) 可读
EPOLLOUT数据(普通数据或者优先数据) 可写
EPOLLPRI文件描述符有紧急数据可读(有带外数据)
EPOLLERR文件描述符发生错误
EPOLLHUP文件描述符被挂断(对端关闭连接)
EPOLLET将EPOLL设为边缘触发(epoll默认是水平触发模式)
EPOLLONESHOT只监听一次事件,监听完这次以后,如果需要再次监听需要重新添加

比如我们要添加事件:

struct epoll_event epevent;
epevent.events |= EPOLLIN;        //添加监听读事件
//epevent.events = EPOLLIN | EPOLLOUT;    //既监听读事件又监听写事件

(5) 返回值

成功返回0,失败返回-1 。

3、epoll_wait

epoll_wait 的作用是告知你有文件已经就绪(返还就绪数组的首地址),但是不会具体告知你哪些事件就绪。函数声明如下:

(1) 参数和返回值解析 

第一个参数epfd:epoll模型的文件描述符,即epoll_create的返回值

第二个参数events:这是一个输出型参数,我们填入一个空数组的首地址,这个函数会将xx结构体拷贝到这个数组,该结构体包含文件描述符以及文件描述符上的就绪事件(结构体类型参考上面的epoll_ctl)

第三个参数maxevents:输入型参数,限制每次最多可以返回多少个结构体,即接收返回事件的数组的最大容量。如果有较多文件描述符事件就绪,超过了我们限制的数目,超出部分是否会被舍弃呢??答案是不会!拿完一次,如果没拿完,会通过循环再拿第二次。

第四个参数timeout:代表超时时间,单位是毫秒。

  • timeout = -1代表永久阻塞,即在有时间就绪之前epoll_wait函数就不返回;
  • timeout =0代表非阻塞等待;
  • timeout > 0 代表先阻塞等待timeout毫秒,然后变为非阻塞等待,此时epoll_wait会返回,并继续执行这之后的代码。

返回值:有三种情况

  • 返回值 < 0 ,代表epoll_wait调用出错;
  • 返回值 = 0,代表等待超时;
  • 返回值 > 0,代表有事件就绪的文件描述符数目;

(2) 判断事件是否就绪

我们可以通过 第二个参数events来获取哪个文件描述符上的哪些事件就绪。

#define NUM 64
struct epoll_event revents[NUM];    //就绪数组
int num = epoll_wait(epfd, revents, NUM, 1000);
if(num > 0){
    for (size_t i = 0; i < num; i++)    //以轮询的方式获取到就绪事件以及对应的文件描述符
    {    
        if(revents[i].events & EPOLLIN)    //检查读事件是否就绪
        {
            if(revents[i].data.fd == listen_fd)
            {
                //处理链接事件
            }               
            else
            {
                //处理正常读取事件
            } 
        }

        if(revents[i].events & EPOLLOUT)    //检查写事件是否就绪
        {
        
        }
    }
}

二、epoll模型实现

下面只给出了一个大致的框架,处理工作请自行填充

int main()
{
  //创建套接字
  int listen_fd = TcpSocket::CreateSocket();
  //绑定端口号
  TcpSocket::Bind(listen_fd, 8990);
  //设为监听套接字
  TcpSocket::Listen(listen_fd);

  // 1. 创建epoll模型
  int epfd = epoll_create(128);

  // 2. 添加listen_fd,并添加读事件监听
  struct epoll_event epev;
  epev.events |= EPOLLIN;
  epev.data.fd = listen_fd; //将读事件与 listen_fd绑定,用于后面的revents判断哪个文件描述符上有事件就绪
  epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &epev);

  // 3. 事件循环检测是否就绪
  volatile bool quit = false;
#define NUM 64
  struct epoll_event revents[NUM];
  while (!quit)
  {
    int timeout = 1000;
    // 4. 等待事件就绪
    int num = epoll_wait(epfd, revents, NUM, timeout);
    if (num < 0)
    {
      std::cerr << "等待出错..." << std::endl;
    }
    else if (num == 0)
    {
      std::cout << "等待时间超时..." << std::endl;
    }
    else
    {
      std::cout << "有事件就绪了" << std::endl;
      for (size_t i = 0; i < num; i++)
      {
        if (revents[i].events & EPOLLIN) //检查读事件是否就绪
        {
          if (revents[i].data.fd == listen_fd)
          {
            //处理链接事件
            int accept_fd = TcpSocket::Accept(listen_fd);
            if (accept_fd > 0)
            {
              //将新的文件描述符托管给epoll,此时需要调用epoll_ctl
              struct epoll_event ev;
              ev.events |= EPOLLIN;
              ev.data.fd = accept_fd;
              epoll_ctl(epfd, EPOLL_CTL_ADD, accept_fd, &ev);
            }
          }
          else
          {
            //处理正常读取事件
            char buffer[1024];
            int s = recv(revents[i].data.fd,buffer,sizeof(buffer)-1,0);
            if(s < 0)
            {
                std::cerr<<"读取出错"<<std::endl;
                //关闭文件描述符(套接字)
                close(revents[i].data.fd);
                //取消对该文件描述符的关注
                epoll(epfd,EPOLL_CTL_DEL,sock,nullptr);
            }
            else if(s == 0){
                std::cout<<"对端关闭连接"<<std::endl;
                //关闭文件描述符(套接字)
                close(revents[i].data.fd);
                //取消对该文件描述符的关注
                epoll(epfd,EPOLL_CTL_DEL,sock,nullptr);
            }
            else
            {
                //开始处理数据
            }
          }
        }

        if (revents[i].events & EPOLLOUT) //检查写事件是否就绪
        {
        }
      }
    }
  }

  return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值