【网络精讲6】muduo 的组件与接口:可视化了解muduo库
写在前面
上一篇把 muduo 的定位说清楚了:它是 IO 底座,不碰业务。定位清楚之后,下一个问题立刻浮上来------真动手写项目时,到底该调哪个类、哪个函数。
这个问题不好答,原因是 muduo 的类太多,而且分布在不同层:有的在 base,有的在 net,有的只活在库内部、外面根本碰不到。看官方示例能跑起来,但换成自己的业务,往往卡在"这个回调是谁调谁"、"这个缓冲区该从哪拿"上。
换一个视角会清楚很多:把这个库当成一块电路板。
- 芯片就是类,引脚就是公开接口;
- 引脚分方向:有的是外部往里灌信号的(调用接口),有的是芯片往外吐信号的(回调);
- 板上还有电源轨和地线(
base层的日志、时间、线程、锁),所有芯片都挂在上面; - 有一条总线在不停扫描(
EventLoop的poll),谁有信号就把谁的事件推出去。
按这个视角把整库摊开,写项目时需要的只有一件事:看准引脚,焊对线。
引脚签名全部来自实际安装的 /usr/local/include/muduo 头文件,不是凭记忆复述。
一、主板总览:整库只有六层
先看整块板子的分区。从上到下不是随意排的,而是严格的调用方向:上层只用得到下层的公开接口,反过来不行。
#mermaid-svg-oP9NvfX4TMcke8j8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oP9NvfX4TMcke8j8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oP9NvfX4TMcke8j8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oP9NvfX4TMcke8j8 .error-icon{fill:#552222;}#mermaid-svg-oP9NvfX4TMcke8j8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oP9NvfX4TMcke8j8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oP9NvfX4TMcke8j8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oP9NvfX4TMcke8j8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oP9NvfX4TMcke8j8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oP9NvfX4TMcke8j8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oP9NvfX4TMcke8j8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oP9NvfX4TMcke8j8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oP9NvfX4TMcke8j8 .marker.cross{stroke:#333333;}#mermaid-svg-oP9NvfX4TMcke8j8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oP9NvfX4TMcke8j8 p{margin:0;}#mermaid-svg-oP9NvfX4TMcke8j8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oP9NvfX4TMcke8j8 .cluster-label text{fill:#333;}#mermaid-svg-oP9NvfX4TMcke8j8 .cluster-label span{color:#333;}#mermaid-svg-oP9NvfX4TMcke8j8 .cluster-label span p{background-color:transparent;}#mermaid-svg-oP9NvfX4TMcke8j8 .label text,#mermaid-svg-oP9NvfX4TMcke8j8 span{fill:#333;color:#333;}#mermaid-svg-oP9NvfX4TMcke8j8 .node rect,#mermaid-svg-oP9NvfX4TMcke8j8 .node circle,#mermaid-svg-oP9NvfX4TMcke8j8 .node ellipse,#mermaid-svg-oP9NvfX4TMcke8j8 .node polygon,#mermaid-svg-oP9NvfX4TMcke8j8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oP9NvfX4TMcke8j8 .rough-node .label text,#mermaid-svg-oP9NvfX4TMcke8j8 .node .label text,#mermaid-svg-oP9NvfX4TMcke8j8 .image-shape .label,#mermaid-svg-oP9NvfX4TMcke8j8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-oP9NvfX4TMcke8j8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-oP9NvfX4TMcke8j8 .rough-node .label,#mermaid-svg-oP9NvfX4TMcke8j8 .node .label,#mermaid-svg-oP9NvfX4TMcke8j8 .image-shape .label,#mermaid-svg-oP9NvfX4TMcke8j8 .icon-shape .label{text-align:center;}#mermaid-svg-oP9NvfX4TMcke8j8 .node.clickable{cursor:pointer;}#mermaid-svg-oP9NvfX4TMcke8j8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-oP9NvfX4TMcke8j8 .arrowheadPath{fill:#333333;}#mermaid-svg-oP9NvfX4TMcke8j8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oP9NvfX4TMcke8j8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oP9NvfX4TMcke8j8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oP9NvfX4TMcke8j8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-oP9NvfX4TMcke8j8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oP9NvfX4TMcke8j8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-oP9NvfX4TMcke8j8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oP9NvfX4TMcke8j8 .cluster text{fill:#333;}#mermaid-svg-oP9NvfX4TMcke8j8 .cluster span{color:#333;}#mermaid-svg-oP9NvfX4TMcke8j8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oP9NvfX4TMcke8j8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-oP9NvfX4TMcke8j8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-oP9NvfX4TMcke8j8 .icon-shape,#mermaid-svg-oP9NvfX4TMcke8j8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oP9NvfX4TMcke8j8 .icon-shape p,#mermaid-svg-oP9NvfX4TMcke8j8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-oP9NvfX4TMcke8j8 .icon-shape .label rect,#mermaid-svg-oP9NvfX4TMcke8j8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oP9NvfX4TMcke8j8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-oP9NvfX4TMcke8j8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-oP9NvfX4TMcke8j8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 系统调用层
事件层
连接层
接口层
应用层
base 基础设施
Timestamp / Logging / AsyncLogging
Thread / ThreadPool / ThreadLocal
Mutex / Condition / BlockingQueue
业务代码
new TcpServer + setXxxCallback + loop.loop
TcpServer
一个端口一个实例
EventLoopThreadPool
子 Reactor 线程池
Acceptor
只做 accept
TcpConnection
每条连接一个
Connector
只做 connect
Channel
一个 fd 一个
Poller / EPollPoller
epoll 抽象
TimerQueue
定时器
wakeupChannel
跨线程唤醒
epoll_create1 / epoll_ctl / epoll_wait
accept4 / readv / write / close
timerfd_settime / eventfd
完整版布局图(含每层内部的组件划分与连线标注)放在文末附录。
每一层的职责和"要不要碰",可以列成一张表:
| 层 | 代表组件 | 职责 | 写业务时是否直接操作 |
|---|---|---|---|
| 应用层 | 自己的 ChatServer 之类 |
协议解析、业务分发、数据落库 | 全部自己写 |
| 接口层 | TcpServer、EventLoopThreadPool |
把监听、线程、回调三件事打包好 | 只调 setXxx 和 start() |
| 连接层 | Acceptor、TcpConnection、Connector |
收连接、管单条连接、发连接 | 只通过回调拿到 conn |
| 事件层 | Channel、Poller、TimerQueue |
fd 事件注册、epoll 等待、定时 | 基本不碰 |
| 系统调用层 | epoll_wait、accept4、timerfd |
真正的内核交互 | 完全不碰 |
| base 层 | Timestamp、Logging、Thread、Mutex |
时间、日志、线程、锁、队列 | 日志和计时会用到 |
一个关键事实:中间四层的组装全部由库自己完成 。TcpServer 构造时会自己 new Acceptor、new EventLoopThreadPool;Acceptor 会自己造 Channel;TcpConnection 会自己造 Channel 和两个 Buffer。业务代码一个都不需要 new。
二、TcpServer:一颗双面芯片
TcpServer 是应用唯一直接持有并配置的对象。把它看成一颗粒子,左边是灌进去的配置,右边是吐出来的回调。
#mermaid-svg-DqeuAIEzFjZ24Cyp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DqeuAIEzFjZ24Cyp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DqeuAIEzFjZ24Cyp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DqeuAIEzFjZ24Cyp .error-icon{fill:#552222;}#mermaid-svg-DqeuAIEzFjZ24Cyp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DqeuAIEzFjZ24Cyp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DqeuAIEzFjZ24Cyp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DqeuAIEzFjZ24Cyp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DqeuAIEzFjZ24Cyp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DqeuAIEzFjZ24Cyp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DqeuAIEzFjZ24Cyp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DqeuAIEzFjZ24Cyp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DqeuAIEzFjZ24Cyp .marker.cross{stroke:#333333;}#mermaid-svg-DqeuAIEzFjZ24Cyp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DqeuAIEzFjZ24Cyp p{margin:0;}#mermaid-svg-DqeuAIEzFjZ24Cyp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DqeuAIEzFjZ24Cyp .cluster-label text{fill:#333;}#mermaid-svg-DqeuAIEzFjZ24Cyp .cluster-label span{color:#333;}#mermaid-svg-DqeuAIEzFjZ24Cyp .cluster-label span p{background-color:transparent;}#mermaid-svg-DqeuAIEzFjZ24Cyp .label text,#mermaid-svg-DqeuAIEzFjZ24Cyp span{fill:#333;color:#333;}#mermaid-svg-DqeuAIEzFjZ24Cyp .node rect,#mermaid-svg-DqeuAIEzFjZ24Cyp .node circle,#mermaid-svg-DqeuAIEzFjZ24Cyp .node ellipse,#mermaid-svg-DqeuAIEzFjZ24Cyp .node polygon,#mermaid-svg-DqeuAIEzFjZ24Cyp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DqeuAIEzFjZ24Cyp .rough-node .label text,#mermaid-svg-DqeuAIEzFjZ24Cyp .node .label text,#mermaid-svg-DqeuAIEzFjZ24Cyp .image-shape .label,#mermaid-svg-DqeuAIEzFjZ24Cyp .icon-shape .label{text-anchor:middle;}#mermaid-svg-DqeuAIEzFjZ24Cyp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DqeuAIEzFjZ24Cyp .rough-node .label,#mermaid-svg-DqeuAIEzFjZ24Cyp .node .label,#mermaid-svg-DqeuAIEzFjZ24Cyp .image-shape .label,#mermaid-svg-DqeuAIEzFjZ24Cyp .icon-shape .label{text-align:center;}#mermaid-svg-DqeuAIEzFjZ24Cyp .node.clickable{cursor:pointer;}#mermaid-svg-DqeuAIEzFjZ24Cyp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DqeuAIEzFjZ24Cyp .arrowheadPath{fill:#333333;}#mermaid-svg-DqeuAIEzFjZ24Cyp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DqeuAIEzFjZ24Cyp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DqeuAIEzFjZ24Cyp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DqeuAIEzFjZ24Cyp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DqeuAIEzFjZ24Cyp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DqeuAIEzFjZ24Cyp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DqeuAIEzFjZ24Cyp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DqeuAIEzFjZ24Cyp .cluster text{fill:#333;}#mermaid-svg-DqeuAIEzFjZ24Cyp .cluster span{color:#333;}#mermaid-svg-DqeuAIEzFjZ24Cyp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-DqeuAIEzFjZ24Cyp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DqeuAIEzFjZ24Cyp rect.text{fill:none;stroke-width:0;}#mermaid-svg-DqeuAIEzFjZ24Cyp .icon-shape,#mermaid-svg-DqeuAIEzFjZ24Cyp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DqeuAIEzFjZ24Cyp .icon-shape p,#mermaid-svg-DqeuAIEzFjZ24Cyp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DqeuAIEzFjZ24Cyp .icon-shape .label rect,#mermaid-svg-DqeuAIEzFjZ24Cyp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DqeuAIEzFjZ24Cyp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DqeuAIEzFjZ24Cyp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DqeuAIEzFjZ24Cyp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 构造 TcpServer(loop, addr, name)
TcpServer
net/TcpServer.h
内部持有 Acceptor + 线程池 + 连接表
setThreadNum(n)
setThreadInitCallback(cb)
setConnectionCallback(cb)
setMessageCallback(cb)
setWriteCompleteCallback(cb)
start()
onConnection(conn)
onMessage(conn, buf, t)
onWriteComplete(conn)
左侧引脚,逐个看签名:
| 引脚 | 签名 | 说明 |
|---|---|---|
| 构造 | TcpServer(EventLoop* loop, const InetAddress& listenAddr, const string& nameArg, Option option = kNoReusePort) |
nameArg 会拼进每条连接的名字,日志里靠它区分 |
| 线程数 | void setThreadNum(int numThreads) |
子 Reactor 个数,0 表示不用线程池 |
| 线程初始化 | void setThreadInitCallback(const ThreadInitCallback& cb) |
参数是 void(EventLoop*),每个 IO 线程启动时执行一次 |
| 连接回调 | void setConnectionCallback(const ConnectionCallback& cb) |
上下线都会进来,靠 conn->connected() 区分 |
| 消息回调 | void setMessageCallback(const MessageCallback& cb) |
业务的真正入口 |
| 写完成回调 | void setWriteCompleteCallback(const WriteCompleteCallback& cb) |
输出缓冲被清空时触发 |
| 启动 | void start() |
构造时不监听,start() 才 listen |
三个容易被忽略的点:
第一个,构造函数的第四个参数。 绝大多数教程只写前三个参数,实际上有 Option option = kNoReusePort,取值 kNoReusePort / kReusePort。要开 SO_REUSEPORT(多进程共用一个端口做负载分摊)必须显式传 kReusePort,光加 SO_REUSEADDR 是不够的。
第二个,start() 之前什么都不会发生。 TcpServer 的构造函数只保存参数、创建 Acceptor 与线程池对象,不 bind、不 listen。忘了调 start() 的表现是:进程正常跑着,端口根本不存在,客户端连接直接 Connection refused。
第三个,三个回调一个都不设也能跑。 Callbacks.h 里定义了兜底实现 defaultConnectionCallback 与 defaultMessageCallback,TcpServer 成员初始就指向它们。兜底版本只打一行日志,并且把收到的数据直接丢弃。所以"服务能起来但收不到数据"这个现象,第一嫌疑永远是 setMessageCallback 没接上。
右侧三个回调的取值时刻:
| 回调 | 触发条件 | 典型用途 |
|---|---|---|
onConnection |
连接建立完成、连接销毁前 | 上线打日志、初始化会话、下线清理 |
onMessage |
inputBuffer_ 里可读数据到达 |
协议解析、业务分发 |
onWriteComplete |
outputBuffer_ 被写空 |
大文件分块续传、关闭输出缓冲监听 |
三、TcpConnection:最常焊的那颗
连接建立之后,TcpServer 会把每个连接包成 TcpConnectionPtr 交出来。之后所有针对单条连接的操作,都在这个对象上。
先说生命周期,这是最重要的一条。
TcpConnection 继承 enable_shared_from_this,由 shared_ptr 管理。绝不能把 TcpConnection* 或 TcpConnectionPtr 的引用长期保存下来------连接断开即析构,保存的指针随即悬空。所有对连接的操作,只用回调参数传进来的那个 TcpConnectionPtr。库里有一个专门的机制保护这一点:Channel::tie() 持有一个 weak_ptr,回调执行期间临时升级为 shared_ptr,保证这几十微秒内对象不会被析构。
状态机只有四个状态:
#mermaid-svg-lfU2dPa3q2juY5vH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lfU2dPa3q2juY5vH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lfU2dPa3q2juY5vH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lfU2dPa3q2juY5vH .error-icon{fill:#552222;}#mermaid-svg-lfU2dPa3q2juY5vH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lfU2dPa3q2juY5vH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lfU2dPa3q2juY5vH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lfU2dPa3q2juY5vH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lfU2dPa3q2juY5vH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lfU2dPa3q2juY5vH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lfU2dPa3q2juY5vH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lfU2dPa3q2juY5vH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lfU2dPa3q2juY5vH .marker.cross{stroke:#333333;}#mermaid-svg-lfU2dPa3q2juY5vH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lfU2dPa3q2juY5vH p{margin:0;}#mermaid-svg-lfU2dPa3q2juY5vH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lfU2dPa3q2juY5vH .cluster-label text{fill:#333;}#mermaid-svg-lfU2dPa3q2juY5vH .cluster-label span{color:#333;}#mermaid-svg-lfU2dPa3q2juY5vH .cluster-label span p{background-color:transparent;}#mermaid-svg-lfU2dPa3q2juY5vH .label text,#mermaid-svg-lfU2dPa3q2juY5vH span{fill:#333;color:#333;}#mermaid-svg-lfU2dPa3q2juY5vH .node rect,#mermaid-svg-lfU2dPa3q2juY5vH .node circle,#mermaid-svg-lfU2dPa3q2juY5vH .node ellipse,#mermaid-svg-lfU2dPa3q2juY5vH .node polygon,#mermaid-svg-lfU2dPa3q2juY5vH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lfU2dPa3q2juY5vH .rough-node .label text,#mermaid-svg-lfU2dPa3q2juY5vH .node .label text,#mermaid-svg-lfU2dPa3q2juY5vH .image-shape .label,#mermaid-svg-lfU2dPa3q2juY5vH .icon-shape .label{text-anchor:middle;}#mermaid-svg-lfU2dPa3q2juY5vH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lfU2dPa3q2juY5vH .rough-node .label,#mermaid-svg-lfU2dPa3q2juY5vH .node .label,#mermaid-svg-lfU2dPa3q2juY5vH .image-shape .label,#mermaid-svg-lfU2dPa3q2juY5vH .icon-shape .label{text-align:center;}#mermaid-svg-lfU2dPa3q2juY5vH .node.clickable{cursor:pointer;}#mermaid-svg-lfU2dPa3q2juY5vH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lfU2dPa3q2juY5vH .arrowheadPath{fill:#333333;}#mermaid-svg-lfU2dPa3q2juY5vH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lfU2dPa3q2juY5vH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lfU2dPa3q2juY5vH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lfU2dPa3q2juY5vH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lfU2dPa3q2juY5vH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lfU2dPa3q2juY5vH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lfU2dPa3q2juY5vH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lfU2dPa3q2juY5vH .cluster text{fill:#333;}#mermaid-svg-lfU2dPa3q2juY5vH .cluster span{color:#333;}#mermaid-svg-lfU2dPa3q2juY5vH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lfU2dPa3q2juY5vH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lfU2dPa3q2juY5vH rect.text{fill:none;stroke-width:0;}#mermaid-svg-lfU2dPa3q2juY5vH .icon-shape,#mermaid-svg-lfU2dPa3q2juY5vH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lfU2dPa3q2juY5vH .icon-shape p,#mermaid-svg-lfU2dPa3q2juY5vH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lfU2dPa3q2juY5vH .icon-shape .label rect,#mermaid-svg-lfU2dPa3q2juY5vH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lfU2dPa3q2juY5vH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lfU2dPa3q2juY5vH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lfU2dPa3q2juY5vH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} kConnecting
kConnected
kDisconnecting
kDisconnected
kConnecting 只在客户端侧短暂存在;服务端 onConnection 拿到连接时,状态已经是 kConnected。
主动操作引脚:
| 引脚 | 签名 | 注意事项 |
|---|---|---|
| 发送 | void send(const void* message, int len) |
最底层的一版,二进制安全 |
| 发送 | void send(const StringPiece& message) |
传字符串最方便 |
| 发送 | void send(Buffer* message) |
会 swap 数据,传进来的 Buffer 立刻变空 |
| 优雅关闭 | void shutdown() |
源码注释明确写 NOT thread safe,只在所属 loop 线程调 |
| 强制关闭 | void forceClose() / void forceCloseWithDelay(double seconds) |
不等输出缓冲,直接关 |
| 读开关 | void startRead() / void stopRead() |
做流量控制用,不读时自动摘掉 EPOLLIN |
| 读状态 | bool isReading() const |
注释同样标了 NOT thread safe |
| 粘包控制 | void setTcpNoDelay(bool on) |
关 Nagle,低延迟场景必开 |
| 挂业务状态 | void setContext(const boost::any&) / const boost::any& getContext() / boost::any* getMutableContext() |
会话对象挂这里,别自己维护 map |
| 缓冲区 | Buffer* inputBuffer() / Buffer* outputBuffer() |
直接拿库的缓冲区,注意有效期 |
| 水位回调 | void setHighWaterMarkCallback(const HighWaterMarkCallback& cb, size_t highWaterMark) |
两个参数:回调 + 阈值 |
三个 send 重载的区别值得单独说,因为这里出过太多问题:
send(const void*, int) 和 send(const StringPiece&) 都是拷贝 语义,数据复制进 outputBuffer_(或直接 writev 出去),调用方立刻可以释放自己的内存。
send(Buffer*) 是移动 语义,源码里就一行 std::swap(*message, outputBuffer_)。传进去的 Buffer 内容被换走,出来时是空的。这个设计是为了避免大块数据再拷贝一次,但如果调用方不知道这条,后面继续从那个 Buffer 读数据会读到空------表现是"转发大量数据时偶发丢包"。
查询引脚:
| 引脚 | 签名 | 用途 |
|---|---|---|
| 名字 | const string& name() |
形如 ChatServer-0.0.0.0:6000#3,日志定位用 |
| 对端地址 | const InetAddress& peerAddress() |
配合 toIpPort() 打印 1.2.3.4:5678 |
| 本端地址 | const InetAddress& localAddress() |
多网卡环境判断从哪个地址进来的 |
| 连接状态 | bool connected() / bool disconnected() |
onConnection 里区分上下线 |
| TCP 信息 | bool getTcpInfo(struct tcp_info*) / string getTcpInfoString() |
取 RTT、重传数、拥塞窗口 |
| 所属循环 | EventLoop* getLoop() |
判断当前线程是否连接所属线程 |
getTcpInfoString() 返回的是一行现成的可读文本,排查网络抖动时直接打进日志即可,不用自己解析 struct tcp_info 的几十个字段。
四、回调接线:std::bind 是焊锡
库和业务之间只有一个连接点:把库定义的回调类型,焊到自己的成员函数上。这一步焊接动作就是 std::bind。
EventLoop TcpConnection Acceptor TcpServer 业务代码 EventLoop TcpConnection Acceptor TcpServer 业务代码 #mermaid-svg-UKqiWxBNA9anU9Ld{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UKqiWxBNA9anU9Ld .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UKqiWxBNA9anU9Ld .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UKqiWxBNA9anU9Ld .error-icon{fill:#552222;}#mermaid-svg-UKqiWxBNA9anU9Ld .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UKqiWxBNA9anU9Ld .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UKqiWxBNA9anU9Ld .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UKqiWxBNA9anU9Ld .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UKqiWxBNA9anU9Ld .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UKqiWxBNA9anU9Ld .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UKqiWxBNA9anU9Ld .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UKqiWxBNA9anU9Ld .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UKqiWxBNA9anU9Ld .marker.cross{stroke:#333333;}#mermaid-svg-UKqiWxBNA9anU9Ld svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UKqiWxBNA9anU9Ld p{margin:0;}#mermaid-svg-UKqiWxBNA9anU9Ld .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-UKqiWxBNA9anU9Ld text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-UKqiWxBNA9anU9Ld .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-UKqiWxBNA9anU9Ld .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-UKqiWxBNA9anU9Ld .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-UKqiWxBNA9anU9Ld .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-UKqiWxBNA9anU9Ld #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-UKqiWxBNA9anU9Ld .sequenceNumber{fill:white;}#mermaid-svg-UKqiWxBNA9anU9Ld #sequencenumber{fill:#333;}#mermaid-svg-UKqiWxBNA9anU9Ld #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-UKqiWxBNA9anU9Ld .messageText{fill:#333;stroke:none;}#mermaid-svg-UKqiWxBNA9anU9Ld .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-UKqiWxBNA9anU9Ld .labelText,#mermaid-svg-UKqiWxBNA9anU9Ld .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-UKqiWxBNA9anU9Ld .loopText,#mermaid-svg-UKqiWxBNA9anU9Ld .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-UKqiWxBNA9anU9Ld .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-UKqiWxBNA9anU9Ld .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-UKqiWxBNA9anU9Ld .noteText,#mermaid-svg-UKqiWxBNA9anU9Ld .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-UKqiWxBNA9anU9Ld .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-UKqiWxBNA9anU9Ld .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-UKqiWxBNA9anU9Ld .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-UKqiWxBNA9anU9Ld .actorPopupMenu{position:absolute;}#mermaid-svg-UKqiWxBNA9anU9Ld .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-UKqiWxBNA9anU9Ld .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-UKqiWxBNA9anU9Ld .actor-man circle,#mermaid-svg-UKqiWxBNA9anU9Ld line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-UKqiWxBNA9anU9Ld :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} setMessageCallback(onMessage)start()listen()listenfd 可读handleRead 产出新连接创建 TcpConnectiononConnection(conn)连接 fd 可读readFd 写入 inputBuffer_onMessage(conn, buf, t)send(reply)sendInLoop 或直接 writev
上面的时序图讲的是执行顺序;静态的接线关系------五个回调类型各自焊到哪个业务函数------见文末附录。
五种回调类型的完整定义(net/Callbacks.h):
| 类型 | 定义 | 由谁调用 | 什么时候 |
|---|---|---|---|
ConnectionCallback |
std::function<void(const TcpConnectionPtr&)> |
TcpConnection |
连接建立完成、销毁前 |
MessageCallback |
std::function<void(const TcpConnectionPtr&, Buffer*, Timestamp)> |
TcpConnection |
数据到达并写入输入缓冲后 |
WriteCompleteCallback |
std::function<void(const TcpConnectionPtr&)> |
TcpConnection |
输出缓冲写空 |
HighWaterMarkCallback |
std::function<void(const TcpConnectionPtr&, size_t)> |
TcpConnection |
输出缓冲超过阈值,参数是超阈前的积压字节数 |
CloseCallback |
std::function<void(const TcpConnectionPtr&)> |
TcpConnection |
连接销毁,库内部用 |
TimerCallback |
std::function<void()> |
EventLoop |
定时到期 |
Callbacks.h 顺手把 _1、_2、_3 三个占位符 using 到了 muduo::net 命名空间,所以写 std::bind(&ChatServer::onMessage, this, _1, _2, _3) 不需要自己再 using namespace std::placeholders。
接线写法只有一条原则:成员函数必须用 std::bind 绑 this,或者直接用 lambda 捕获 this 。回调函数签名里的参数个数必须和 std::function 完全对上,MessageCallback 就是三个参数,少写一个编译期报错,多写一个也报错。
五、引脚总表
写代码时对着这张表查即可。
| 组件 | 关键接口 | 干什么用 |
|---|---|---|
TcpServer |
TcpServer(loop, addr, name, option) · setThreadNum(n) · setThreadInitCallback · setConnectionCallback · setMessageCallback · setWriteCompleteCallback · start() · ipPort() · name() · getLoop() · threadPool() |
服务端总装,一个端口一个 |
TcpConnection |
send(void*, int) · send(StringPiece) · send(Buffer*) · shutdown() · forceClose() · forceCloseWithDelay(s) · startRead() · stopRead() · isReading() · setTcpNoDelay(bool) · setContext/getContext · inputBuffer()/outputBuffer() · setHighWaterMarkCallback(cb, n) · name() · localAddress() · peerAddress() · connected()/disconnected() · getTcpInfoString() |
单条连接,最常用 |
EventLoop |
loop() · quit() · runInLoop(cb) · queueInLoop(cb) · runAt/runAfter/runEvery · cancel(TimerId) · isInLoopThread() · assertInLoopThread() · setContext/getContext |
事件循环,跨线程调度 |
Channel |
enableReading() · enableWriting() · disableReading() · disableWriting() · disableAll() · isWriting() · handleEvent(t) · set{Read,Write,Close,Error}Callback · tie(shared_ptr) · fd() · events() |
一个 fd 的事件封装 |
Poller |
poll(timeoutMs, activeChannels) · updateChannel(ch) · removeChannel(ch) · hasChannel(ch) |
epoll 的抽象基类,EPollPoller 是实现 |
Buffer |
readableBytes() · writableBytes() · prependableBytes() · peek() · retrieve(n) · retrieveAll() · retrieveAsString(n) · retrieveAllAsString() · append(str, len) · findCRLF() · readFd(fd, &err) |
用户态读写缓冲 |
InetAddress |
InetAddress(port) · InetAddress(ip, port) · toIp() · toIpPort() · port() |
地址端口封装 |
EventLoopThreadPool |
setThreadNum(n) · start(cb) · getNextLoop() · getLoopForHash(h) · getAllLoops() |
子 Reactor 线程池 |
EventLoopThread |
EventLoopThread(cb, name) · startLoop() |
把一个 loop 绑到新线程上跑 |
TcpClient |
connect() · disconnect() · enableRetry() · setConnectionCallback · setMessageCallback |
客户端侧,连别的服务 |
TimerId |
拷贝、比较、赋值 | 定时器句柄,用于 cancel |
Timestamp |
now() · addTime(ts, s) · timeDifference(hi, lo) · toFormattedString() · microSecondsSinceEpoch() |
微秒时间戳 |
Logging |
LOG_INFO · LOG_WARN · LOG_ERROR · LOG_DEBUG · LOG_FATAL · CHECK_EQ/CHECK_NE/CHECK_LT · setLogLevel() |
同步日志 |
AsyncLogging |
AsyncLogging(baseName, rollSize, flushInterval) · start() · stop() · append(line, len) |
异步日志,后台线程落盘 |
Thread |
Thread(func, name) · start() · join() · tid() · cachedTid() |
线程封装 |
ThreadPool |
ThreadPool(name) · start(n) · run(Task) · setMaxQueueSize(n) · stop() |
计算任务池,业务侧用 |
Mutex / Condition |
MutexLockGuard(lock) · wait() · notify() · notifyAll() |
锁与条件变量 |
BlockingQueue |
put(x) · take() · size() |
无界线程安全队列 |
BoundedBlockingQueue |
put(x) · take() · size() · capacity() |
有界队列,内部是 boost::circular_buffer |
Singleton |
Singleton<T>::instance() |
全局单例 |
七、两个实测出来的坑
这两条都能复现,且都跟"头文件"有关。
7.1 内部头文件根本没装
按官方 make install 装完之后,/usr/local/include/muduo/net/ 下只有 12 个平铺头文件,而源码里这个目录有 21 个。差出来的 9 个是:
Acceptor.h、Connector.h、Poller.h、Socket.h、SocketsOps.h、Timer.h、TimerQueue.h,外加 boilerplate.h(空占位类)和 ZlibStream.h(没有参与编译)。
子目录同样不全:poller/ 下的 EPollPoller.h、PollPoller.h,inspect/ 下的 PerformanceInspector.h、ProcessInspector.h、SystemInspector.h,都没有装。
原因在 muduo/net/CMakeLists.txt。它的 install 规则是显式白名单,不是 glob 全量:
cmake
set(HEADERS
Buffer.h
Callbacks.h
Channel.h
Endian.h
EventLoop.h
EventLoopThread.h
EventLoopThreadPool.h
InetAddress.h
TcpClient.h
TcpConnection.h
TcpServer.h
TimerId.h
)
install(FILES ${HEADERS} DESTINATION include/muduo/net)
这 12 个是"对外面够用"的集合,内部类没有列进去。但它们的 .cc 全都编译进了 libmuduo_net.a------实现是齐的,只缺声明文件。
后果很直接:按某些教程写 #include <muduo/net/Acceptor.h> 会编译失败,报 No such file or directory。遇到这种情况不必怀疑装错了,补头文件就行:
bash
# 在 muduo 源码目录下,把缺失的内部头文件补进去
cd ~/muduo/muduo/net
# 补齐 net/ 下的平铺头文件
sudo install -m 644 Acceptor.h Connector.h Poller.h Timer.h TimerQueue.h \
Socket.h SocketsOps.h /usr/local/include/muduo/net/
# poller/ 子目录不存在,先建目录再拷
sudo install -d /usr/local/include/muduo/net/poller
sudo install -m 644 poller/EPollPoller.h poller/PollPoller.h \
/usr/local/include/muduo/net/poller/
这里有个细节值得注意:install -D 看起来正好用于"目标目录不存在时自动创建",但它只接受单个源文件 。install -m 644 -D a.h b.h dir/ 会直接报 目标不是目录。多文件场景必须先 install -d 把目录建好,再批量拷进去。
补完不需要重新编译库,也不需要 ldconfig(静态库本来就不需要)。补完后的验证方式是:#include 一遍所有头文件,再实际构造一个 Acceptor 并 listen() 一个端口------能编过、能链上、能跑通,说明声明与实现版本一致。
有两个头文件即使补了也不该用:
net/ZlibStream.h:ZlibStream.cc不在net_SRCS列表里,只被单元测试引用,补上也链接不到实现。net/protobuf/、net/protorpc/:只有检测到 protobuf 才会生成对应库文件,环境里没有libmuduo_protobuf.a时装了也链接失败。
7.2 boost 没有被"彻底移除"
上一篇提到过,muduo 2.0 迁移到 C++11 之后不再需要链接 boost,这一点在链接命令上完全成立-------lmuduo_net -lmuduo_base -lpthread 三个就够了,.a 里查不到任何 boost 未定义符号。
但要补一句更精确的:boost 从链接依赖降级成了头文件依赖,没有被彻底移除。实际安装的头文件里仍有四处引用:
| 头文件 | 引用内容 | 影响面 |
|---|---|---|
base/Timestamp.h |
boost/operators.hpp,继承 equality_comparable / less_than_comparable |
几乎每个程序都会碰到 ,日志、定时器全靠 Timestamp |
net/EventLoop.h |
boost::any,用于 setContext |
想给 loop 挂上下文就会触发 |
net/TcpConnection.h |
boost::any,用于 setContext |
想给连接挂会话对象就会触发 |
base/BoundedBlockingQueue.h |
boost/circular_buffer.hpp |
只有用这个模板类才触发 |
所以 libboost-dev 是编译期必需依赖,缺了会在 #include <muduo/base/Timestamp.h> 这一步直接失败:
fatal error: boost/operators.hpp: No such file or directory
安装方式只有头文件,不需要运行库:
bash
sudo apt install libboost-dev
这也是 setContext 的签名写的是 boost::any 而不是 std::any 的原因------库里这块一直没换。
速查与常见追问
为什么 send 有三个重载
底层那版 send(const void*, int) 最通用;send(const StringPiece&) 是为了不用写 strlen;send(Buffer*) 是为大块数据省一次拷贝。前两版是拷贝语义,第三版是交换语义,传进去的 Buffer 会变空。批量转发场景用第三版最省内存,但必须知道它把数据"拿走"了。
shutdown() 和 forceClose() 有什么区别
shutdown() 是优雅关闭:先关写端,等对端也关,等输出缓冲写完,走完四次挥手再销毁。forceClose() 直接关,输出缓冲里没发完的数据丢弃。业务里正常下线用 shutdown(),心跳超时、协议违规这种要立刻断开用 forceClose()。forceCloseWithDelay(seconds) 是延迟一段时间再强关,给对端一点时间收到最后的数据。
为什么 shutdown() 标了 NOT thread safe,send() 却可以从任意线程调
send() 内部判断了当前线程,不在所属 loop 线程就把任务 queueInLoop 转发过去,是线程安全的。shutdown() 直接改 Channel 的注册状态,没有做转发,跨线程调用会让 epoll_ctl 和事件派发打架。同理 isReading() 也标了不线程安全,因为它在读 reading_ 这个普通 bool。
回调里能不能做耗时操作
不能。所有回调都在 IO 线程里执行,一个回调卡住,该线程上所有连接全部停摆。数据库查询、磁盘写、加解密这类活要丢给 ThreadPool,算完再用 loop->runInLoop() 把结果送回 IO 线程发出去。
setThreadNum(0) 是什么效果
不启用子 Reactor 线程池,所有连接全部挂在主线程的 loop 上。单线程也能扛几千连接,前提是回调足够快。适合调试,不适合生产。
为什么要用 setContext 而不是自己维护 map<string, Session>
自己维护 map 要处理两件事:一是并发,回调在多个 IO 线程里跑,map 必须加锁;二是生命周期,连接销毁时得记得擦掉,漏一处就是内存泄漏。setContext 把会话对象挂在连接自己身上,连接死了对象跟着死,不需要锁,也不需要清理。
inputBuffer() 拿到的指针能存下来吗
不能。这个 Buffer 属于 TcpConnection 内部,retrieveAll 之后下标会重置,缓冲区扩容时内存可能重新分配。回调结束前把数据拷走或解析完,不要把指针带出去。
主 Reactor 和子 Reactor 分别在哪
主 Reactor 是 main 里那个 EventLoop,只负责 accept。子 Reactor 是 EventLoopThreadPool 里的 N 个 EventLoop,各自跑一个线程,负责已建立连接的读写。TcpServer 在 start() 时把线程池拉起来,之后每来一条新连接,按轮询分给其中一个 loop,这条连接之后的所有事件都归那个线程。
附一:三层文件对照
| 层 | 头文件 | 是否可直接 include |
|---|---|---|
| 对外接口 | TcpServer.h、TcpClient.h、TcpConnection.h、EventLoop.h、EventLoopThread.h、EventLoopThreadPool.h、Channel.h、Buffer.h、InetAddress.h、TimerId.h、Callbacks.h、Endian.h |
官方 install 自带 |
| 内部实现 | Acceptor.h、Connector.h、Poller.h、poller/EPollPoller.h、poller/PollPoller.h、Socket.h、SocketsOps.h、Timer.h、TimerQueue.h |
需手动补装,见 7.1 |
| 无实现 | ZlibStream.h、protobuf/*、protorpc/* |
不建议使用 |
附二:完整电路板图
四张图按调用方向自上而下排布:应用层 → 接口层 → 连接层 → 事件层 → 系统调用层 → base 基础设施。
相比正文的流程图,这组图多出三类信息:每层内部的组件划分(比如 TcpConnection 里并排的 inputBuffer_、outputBuffer_、Channel、Socket、context)、连线上的动作标注(配置回调 / 内部持有 / 新建连接 / 注册 Channel / epoll_ctl)、以及双面引脚的左右分栏布局。
