muduo笔记 网络库(一)概括
网络事件处理模式
服务器编程中,通常有两种高效的事件处理模式:reactor模式,proactor模式。
Reactor模式
要求主线程(I/O单元)只负责监听文件描述符上是否有事件发生,有的话就立即将该事件通知工作线程(逻辑单元)。除此之外,主线程不做其他任何实质性的工作。读写数据,接受新连接,以及处理客户请求均在工作线程中完成。
使用同步I/O模型(以epoll(7)为例)实现Reactor模式工作流程:
1)主线程往epoll内核事件表注册socket上的读就绪事件(epoll_ctl + EPOLL_CTL_ADD);
2)主线程调用epoll_wait等待socket上有数据可读;
3)当socket上有数可读时,epoll_wait通知主线程。主线程则将socket可读事件放入请求队列;
4)阻塞在请求队列上的某个工作线程被唤醒,它从socket读取数据,并处理客户请求,然后往epoll内核事件表中注册该socket上写就绪事件。
5)主线程调用epoll_wait等待socket可写;
6)当socket可写时,epoll_wait通知主线程。主线程将socket可写事件放入请求队列。
7)阻塞在请求队列上的某个工作线程被唤醒,它往socket上写入服务器处理客户请求的结果。
当然,epoll不止能监听socket上的事件,其他文件描述符都能监听。
下图是Reactor模式的工作流程示意:
Proactor模式
Proactor模式将所有I/O操作都交给主线程和内核来处理,工作线程仅负责业务逻辑。
异步I/O模型(以aio_read, aio_write为例)实现Proactor模式工作流程:
1)main线程调用aio_read向内核注册socket上的读完成事件,并告诉内核用户读缓冲区的位置,以及读操作完成时如何通知应用程序(这里以signal为例);
2)main线程继续处理其他逻辑;
3)当socket上的数据被读入用户缓冲区后,内核将向应用程序发送一个信号,以通知应用程序数据已经可用;
4)应用程序预先定义好的信号处理函数选择一个工作线程来处理客户请求。工作线程处理完客户请求后,调用aio_write向内核注册socket上的写完成事件,并告诉内核用户写缓冲区的位置,以及写完成时如何通知应用程序(仍以信号为例);
5)main线程继续处理其他逻辑;
6)当用户缓冲区的数据被写入socket之后,内核将向应用程序发送一个信号,以通知应用程序数据已经发送完毕;
7)应用程序预先定义好的信号处理函数,选择一个工作线程来做善后处理,如决定是否关闭socket;
下图是Proactor模式的工作流程示意:
muoduo中的Reactor模式
muduo网络库采用的是Reactor模式。服务器软件框架是one loop per thread,即一个线程一个事件循环。这个循环称为EventLoop,loop线程是指运行EventLoop::loop()的线程。这种以事件为驱动的编程模式称为事件驱动模式。
网络库是由Reactor + 线程池来完成的,线程池中每个线程都是一个Reactor模型。这种结构在处理大量并发I/O连接任务的服务器上,就很有优势。
下图是实现Reactor模式各关键类及类图关系:
特点:
- 每个muduo网络库有一个事件驱动循环线程池EventLoopThreadPool;
- 每个EventLoopThreadPool有多个事件驱动循环线程EventLoopThread;
- 每个线程运行一个事件驱动循环EventLoop;
- 每个EventLoop包含一个I/O复用的分发器Poller,一个计时器队列TimerQueue;
- 每个Poller监听多个Channel,TimerQueue也包含一个Channel;
- 每个Channel对应一个fd,可设置不同的事件(请求event、发生revent)及回调函数。fd就绪后,激活对应Channel,然后在Channel中根据不同事件类型调用回调函数;
- 每个回调函数都是在EventLoop所在线程执行;
- 所有激活的Channel处理完后,EventLoop中继续让Poller监听各Channel;
参考
- 游双. Linux高性能服务器编程[M]. 机械工业出版社, 2013.
- 深入理解Reactor 网络编程模型 | 知乎
muduo库其它部分解析参见:muduo库笔记汇总