Channel 类 链接到标题

Channel 类相当于将 ep->addFd 这一步拆成了两步,第一步是 ch->enablereading,它会调用 ep->UpdateChannel(this)this 就是调用 enablereading 的那个 ch

如何理解 Channel 类?可以认为每一个 ch 的实例,都对应着一个关注的文件描述符 fd 和一个要关注的事件类型 events,当前其实只有两类 Channel,一个是对应的服务器的 fd,另一类对应的是 accept 客户端的连接之后得到的 fdch->eventsfd 所在的 Channel 实例需要关注的事件类型。

active_events 表示该 Channel 当前发生的事件类型,在 ep->Poll() 中会被设置。

auto Epoll::Poll(int timeout) -> std::vector<Channel *> {
    std::vector<Channel *> active_channels;
    int nfds = epoll_wait(epfd, events, MAX_EVENTS, timeout);
    errif(nfds == -1, "epoll wait error\n");
    active_channels.reserve(nfds);
    for (int i = 0; i < nfds; ++i) {
        Channel *ch = (Channel *)events[i].data.ptr;
        ch->set_active_events(events[i].events);
        active_channels.push_back(ch);
    }
    return active_channels;
}

而在 ep->UpdateChannel(this) 这个过程中,会创建一个 struct epoll_event ev,表示 epoll 修改红黑树时,需要关注的事件。 ev 除了事件之外,还有一个可以由用户定义的 Union,它可以是文件描述符,也可以是一个指针,这里我们就让他指向 Channel 类的一个对象,Channel 类中本来就有文件描述了,所以 Union 解释为指针,功能明显更强大。

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

typedef union epoll_data {
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;
void Epoll::UpdateChannel(Channel *ch) {
    int fd = ch->getfd();
    struct epoll_event ev;
    memset(&ev, 0, sizeof(ev));
    ev.data.ptr = ch; // 将 ev.data 解释为指向 channel 的指针;
    ev.events = ch->get_events();
    if (!ch->get_in_epoll()) {
        // 添加到 epoll 中
        errif(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1, "epoll add error!\n");
        ch->set_in_epoll();
    } else {
        errif(epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev) == -1, "epoll mod error!\n");
    }
}

Epoll 的变化 链接到标题

其他变化就是,Epoll 类中的 Poll 函数变成返回 vector<Channel *>,而不是返回 epoll_event。同时会设置 ch->active_events,表示该 ch 正在发生的事件。

主循环中 链接到标题

由于 ep->Poll 返回的是 Channel * 的集合,我们可以拿到 ch 对应的文件描述符和发生的事件,并对其进行处理。