0、参考文档及博客
重要的东西放前面:
文中代码段部分引自以下这篇博客:
https://blog.youkuaiyun.com/amoscykl/article/details/83552549
本文中只摘录了其中部分代码,因为想表达更多自己的理解而不是完全展现源码。对阅读源码有兴趣的读者可以再去阅读上面这篇博客。或者直接看陈硕大神的源码,这里附上muduo的github链接:
https://github.com/chenshuo/muduo
1、模块简介:
本人认为,就像启动一个项目要想清楚需求场景一样,写一个模块最好有一个运行demo一样,了解muduo库应该从场景入手。
首先简单介绍muduo库的几个核心模块:Acceptor、Connector、EventLoop、Poller、Channel、Timer。
Acceptor:(服务端)负责处理新的socket连接,封装了系统调用bind、listen、accept。
Connector:(客户端)负责发起新的socket连接。
EventLoop:存在于一个线程中,主题是loop函数循环执行poller,监听所负责的socket的事件,根据对应的时间将回调函数加入activeChannel中等待处理。
Poller:负责监听一组socket的时间,封装了系统调用poll。
Channel:事件分发器,每个channel对象会负责一个文件描述符(可能是socket也可能是timer)事件的分发,针对socket的不同时间,Channel会执行不同的回调函数。
2、场景:
服务端启动:Acceptor把bind、listen、accept等事情完成。主线程等待客户端连接。初始化10个线程的线程池,每个线程里都有一个EventLoop。
80个客户端发起了连接,Acceptor不断accept得到80个socket的文件描述符,以RR的形式分发给10个线程。每个线程获得了8个socket的文件描述符要负责。
为每个socket的事件注册回调函数,将8个socket放到poll中进行监视。同时可能每隔2秒要记录一下日志,每隔5秒要输出一下日志信息。则创建两个Timmer(内部是linux的timefd定时器,看后缀是fd就知道这也是个文件描述符,可以当成socket一样处理,也可以将它放到poll监听的文件描述符组中。当指定的时间到了,timefd会触发时间,poll会检测到,由事先创建好的timefd对应的channel来执行相应的回调函数,例如2秒的timefd会执行写日志操作,5秒的timefd会执行输出日志操作。
【至此,有11个线程,主线程负责accept新的连接,10个线程的EventLoop负责监听各自的文件描述符,这里是8个socket连接和2个timer定时器。】
当事件到来时,Poller遍历文件描述符组,找到发生事件的文件描述符是1号、3号、6号文件描述符,调用fillActiveChannels将负责这三个描述符的的Channel塞到activeChannels中【而不是找到1号就执行1号的回调函数handleEvent,因为handelEvent可能会添加或者删除Channel,例如监听到文件的结束事件时,删除对应的Channel,这就会导致文件描述符长度改变,所以要全部记录下来之后再处理】。
记录下这轮有三个事件以后,这个线程从acitveChannels中逐个执行对应的回调函数,执行完之后,开始新一轮的poll监听。
【思考:可不可以让这10个监听线程并不负责处理事情,而是将事情塞到activeChannels中,由别的线程来执行。因为处理事件的这十秒钟里其他文件描述符新来的事件都会被忽略掉。】
3、模块设计:
(1)EventLoop
EventLoop的特点是:
(1)每个线程只能有一个EventLoop对象,也只能被自己的线程调用。
(2)析构的时候要把该处理的对象处理好,要保证当前状态可以析构。
(3)内部有一个Poller对象来负责监听文件描述符,其工作是当poller检测到事件发生,并且把activeChannel填好以后,去执行channel里的回调函数。
根据特点我们可以分析其数据成员和成员函数的意义:
(1)每个线程只能有一个EventLoop对象,也只能被自己的线程调用。
用__thread限定符存储EventLoop对象,保证线程只有一个EventLoop对象。
初始化执行if (t_loopInThisThread)访问,判断:这个线程里是否已有EventLoop对象?如果已存在就报错,不存在就顺利初始化,用t_loopInThisThread记录下来,表示这个线程已经分配一个EventLoop对象了。
wakeupChannel_是给EventLoop也注册一个事件分发器,
class EventLoop : boost::noncopyable
{
public:
EventLoop();
~EventLoop(); // force out-line dtor, for scoped_ptr members.
}
__thread EventLoop* t_loopInThisThread = 0;
EventLoop::EventLoop() //初始化事件循环
: looping_(false),
quit_(false),
eventHandling_(false),
callingPendingFunctors_(false),
iteration_(0),
threadId_(CurrentThread::tid()),
poller_(Poller::newDefaultPoller(this)),
timerQueue_(new TimerQueue(this)),
wakeupFd_(createEventfd()),
wakeupChannel_(new Channel(this, wakeupFd_)),
currentActiveChannel_(NULL)
{
LOG_DEBUG << "EventLoop created " << this << " in thread " << threadId_;
if (t_loopInThisThread)
{
LOG_FATAL << "Another EventLoop " << t_loopInThisThread