Channel 类 链接到标题
Channel
类相当于将 ep->addFd
这一步拆成了两步,第一步是 ch->enablereading
,它会调用 ep->UpdateChannel(this)
,this
就是调用 enablereading
的那个 ch
。
如何理解 Channel 类?可以认为每一个 ch 的实例,都对应着一个关注的文件描述符 fd
和一个要关注的事件类型 events
,当前其实只有两类 Channel,一个是对应的服务器的 fd
,另一类对应的是 accept
客户端的连接之后得到的 fd
,ch->events
是 fd
所在的 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
对应的文件描述符和发生的事件,并对其进行处理。