epoll学习随笔

epoll学习

在学习linux网络编程中,epoll好像是一个不能过去的坎,但是他具体做了什么,总是让人觉得不太清晰。可以确定地是在使用过程中往往下面这样,往往epoll用的溜就能将代码功能更好的分块。

  while (true) {
    int num = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);//gdb调试时发现没有事件时一直在这
    if ((num < 0) && (errno != EINTR)) {
      printf("epoll failure\n");
      break;
    }
    //循环遍历事件数组
    for (int i = 0; i < num; i++) {
      int sockfd = events[i].data.fd;
      if (sockfd == listenfd) {
        //有客户端连接进来
        struct sockaddr_in client_address;
        socklen_t client_addrlen = sizeof(client_address);
        int connfd = accept(listenfd, (struct sockaddr *)&client_address,
                            &client_addrlen);
        if (http_conn::m_user_count >= MAX_FD) {
          //目前连接数满了
          //给客户端写一个信息,服务器内部正忙。
          close(connfd);
          continue;
        }
        // 将新的客户的数据初始化,放到数组中
        users[connfd].init(connfd,client_address);
      } else if (events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
        users[sockfd].close_conn();
      } else if (events[i].events & EPOLLIN) {
        if (users[sockfd].read()) {
          //一次性把所有数据都读完
          pool->append(users+sockfd);
        } else {
          users[sockfd].close_conn();
        }
      } else if (events[i].events & EPOLLOUT) {
        if (!users[sockfd].write()) //一次性写完所有数据
        {
          users[sockfd].close_conn();
        }
      }
    }
  }

epoll使用简介

从epoll定义下手,他被创造出来是为了实现IO的多路复用

文件描述符fd

fd可以标识一个文件(Linux好像有一个万物皆文件的说法),在这里是标识需要处理的事件。那么为了提高利用率能不能一个fd当多个fd来用?
通过fd文件描述符当作桥梁,通过1个fd控制多个fd,暂且称这1个fd为大fd,多个为小fd

使用方式

epoll可以分为两块,获取事件的位置和方式比较固定,添加事件可能需要从不同的地方进行操作

获取事件方式

通过epoll_wait函数来实时检测大fd,若是有情况则会将有情况的小fd们放在events[i]数组里。
但是这么多fd各有各的状态,因此在传递文件时,还需要携带一些状态消息,对上面这一串代码来说,
有的是根据小fd号来确定他要干啥
有的是根据events里面的枚举值来确定的

添加方式

添加更像是看他有没有这类事件产生,当没有事件产生时一般不会跑到epoll_wait里头作妖,当所标识的事件产生了那么就可以检测到了。比如设置为EPOLLIN时,一旦所指向的小fd的缓冲区有数据条件,wait会获取到。

//向epoll添加需要监听的文件描述符
void addfd(int epollfd, int fd, bool one_shot) {
  epoll_event event;
  event.data.fd = fd;
  event.events = EPOLLIN |EPOLLRDHUP;
  if (one_shot) {
    event.events|=EPOLLONESHOT;
  }
  epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
  //设置文件描述符非阻塞
  setnonblocking(fd);
}

而EPOLLOUT的触发条件就更加令人迷惑,
客户端连接场景(连接时可能需要写数据到缓冲区)
触发条件:客户端connect上服务端后,得到fd,这时候把fd添加到epoll 事件池里面后,因为连接可写,会触发EPOLLOUT事件
客户端发包场景(缓冲区已经写满了,可能还有数据没写完,当缓冲区不满时,尝试再次写数据调用)
触发条件:缓冲区从满到不满,会触发EPOLLOUT事件
重新注册EPOLLOUT事件(注册EPOLLOUT函数时,说明有数据要写)
触发条件:如果当连接可用后,且缓存区不满的情况下,调用epoll_ctl将fd重新注册到epoll事件池(使用EPOLL_CTL_MOD),这时也会触发EPOLLOUT

void http_conn::process() {
  //解析HTTP请求
  HTTP_CODE read_ret = process_read();
  if (read_ret == NO_REQUEST) {
    modfd(m_epollfd, m_sockfd, EPOLLIN);
    return;
  }
  printf("parse request,create response\n");
  //生成响应
  bool write_ret = process_write(read_ret);//此处将需要写的数据存放到了缓存区,随时可以写
  if (!write_ret) {
    close_conn();
  }
  modfd(m_epollfd,m_sockfd,EPOLLOUT);//第三种方式重新注册进行触发
}

可以借鉴下面这篇文章深入理解

https://blog.csdn.net/xxb249/article/details/105217956/

当EPOLLOUT和EPOLLIN都注册时
connect 调用仅EPOLLOUT信号
close 调用EPOLLOUT和EPOLLIN信号
重新注册EPOLLOUT时会有EPOLLOUT信号
当发送缓冲区满时,收到消息则只会有EPOLLIN信号,否则会收到EPOLLIN和EPOLLOUT信号

读写缓冲区

epoll总是提到缓冲区,但是缓冲区总是说的不清楚,读缓冲区尚可理解,当有消息传输到fd时存放的位置,这一部分一般由外部来完成,如:客户端发送消息到服务端。而需要写的时机是自己把握的,那所谓的缓冲区在哪里呢?
关于读写缓冲区 -------->在系统底层封装的一个区域,我们是不可见的。正是因为这样,缓冲区对于程序员来讲会带来一系列隐晦的问题。
作用: 写缓冲区 -------> 当前用户状态下的数据写到系统状态的数据缓冲区当中然后发送给底层(如果是socket的fd则将数据通过数据链路发出去网络)实际上时调用write函数后数据放置在的区,发送后清空
读缓冲区 -------> 当前系统状态当中接收到来自其他地方的数据,那么就缓存数据且fd可读,等待运行的进程(用户状态)来取走数据

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值