muduo简介
muduo是陈硕大神在Linux平台下基于C/C++开发的高性能网络库,在此基础上可以很方便的扩展,进行二次开发编写如http服务器。muduo网络库的核心框架,one thread per thread + Reactor模式。这也是大多数Linux端高性能网络编程框架应用的主要架构。
其本人写的《Linux多线程服务器端编程》对muduo库整个框架和设计细节进行了非常详尽的介绍和分析,非常推荐大家阅读,不仅仅对网络编程有帮助。
此文使用muduo编写了一个简单的echoserver服务器,从用户使用的角度来进行讲解muduo的核心框架、各个组件的交互、优秀的代码设计和实现细节。从源码角度辅助理解整个muduo的实现,希望对网络编程有更深的理解。
从muduo源码中提取出了其核心逻辑,提供一个包含详细注释的muduo库,可以配合源码阅读,删除了定时器事件、大量断言判错、和客户端只保留了muduo库服务端编程的核心框架,代码行数大概2000+,便于剖析其核心。
阻塞、非阻塞、同步、异步的概念
简单来说,阻塞、非阻塞都是对于操作I/O产生的行为,I/O 无非就是读(read)和写(write)。
以一个sockfd举例,无论是读还是写都有两个阶段,1 等待内核中的接收/发送缓冲区就绪(可读/可写),2 将内核中接收缓冲区数据拷贝至用户区 或 将用户区数据拷贝至发送缓冲区。
linux下默认对sockfd读/写操作,如果未就绪就默认会一直阻塞到就绪事件发生,程序才会继续往下执行,而如果调用了如 fcntl 系统调用对fd设置为了非阻塞,即使数据未就绪也直接返回。
同步与异步
陈硕大神的原话:“在处理 I/O 的时候,阻塞和非阻塞都是同步 I/O。只有使用了特殊的 API 才是异步I/O。”
我的理解是 无论是阻塞I/O、非阻塞I/O、I/O多路复用,都是同步I/O,它们都只是一种就绪事件通知方案,无论是sockfd可读还是可写了,返回的都是就绪事件,用户程序依旧要承担 从用户区将数据拷贝至内核发送缓冲区 或 从内核接收缓冲区将数据拷贝至用户区的时间,而异步I/O通知的是事件完成,用户程序不用承担数据拷贝所花费的时间,拷贝操作已经由内核帮你完成,一返回就可以进行下一步处理。
异步IO相对于同步IO性能更好,但是编程逻辑变得更加复杂,出现了问题也更难排查。
五种IO模型
阻塞IO blocking

非阻塞IO non-blocking

IO多路复用(IO multiplexing)

信号驱动IO(signal-driven)

异步IO(asynchronous)

muduo库的框架设计
现代CPU都有许多个核心,服务器这种程序也必须充分利用多核CPU的优势,如何设计多线程服务器?
引用 libevent 网络库作者的观点:one loop per thread is usually a good model
此时问题就转化为了如何设计一个高效的EventLoop,一个线程运行一个EventLoop。
EventLoop是muduo库的核心,muduo库使用的是 IO multiplexing + non-blocking IO + LT模式。
IO多路复用一般和非阻塞IO搭配使用,它们都无法单独使用,不会有人对一个非阻塞的fd一直轮询读取,浪费CPU资源。也不会有人在IO多路复用中使用阻塞IO,读取过程中可能会阻塞当前执行流,导致其余连接迟迟未响应。
muduo的LT模式
Epoll的ET模式通常是高效的代名词,使用ET模式事件就绪只会通知一次,即使一次未将数据读取完毕之后也不再通知,减少了Epoll底层回调机制的触发次数,提高了性能。
ET模式的高效是相对的,为什么muduo使用LT模式?我认为有以下好处:
- 低延迟:每次读取/写入只调用一次系统调用,兼顾每个连接,不会因为某个连接需要进行大量读写影响其余连接。
- 数据完整性:LT模式只要数据没读完就会一直上报。
Reactor 模型
Reactor模型封装了 对IO的读写、对应事件的回调 、事件操作。
The reactor design pattern is an event handling pattern for handling service requests
delivered concurrently to a service handler by one or more inputs. The service handler
then demultiplexes the incoming requests and dispatches them synchronously to the
associated request handlers.
单个Reactor

Reactor组件:Event事件、Reactor反应堆、Demultiplex事件分发器、EvantHandler事件处理器
muduo库的Multiple Reactors模型

图中的每一个Reactor对应一个EventLoop,一个线程运行一个EventLoop,每个Loop上的客户端连接都只能由其所在的Loop进行读写。
以图上一个MianReactor,2个SubReactor 为例:
muduo库中,MianReactor和SubReactor的交互并没有使用简单的同步队列(如生产者消费者模型),而是使用了高效的eventfd系统调用实现通知/唤醒机制,每个Loop都持有一个eventfd句柄,要通知某个Loop只要对其所拥有的eventfd写数据即可。
如果使用了同步队列,在瞬时高并发的场景,此同步队列很可能成为性能瓶颈,同步队列简化了逻辑,使代码更容易实现,但维持同步的性能损耗在高并发场景可能成为性能杀手,而使用eventfd 不涉及任何的 race condttion
性能杀手:锁竞争、上下文切换、数据拷贝、内存申请
muduo库核心组件
Cannel
将已连接的客户端连接 connfd,打包成了一个Cannel,其内部封装了connfd,loop_ (此Channel所在的EventLoop)、event (EPOLLIN/OUT),revent(响应的事件)和对应事件发生时的一系列回调。
muduo库中只有两种channel, 用来监听新连接的 listenfd-acceptorChannel 和客户端的 connChannel ,acceptorChannel 注册在MainLoop的Poller上。
Poller
Poller对应Reactor模型中的事件分发器,向EventLoop返回就绪事件集合,是具体某个IO多路复用的对象,muduo库中的Poller为一个抽象类,muduo提供两个具体的IO多路复用实现,EpollPoller 和 PollPoller 继承Poller类,重写其虚函数,实现其跨平台性,默认使用的是Epoll。
EventLoop
ChannelList activeChannels_;
std::unique_ptr poller_;
int wakeupFd; ->
976



被折叠的 条评论
为什么被折叠?



