多系统组成的网络通信软件——三个系统如何组合
前两篇分别演化出了业务系统(由模块组成)和网络系统(由组件组成)。现在要把它们组合成一个完整的网络通信软件。注意:网络通信软件分客户端和服务器,组成不同——客户端是「入口系统 + 网络系统」两个系统,服务器是「入口系统 + 业务系统 + 网络系统」三个系统。这一篇以服务器为例展开,客户端的组成在第九节说明。
一、两个系统的矛盾
单体程序跑得好好的,加了网络后,网络逻辑和业务逻辑混在一起:
1 2 3 4
| inspect_program(单体) ├── 读文件、扫描内存(业务逻辑) ├── 收发字节、管理连接(网络逻辑) └── 解析参数、输出结果(入口逻辑)
|
网络代码改了影响业务,业务改了影响网络。两个系统需要独立演化、独立测试。
二、拆成三个系统
先分清两种角色,组成不同:
1 2 3 4 5 6 7 8 9 10 11
| 客户端 = 入口系统 + 网络系统(两个系统) 客户端发起请求,业务逻辑在服务器端 入口系统:用户交互(输入 → 请求、响应 → 展示) 网络系统:收发字节 没有独立的业务系统——客户端的「业务」很薄,并入入口系统
服务器 = 入口系统 + 业务系统 + 网络系统(三个系统) 服务器等待请求、处理业务 入口系统:解析分发 业务系统:真正的业务逻辑 网络系统:收发字节
|
这一篇以服务器为例(三个系统),因为它是最完整的组成。把单体拆成三个独立系统:
1 2 3
| CLI(inspect_cli) ← 入口系统:解析命令、分发到业务 ├── libinspect(业务系统) ← 进程列表、内存扫描、文件操作 └── libnetwork(网络系统) ← EventLoop、Connection、Session、Protocol
|
1 2 3
| inspect_cli(入口适配器) ├── libinspect(业务系统) └── libnetwork(网络系统)
|
三个系统可以分别编译、分别测试、分别换实现。
三、三个系统的职责
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 入口系统(通信程序): 职责:接受输入 → 解析 → 分发 → 调用 结构:解析层(输入→命令)、分发层(命令→Handler)、调用层(Handler→业务) 关心:用户怎么操作、消息交给谁处理
业务系统: 职责:处理具体的业务逻辑 模块:process_list、memory_scan、file_io 关心:业务规则、数据处理、算法
网络系统: 职责:收发字节、管理连接、管理会话 构成:有状态组件 EventLoop/Connection/Session + 无状态工具 Buffer + 协议数据结构 Protocol 关心:字节怎么收发、连接怎么管理
|
三个系统通过接口连接,不通过实现连接。
四、系统的组装
谁来组装这三个系统?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 入口系统(inspect_cli)的 main 负责组装:
int main(int argc, char** argv) { // 1. 创建业务系统 InspectService service; // 2. 创建网络系统 NetworkClient network; // 3. 组装:把业务系统的功能以 Handler 形式挂到网络系统上 // (Handler = 入口系统的调用层:网络消息进来 → 调业务 → 发响应) network.on_message(CMD_LIST, [&](Session* s, const Message* msg) { auto result = service.listProcesses(); s->send(serialize(result)); }); // 4. 启动 network.start(port); }
|
入口系统是顶层的组装者——它位于依赖方向的最上层(见第七节),知道业务系统和网络系统的存在,负责把它们连接起来;业务系统和网络系统不互相组装、不互相依赖。
五、运行时的数据流
用户的一次操作经过三个系统:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 用户输入命令 ↓ 入口系统:解析命令 → 分发到 Handler ↓ Handler 调用业务系统:service.listProcesses() ↓ 业务系统:读取 /proc,返回进程列表 ↓ Handler 把结果序列化成消息 ↓ 网络系统:通过 Connection 发送给客户端 ↓ 客户端收到响应
|
三个系统的数据流是单向的:
1
| 入口系统 → 业务系统 → 入口系统 → 网络系统 → 客户端
|
六、三个系统的异常处理
每个系统的异常处理完全不同:
1 2 3 4 5 6 7 8 9 10 11
| 入口系统异常: 解析失败 → 发送错误响应给客户端 路由失败 → 发送"未知命令"错误
业务系统异常: 文件不存在 → 返回"文件未找到"错误 内存不足 → 返回"资源不足"错误
网络系统异常: 连接断开 → 清理会话,尝试重连 缓冲区满 → 丢弃数据,记录日志
|
每层只处理自己能处理的异常,不越层处理。
七、三个系统的依赖方向
1 2 3 4 5 6 7
| 入口系统 → 业务系统(调用业务逻辑) 入口系统 → 网络系统(调用收发能力)
✅ 入口系统依赖业务系统和网络系统 ❌ 业务系统不依赖入口系统 ❌ 网络系统不依赖入口系统 ❌ 业务系统和网络系统不互相依赖
|
依赖方向是单向的——入口系统在最上层,业务系统和网络系统在下层,互不依赖。
八、系统的封装:业务系统接口化,网络系统框架化
两个子系统封装形态不同,不能都叫「框架化子系统」:
1 2 3 4 5 6 7 8 9
| 业务系统(接口化): → 由模块组成(ProcessList + MemoryScan + FileIO) → 对外提供功能函数给调用层(service.listProcesses() 即调即用) → 无状态、无生命周期 → 不需要启动,调用即执行
网络系统(框架化): → 由组件组成(EventLoop + Connection + Session + Buffer + Protocol) → 有状态、持续运行(EventLoop 有自己的线程,一直在循环) → 必须在入口处实例化、启动、停止(network.start() / stop())
|
两种封装形态的本质区别:
1 2
| 接口化:无状态、即调即用 → 上层调用接口(业务系统) 框架化:有状态、有生命周期 → 入口实例化启动(网络系统)
|
多系统框架化后组装,就出现明显的启动流:
1 2 3 4 5 6 7
| main: 1. 实例化业务系统(接口化,不需要启动) 2. 实例化网络系统(框架化,需要启动) 3. 注册 Handler(把业务功能接到网络消息上) 4. 启动网络系统(框架化生命周期开始) 5. 运行(EventLoop 驱动一切) 6. 停止网络系统(框架化生命周期结束)
|
启动流是框架化系统特有的——接口化系统没有 start/stop,调用即执行;框架化系统才有「实例化 → 启动 → 运行 → 停止」的完整生命周期。
客户端 vs 服务器的入口系统:
1 2 3 4 5 6 7 8 9 10
| 入口骨架 = 接受 → 解析 → 分发 → 调用
客户端入口(CLI,主线 01 已解构): 接受 = 接收参数(argc/argv)
服务器入口(服务器入口解构线,已解构): 接受 = 通过接口接收消息(网络接口,非参数)
两者后面的结构相同: 解析层(输入→命令)+ 分发层(命令→Handler)+ 调用层(Handler→业务)
|
只有「接受」一步不同(参数 vs 接口),后面的解析层、分发层、调用层三层结构完全一样。服务器入口的解构有独立线展开(04-系统角色/07-服务器入口的解构)。
九、网络通信软件的完整架构
1 2 3 4 5 6 7 8 9 10 11 12
| ┌─────────────────────────────────────┐ │ 入口系统 │ │ CLI / GUI / HTTP Server │ │ 解析层 + 分发层 + 调用层 │ ├──────────────────┬──────────────────┤ │ 业务系统 │ 网络系统 │ │ ProcessList │ EventLoop │ │ MemoryScan │ Connection │ │ FileIO │ Session │ │ │ Protocol │ │ │ Buffer │ └──────────────────┴──────────────────┘
|
入口系统在最上层,负责接收输入和分发。业务系统和网络系统在下层,各自独立演化。三者通过接口连接,不通过实现连接。
客户端的架构(两个系统,没有业务系统):
1 2 3 4 5 6 7 8 9 10 11
| ┌─────────────────────┐ │ 入口系统 │ │ CLI / GUI │ │ 解析层+分发层+调用层 │ ├─────────────────────┤ │ 网络系统 │ │ EventLoop │ │ Connection │ │ Session │ │ Buffer / Protocol │ └─────────────────────┘
|
客户端没有业务系统——业务逻辑在服务器端,客户端只负责「发起请求、展示响应」。所以客户端是两系统(入口 + 网络),服务器是三系统(入口 + 业务 + 网络)。
收束:从单条指令到网络通信软件
1 2 3
| 第一篇:指令 → 结构 → 函数 → 文件 → 分层 → 业务系统(模块组成) 第二篇:socket → 循环 → 多连接 → EventLoop → 协议 → 会话 → 网络系统(组件组成) 第三篇:业务系统 + 网络系统 + 入口系统 → 网络通信软件(三系统协作)
|
三条演化线共享同一种逻辑:职责混乱 / 重复 / 边界 / 依赖 → 拆分 → 重新组织 → 更高一层的系统。 区别只在领域和终点。
所有的拆分都沿边界切:入口与业务之间是变化边界,业务与网络之间是职责/生命周期边界。边界的完整类型(职责/变化/生命周期/依赖/物理)有独立的线展开。
业务系统的模块和网络系统的组件是同一种东西的不同称呼——都是「数据结构 + 算法 + 接口」的封装,只是业务系统强调功能职责,网络系统强调运行机制。