Linux 多线程服务端编程读书笔记(八)
第八章 muduo 网络库设计与实现
这一章从0开始实现一个类似muduo的基于Reactor模式的C++网络库
1、 Evenloop类
- one loop per thread 顾名思义每个线程都只能有一个Evenloop对象。故构造之前要检查当前线程是否已经创建
- 其构造函数会记住本对象所属的线程,创建了Evenloop对象的线程时IO线程,主要功能是运行事件循环
- 除此之外,该类还拥有一个Poller类对象和一个ChannelList对象(用于保存Poller对象返回的所有的活跃fd)。
2、Channel class类
- 每一个Channel对象自始至终只属于一个EvenLoop,因此每个Channel对象都只属于某一个IO线程
- 每个Channel对象自始至终只负责一个文件描述符(fd)的IO事件分发,但不拥有这个fd,也不会再析构的时候关闭这个fd.
- Channel会把不同的IO事件分发为不同的回调,例如ReadCallback、WriteCallback等
- Channel的成员函数都只能在IO线程调用,因此更新数据都不需要加锁
3 、 Poller class
- Poller class 是IO多路复用的封装,同时支持poll(2)和epoll(4)两种多路复用机制
- Poller 是EvenLoop的间接成员,只供其owner EvenLoop在IO线程中调用,因此无需加锁。其生命期与EvenLoop相等。
- Poller并不拥有Channel,Channel在析构之前必须自己unregister(EventLoop::removeChannel()),避免空悬指针
4、TimerQueue定时器
传统的Reactor通过控制select(2)和poll(2)的等待时间来实现定时,而现在在Linux中有了timerfd,我们可以处理IO事件相同的方式来处理定时,代码的一直性要求的更好
- muduo的定时器功能由三个class实现,TimeId、Timer、TimeQueue。用户只能看到第一个class,另外两个都是内部实现的细节
- 使用的数据结构是std::set
- TimerQueue使用了一个Channel来观察timerfd_上的readable事件
5、 EventLoop::runInLoop()函数
-
EvenLoop在它的IO线程内执行某个用户任务回调即runInLoop(),如果用户在当前IO线程调用这个函数,回调会同步进行;如果用户在其他线程调用该函数,回调函数cb会加入到队列,IO线程会被唤醒来调用这个Functor
void EventLoop::runInLoop(const Functor& cb) { if (isInLoopThread()) { cb(); } else { queueInLoop(cb); } }
有了这个功能,我们就能够轻易地在线程间调配任务,比方说吧TimerQueue的成员函数调用移动到IO线程,这样可以在不用锁的情况下保证线程安全性
-
IO线程平时阻塞到事件循环EvenLoop::loop()的poll(2)调用中,为了让IO线程立刻唤醒它,传统的方法是使用pipe(2),IO线程始终监视此管道的可读事件,需要唤醒的时候,其他线程网管道里写一个字节。** 现在的Linux有了eventfd(2),可以更加高效的唤醒,因为不必管理缓冲区
-
EventLoopThread class
-
一个线程可以有不止一个IO线程,可以按照优先级将不同的Socket分给不同的IO线程,避免优先级反转,为了方便将来使用故定义了此类
-
EventLoopThread 会启动自己的线程,并在其中运行EventLoop::loop();
ventLoop* EventLoopThread::startLoop() { assert(!thread_.started()); thread_.start(); { MutexLockGuard lock(mutex_); while (loop_ == NULL) { cond_.wait(); } } return loop_; } void EventLoopThread::threadFunc() { EventLoop loop; { MutexLockGuard lock(mutex_); loop_ = &loop; cond_.notify(); } loop.loop(); //assert(exiting_); }
-
6 、实现TCP网络库
本节开始,逐步实现一个非阻塞TCP网络库
-
Acceptor class
用于accept(2)新TCP连接,并通过回调通知使用者,是内部类,供TcpServer使用,声明周期由后者控制。
-
TcpServer class
- 它的功能是管理accept(2)获得的TcpConnection。Tcpserver是供用户直接使用的,生命周期由用户控制用户只需要设置好callback,调用start()即可
- TcpServer内部使用Acceptor来获得新连接的fd。它保存用户提供的Connection callback和MessageCallBack。
- 每个TcpConnection对象都有一个名字,这个名字是由其所属的TcpServer在创建TcpConnection对象时生成的,名字是ConnectionMap的key
-
TcpConnecttion class
该类时muduo里面最复杂,最核心的class
- 是muduo里面唯一默认使用shared_ptr来管理的class,也是唯一继承enable_shared_from_this的class
- muduo里面只有一种关闭连接的方式,被动关闭,即对方先关闭连接,本地read(2)返回0,触发关闭逻辑
7 、Epoll
- epoll(4)是Linux独有的高效的IO多路复用机制,与poll的不同之处在于poll每次返回整个文件的描述符数组,用户代码需要遍历数组以找到哪些文件描述符上面有IO事件,而epoll_wait返回的是活动的fd的列表,需要遍历的数组通常会小很多
- 在并发连接数大而活动连接比例不高时候,epoll(4)比poll(2)更加高效