Channel通道类,它是muduo库负责注册读写事件的类,并保存了fd读写事件发生时调用的回调函数,那么它到底是如何实现对应事件发生时系统执行指定的回调函数的呢?下面我们来研究一下:
Channel的数据成员:
我们看到Channel类的数据成员中有一个描述符成员fd_,每个描述符会与一个Channel绑定。同时我们看到其由4个回调函数指针成员。
事件关注设置
首先,要想执行回调函数,必然要注册回调函数,光注册回调函数还不行,我们首先得关注指定的事件才行啊,不然事件不会触发,怎么有机会执行回调函数呢?那么我们首先以关注读事件为例,来讲讲如何设置关注读事件。
我们应该调用Channel的成员函数enableReading,其内部有调用了update成员函数。
我们发现,update函数内部又调用了EventLoop的成员函数updateChannel,
EventLoop的成员函数updateChannel会依据其底层是PollPoller还是EPollPoller调用对应的updateChannel,我们以默认的EPollPoller为例,
EPollPoller在updateChannel内部又调用了自己的成员函数update,可以看到其调用了epoll_ctl正式地将要关注的事件挂到了红黑树上了,要关注的事件就是Channel的成员event_。务必注意这个地方event.data.ptr=channel,这个地方非常重要,因为当Channel上关注的事件发生时,epoll能够通过ptr指针把该事件对应的通道返回,我们就知道这是哪个通道发生的事件,从而就可以回调这个通道上注册的相应的回调函数。
好了,至此,Channel要关注的事件设置好了。下面我们看看当对应的事件发生时是如何回调的。
回调函数注册与执行
由于我们之前注册了可读事件,所以当可读事件发生时,epoll_wait会返回对应发生的事件,由于我们之前在epoll_ctl的时候执行了event.data.ptr=channel这句,所以epoll_wait返回的时候能够返回事件所对应的通道,我们看到,事件发生时,poll内部调用了fillActiveChannels函数,把发生事件的通道指针添加到存放通道指针的vector容器activeChannels里面。调用EpollPoller::poll函数的上层函数其实是EventLoop中的loop函数,正好EventLoop有数据成员activeChannels_,所以最终epoll_wait检测到的事件所对应的活动通道都会存放在EventLoop的数据成员activeChannels_中。
接下来,我们看到loop函数中遍历每一个通道,执行通道的handleEvent函数。
handleEvent函数又执行了handleEventWithGuard函数,在这里面执行了具体的回调函数,也就是依据具体发生的事件进行相应的处理的函数。我们知道,一个通道有四个回调函数指针成员,一开始Channel类的数据成员里就展示了。并且我们知道一个Channel与一个文件描述符关联,那么也就是说,一个文件描述符上的发生了事件,其对应的通道因为注册了有关这个文件描述符相关事件的回调函数,自然就可以执行了。
问题是到现在我们还没看到回调函数是在哪设置的?如下所示,Channel类的几个成员函数可以设置回调函数。
一般情况下,我们有一个文件描述符fd就会将其与一个Channel关联,在Channel的构造函数中利用该fd对Channel进行初始化,初始化结束之后,我们就可以设置关注的事件的回调函数(set***Callback系列函数)以及上面具体要关注的事件(enable***系列函数)。
以EventLoop的构造函数为例,我们看到wakeupFd_一创建出来就立刻调用Channel类的构造函数初始化wakeupChannel_,将wakeupFd_与wakeupChannel_关联,然后使用wakeupChannel_的setReadCallback()函数设置读回调函数并调用enableReading函数注册关注可读事件。