从零实现隧道穿透(三):epoll 用法

int epoll_create(int size);

  • 作用:创建一个 epoll 的句柄。
  • 参数:size 用来告诉内核监听的数目大小,内核会根据 size 的大小去申请对应的内存。
  • 返回值:成功则返回新的 epoll 对象的文件描述符,这个文件描述符用于后续的 epoll 操作。如果不需要使用这个描述符,使用 close 关闭。失败返回 -1 。

int epoll_create1(int flags);

  • 作用:创建一个 epoll 的句柄。
  • 参数:flags的值是 0,epoll_create1()等同于epoll_create()。但是不再根据 size 大小申请内存。而是动态申请内存。flasg 还可以使用其他值。
  • 返回值:成功返回文件描述符。失败返回 -1 。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

  • 作用:事件注册函数。我个人的理解就是将要监测的目标 fd 添加进 epfd 中。
  • 参数:epfd 是 epoll_create 的返回值。op:添加事件的类型选项。fd:目标文件描述符(需要监听的目标)。event 告诉内核需要监听什么事。
  • 返回值:成功返回 0 ,失败返回 -1 。

op 宏(添加事件的类型选项)有以下几种:

EPOLL_CTL_ADD:在 epfd 中注册新的 fd ,对这个 fd 要监听的是 event。
EPOLL_CTL_MOD:改变已注册到 epfd 的 fd 的监听事件。即改成新的 event。
EPOLL_CTL_DEL:从指定的 epfd 中 删除 fd 文件描述符。在这种模式中 event 是被忽略的,并且为可以等于 NULL 。

struct epoll_event(需要监听的事)结构如下:

struct epoll_event {
    uint32_t     events;      /* Epoll events */
    epoll_data_t data;        /* User data variable */
};

events 宏有以下几种,可以取他们的并:

EPOLLIN :表示对应的文件描述符执行读时;
EPOLLOUT:表示对应的文件描述符执行写时;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

epoll_data 的结构如下:

typedef union epoll_data {
    void        *ptr;
    int          fd;
    uint32_t     u32;
    uint64_t     u64;
} epoll_data_t;

一般用法是这样的:

struct epoll_event e;
e.data.fd = socket_fd;
e.events = EPOLLIN | EPOLLOUT;
epoll_ctl(events_fd, EPOLL_CTL_MOD, socket_fd, &e);

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

  • 作用:等待 epfd 中的事件。类似于select()调用。
  • 参数:events 用来从内核得到事件的集合。maxevents 告知内核有多少个 events,必须要大于0。timeout 是用来制定epoll_wait 会阻塞多少毫秒的。

关于 events 的用法一般是这样的:

struct epoll_event events[100000]; // 一个数组
// num 告知用户有多少个事件被监听到,并且这些事件放在 events 数组里了
int num = epoll_wait(epfd, events, 100000, 0); 

// 循环查看 num 个事件
for (int i = 0; i < num; ++i) {
    if (events[i].events & (EPOLLIN | EPOLLPRI)) {
        // 表示对应的文件描述符执行读;即,events[i].data.fd 执行读
        // ...
    } else if (events[i].events & EPOLLOUT) {
        // 表示对应的文件描述符执行写;即,events[i].data.fd 执行写
        // ...
    } else{
        // ...
    }
}

参考:
https://www.jianshu.com/p/ee381d365a29

哈哈哈哈哈哈