总纲
每个muduo网络库有一个事件驱动循环线程池EventLoopThreadPool
每个线程池中有多个事件驱动线程EventLoopThread
每个线程运行一个EventLoop事件循环
每个EventLoop事件循环包含一个io复用Poller,一个计时器队列TimerQueue
每个Poller监听多个Channel,TimerQueue其实也是一个Channel
每个Channel对应一个fd,在Channel被激活后调用回调函数
每个回调函数是在EventLoop所在线程执行
所有激活的Channel回调结束后EventLoop继续让Poller监听
所以调用回调函数的过程中是同步的,如果回调函数执行时间很长,那么这个EventLoop所在线程就会等待很久之后才会再次调用poll。
整个muduo网络库实际上是由Reactor + 线程池实现的,线程池中每一个线程都是一个Reactor模型。在处理大并发的服务器任务上有很大优势
[
](https://blog.csdn.net/sinat_35261315/article/details/78329657)
1、类图
muduo关闭连接
2、整体简介
1、muduo目录 基础库
muduo | ||
---|---|---|
\—base | ||
|— AsyncLogging.{h,cc} | 异步日志 backend | |
|— Atomic.h | 原子操作与原子整数 | |
|— BlockingQueue.h | 无界阻塞队列(生产者消费者队列) | |
|— BoundedBlockingQueue.h | 有界阻塞队列 | |
|— Condition {h,cc} | 条件变量,与Mutex.h一同使用 | |
|— copyable.h | 一个空基类,用于标识(tag)值类型 | |
|— CountDownLatch {h,cc} | “倒计时门闩” 同步 | |
|— CurrentThread {h,cc} | 当前线程 | |
|— Date {h,cc} | Julian 日期库(即公历) | |
|— Exception {h,cc} | 带 stack trace 的异常基类 | |
|— FileUtil {h,cc} | 用来操作文件。AppendFile用来写日志 | |
|— GzipFile.h | 封装了gzip的一些API | |
|— LogFile {h,cc} | 日志文件 | |
|— Logging {h,cc} | 简单的日志,可搭配AsyncLogging使用 | |
|— LogStream {h,cc} | 流日志 | |
|— Mutex.h | 锁 | |
|— noncopyable.h | ||
|— ProcessInfo | 进程信息 | |
|— Singleton.h | 线程安全的singleton | |
|— StringPiece.h | 从google开源代码借用的字符串参数传递类型 | |
|— Thread {h,cc} | 线程对象 | |
|— ThreadLocal.h | 线程局部数据 | |
|— ThreadLocalSingleton.h | 每个线程一个singleton | |
|— ThreadPool {h,cc} | 简单的固定大小线程池 | |
|— Timestamp {h,cc} | UTC时间戳 | |
|— TimeZone {h,cc} | 时区与夏令时 | |
|— Types.h | 基本类型的声明,包括muduo::string | |
|— WeakCallback.h |
2、muduo目录 网络库
Buffer
muduo | ||
---|---|---|
\—net | ||
|— Acceptor.{h,cc} | 接受器,用于服务端接受连接 | |
|— boilerplate {h,cc} | ||
|— Buffer {h,cc} | 缓冲区,非阻塞IO必备 | |
|— BUILD.bazel | ||
|— Callbacks. {h,cc} | ||
|— Channel {h,cc} | 用于每个socket连接的事件分发 | |
|— Connector {h,cc} | 连接器,用于客户端发起连接 | |
|— Endian.h | 网络字节序与本机字节序的转换 | |
|— EventLoop {h,cc} | 事件分发器 | |
|— EventLoopThread {h,cc} | 新建一个专门用于EventLoop的线程 | |
|— EventLoopThreadPool {h,cc} | muduo默认多线程IO模型 | |
|— InetAddress {h,cc} | IP地址的简单封装 | |
|— Poller {h,cc} | IO multiplexing 的基类接口 | |
|— Socket {h,cc} | 封装socket描述符,负责关闭连接 | |
|— SocketsOps{h,cc} | 封装底层的socket API | |
|— TcpClient {h,cc} | tcp客户端 | |
|— TcpConnection {h,cc} | muduo里最大的一个类 | |
|— TcpServer {h,cc} | tcp服务端 | |
|— Timer {h,cc} | 定时器 | |
|— TimerId.h | ||
|— TimerQueue {h,cc} | 定时器回调 | |
|— ZlibStream.h |
net部分使用base中的工具类实现更高层次的逻辑,网络编程无非是对socket和其使用的epoll/poll等进行封装,使其便于使用,屏蔽掉底层网络库的一些 "坑", 在满足了基础的网络IO之后,就需要考虑高性能,高并发的问题,muduo 的是由poll/epoll 这些IO复用模型构成,但是单个IO线程在面对大量请求时难免处理不过来,所以就需要结合多线程或者线程池,一个线程对应一个epoll进行网络IO,这样就可以充分利用硬件多核系统。从软硬两方面综合提升性能。net部分封装的较为彻底,对上层提供的接口简单易用,所以涉及复杂的内部处理,接下来就对其内部实现进行探究。<br />Buffer和InetAddress具有值语义,可以拷贝;其他class 都是对象语义,不可以拷贝
Buffer
仿Netty ChannelBuffer的buffer class,数据的读写通过buffer 进行。用户代码不需要调用read()/write(),只需要处理收到的数据和 准备好要发送的数据
生命期由TcpConnection控制
InetAddress
封装IPv4地址(end point),注意,它不能解析域名, 只认IP地址。因为直接用gethostbyname()解析域名会阻塞IO线程
EventLoop
事件循环(反应器Reactor),每个线程只能有一个 EventLoop实体,它负责IO和定时器事件的分派。它用eventfd()来异步唤醒,这有别于传统的用一对pipe()的办法。它用TimerQueue作为计时器管理,用Poller作为IO multiplexing
EventLoopThread
TcpConnection
整个网络库的核心,封装一次TCP连接,注意它不能发起连接
生命期依靠shared_ptr管理(即用户和库共同控制)
TcpClient
TcpServer
Channel
Channel是selectable IO channel,负责注册与响应IO事件,注意它不拥有file descriptor。它是Acceptor、Connector、EventLoop、 TimerQueue、TcpConnection的成员,生命期由后者控制.
Socket
Socket是一个RAIIhandle,封装一个filedescriptor,并在析构时关闭 fd。它是Acceptor、TcpConnection的成员,生命期由后者控制。 EventLoop、TimerQueue也拥有fd,但是不封装为Socket class
SocketsOps
Poller
是PollPoller和EPollPoller的基类,采用“电平触发”的语意。 它是EventLoop的成员,生命期由后者控制
PollPoller EPollPoller
封装poll()和epoll()两种IO multiplexing后端。poll的存在价值是便于调试,因为poll(2)调用是上下文无关的,用 strace(1)很容易知道库的行为是否正确
Connector
用于发起TCP连接,它是TcpClient的成员,生命期由后者控制
Acceptor
用于接受TCP连接,它是TcpServer的成员,生命期由后者控制
TimerQueue
用timerfd实现定时,这有别于传统的设置 poll/epoll_wait的等待时长的办法。TimerQueue用std::map来管理Timer, 常用操作的复杂度是O(logN),N为定时器数目。它是EventLoop的成员,生命期由后者控制
EventLoopThreadPool
用于创建IO线程池,用于把TcpConnection分派到某个EventLoop线程上。它是TcpServer的成员,生命期由后者控制
3、Event Loop
4、Buffer
muduo buffer的设计要点:
1、对外表现为一块连续的内存(char* p,int len).方便客户代码的编写
2、其size()可以自动增长,以适应不同大小的消息。它不是一个fixed size array(例如 char buf[8192]).
3、内部以std:vector
4、input buffer, TcpConnection会从socket读取数据,然后写入input buffer.客户代码从input buffer读取数据。
5、output buffer 客户代码会把数据写入output buffer; TcpConnection 从output buffer读取数据并写入socket