前面我们分析了Acceptor处理连接请求,之后创建TcpConnection对象分发给合适的EventLoop,而TcpConnection里面是包含Channel,EventLoop以及相应的socket fd的,我们就可以假想成TcpConnection是针对一个连接的所有东西(socket,回调函数等)。但是对于socket的事件监听最后是需要装载到Pool里面的,而这里就是通过Channel和EventLoop将它们连接起来的,这篇文章我们就分析Channel类。
我们再次说明Channel类是对于某个socket以及对应的读写事件的封装,并且他也有包含了处理该TcpConnection的线程的EventLoop对象指针,通过Channel可以将监听的事件装载到它的EventLoop里面,因为poll是封装在EventLoop里面,因此最后事件监听还是由系统poll执行(这里poller是一次基类,muduo实现了poll和epoll,分别对应于PollPoller和EPollPoller)。
在Acceptor的章节有分析过Channel的四个接口函数分别提供打开关闭读写事件的功能,我们这边主要分析下它的实现
53 void Channel::update()
54 {
55 /* enableReading can call here
56 * pass channel object point to EventLoop
57 */
58 addedToLoop_ = true;
59 loop_->updateChannel(this);
60 }
可以看到这里调用了channel里面的EvetLoop成员的 loop_->updateChannel(),通过这个函数把相应的监听事件装载到EventLoop里面(其实最后是到poller的,不过EventLoop对poller进行了封装,目前我们统一看成是装在到EventLoop里面),我们来看下它的源码:
251 void EventLoop::updateChannel(Channel* channel)
252 {
253 assert(channel->ownerLoop() == this);
254 assertInLoopThread();
255 poller_->updateChannel(channel);
256 }
首先是再次判断下当前Channel和EventLoop是否属于同一个线程,之后调用了poller->updateChannel(),之前我们说过这里运用了C++的多态,poller实际上是个基类,子类有EPollPoller和PollPoller两种,我们讨论EPoll的,看如下源码:
108 void EPollPoller::updateChannel(Channel* channel)
109 {
110 Poller::assertInLoopThread();
111 const int index = channel->index();
112 LOG_TRACE << "fd = " << channel->fd()
113 << " events = " << channel->events() << " index = " << index;
114 if (index == kNew || index == kDeleted)
115 {
116 // a new one, add with EPOLL_CTL_ADD
117 int fd = channel->fd();
118 if (index == kNew)
119 {
120 assert(channels_.find(fd) == channels_.end());
121 channels_[fd] = channel;
122 }
123 else // index == kDeleted
124 {
125 assert(channels_.find(fd) != channels_.end());
126 assert(channels_[fd] == channel);
127 }
128
129 channel->set_index(kAdded);
130 update(EPOLL_CTL_ADD, channel);
131 }
132 else
133 {
134 // update existing one with EPOLL_CTL_MOD/DEL
135 int fd = channel->fd();
136 (void)fd;
137 assert(channels_.find(fd) != channels_.end());
138 assert(channels_[fd] == channel);
139 assert(index == kAdded);
140 if (channel->isNoneEvent())
141 {
142 update(EPOLL_CTL_DEL, channel);
143 channel->set_index(kDeleted);
144 }
145 else
146 {
147 update(EPOLL_CTL_MOD, channel);
148 }
149 }
150 }
从上面的代码可以看到这里用到channel的index成员,这个主要就是指明当前channel的一个监听状态,可以有如下三种类型:
33 namespace
34 {
35 const int kNew = -1;
36 const int kAdded = 1;
37 const int kDeleted = 2;
38 }
如果是kDeleted或者kNew的那么就需要更新channels_列表并且调用EPOLL_CTL_ADD,而kAdded的话那么则是调用EPOLL_CTL_MOD。channels_保存了该poller已有的所有的channel以及对应的状态:
173 void EPollPoller::update(int operation, Channel* channel)
174 {
175 struct epoll_event event;
176 bzero(&event, sizeof event);
177 event.events = channel->events();
178 event.data.ptr = channel;
179 int fd = channel->fd();
180 LOG_TRACE << "epoll_ctl op = " << operationToString(operation)
181 << " fd = " << fd << " event = { " << channel->eventsToString() << " }";
182 if (::epoll_ctl(epollfd_, operation, fd, &event) < 0)
183 {
184 if (operation == EPOLL_CTL_DEL)
185 {
186 LOG_SYSERR << "epoll_ctl op =" << operationToString(operation) << " fd =" << fd;
187 }
188 else
189 {
190 LOG_SYSFATAL << "epoll_ctl op =" << operationToString(operation) << " fd =" << fd;
191 }
192 }
193 }
这里最后还是调用到了系统调用的epoll_ctl(),第一个参数是epoll_create1()创建的epollfd,是该poller持有的,而第三个fd才是真正的该channel对应的fd呢。
到此我们就理清了当服务器接收到一个连接请求创建TcpConnection对象并分发给一个EventLoop,之后是如何针对该socket的监听事件装载到所属的EventLoop的poller里面的。注意,一个eventLoop拥有一个poller,对应一个channels列表以及一个epollfd,而它是所属于一个线程的,对于从Acceptor分配给该线程的所有TcpConnection都会加入到channelMap里面,键值就是fd。
现在监听事件已经装载完毕,那么就需要EventLoop跑起来(准确的说是poller跑起来),那么之后如果有事件发生又是如何处理的呢,下一节就分析EventLoop对象。