彻底弄懂EPOLLOUT事件

EPOLLOUT事件触发的条件及典型用法

epoll是linux下实现IO multiplex的利器。一般编程实现方式如下:

  1. 使用epoll_create创建一个epoll fd
  2. 使用epoll_ctl往epoll fd里添加需要监听的fd,并注册需要监听的事件(由于EPOLLLET边缘触发方式更加高效,所以一般都使用边缘触发方式)
  3. 使用epoll_wait等待事件,然后依次处理各个事件,反复循环

其最常用的网络事件为EPOLLIN和EPOLLOUT,EPOLLIN对应为有socket缓冲区数据可读(当又收到了对端的一些数据,就会触发;或者作为服务端时有连接连过来),EPOLLOUT对应socket缓冲区可写。
这边EPOLLIN比较好理解,但是EPOLLOUT事件小白用户可能比较难理解了。比如经常看到小白用户的疑问:客户端需要发送数据给服务端,直接调用send(fd, ...)发送即可,看着和EPOLLOUT事件没有啥关系(说明:这种一般情况下可以,但是当缓存区满就GG了)。所以本文下面以边缘触发下的场景为例,重点说下什么时候会触发EPOLLOUT。
下面总结了三种场景下的EPOLLOUT并分别说明:

  • 客户端连接场景
    触发条件:客户端connect上服务端后,得到fd,这时候把fd添加到epoll 事件池里面后,因为连接可写,会触发EPOLLOUT事件
  • 客户端发包场景
    触发条件:缓冲区从满到不满,会触发EPOLLOUT事件
    典型应用场景(数据包发送问题):
    数据包发送逻辑:将数据包发完内核缓冲区–>进而由内核再将缓冲区的内容发送出去;这边send只是做了第一部分的工作,如果缓存区满的话send将会得到已发送的数据大小(成功放到缓冲区的),而不是整个数据包大小。
    这种情况我们可以借助EPOLLOUT事件加以解决:如果send部分成功,则表示缓存区满了,那么把剩下的部分交给epoll,当检测到EPOLLOUT事件后,再将剩余的包发送出去。
  • 重新注册EPOLLOUT事件
    触发条件:如果当连接可用后,且缓存区不满的情况下,调用epoll_ctl将fd重新注册到epoll事件池(使用EPOLL_CTL_MOD),这时也会触发EPOLLOUT时间。
    典型应用场景:
    send或write发包函数会涉及系统调用,存在一定开销,如果能将数据包聚合起来,然后调用writev将多个数据包一并发送,则可以减少系统调用次数,提高效率。这时EPOLLOUT事件就派上用场了:当发包时,可以将先数据包发到数据buffer(用户缓存区)中存放,然后通过重新注册EPOLLOUT事件,从而触发EPOLLOUT事件时,再将数据包一起通过writev发送出去。

Demo

贴下测试代码,有兴趣的同学可以基于此demo自己实践下,加深理解:

  • 客户端代码
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <sys/epoll.h> 
#include <stdlib.h>
#define PORT 2222  // Your server port
#define SERVER_IP "127.0.0.1"
int main()
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd == -1)
    {
        printf("create socket error\n");
        return -1;
    }

    struct sockaddr_in addr = {0};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    inet_aton(SERVER_IP, &addr.sin_addr);

    int ret = connect(fd, (struct sockaddr*)&addr, sizeof(struct sockaddr));
    if(ret)
    {
        printf("connect to server failed, error = %s\n", strerror(errno));
        close(fd);
        return -1;
    }
   
    int efd = epoll_create1(0);
    struct epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLOUT | EPOLLET;
    int s = epoll_ctl(efd, EPOLL_CTL_ADD, fd, &event);
    if (s == -1) {
        perror ("epoll_ctl error");
        close(fd);
        close(efd);
        abort();
    }

    #define MAXEVENTS 10
    struct epoll_event *events;
    events = (struct epoll_event *) calloc (MAXEVENTS, sizeof event);
    while (1) {
      int n = epoll_wait(efd, events, MAXEVENTS, 1000);
      usleep(100000);
      for (int i = 0; i < n; i++) {
        if (events[i].events & EPOLLOUT) {
          printf("received epollout event\n");

          printf("Start send hello\n");
          char input[] = "hello";
          send(fd, input, sizeof(input), 0);  // send packet

          // mod epollout event to trigger event
          struct epoll_event event;
          event.data.fd = fd;
          event.events = EPOLLOUT | EPOLLET;
          epoll_ctl(efd, EPOLL_CTL_MOD, fd, &event);
         }
      }
    } 

 
    close(fd);
    close(efd);
    return 0;
}
  • 17
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值