多系统组成的网络通信软件——三个系统如何组合

前两篇分别演化出了业务系统(由模块组成)和网络系统(由组件组成)。现在要把它们组合成一个完整的网络通信软件。注意:网络通信软件分客户端服务器,组成不同——客户端是「入口系统 + 网络系统」两个系统,服务器是「入口系统 + 业务系统 + 网络系统」三个系统。这一篇以服务器为例展开,客户端的组成在第九节说明。


一、两个系统的矛盾

单体程序跑得好好的,加了网络后,网络逻辑和业务逻辑混在一起:

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 → 协议 → 会话 → 网络系统(组件组成)
第三篇:业务系统 + 网络系统 + 入口系统 → 网络通信软件(三系统协作)

三条演化线共享同一种逻辑:职责混乱 / 重复 / 边界 / 依赖 → 拆分 → 重新组织 → 更高一层的系统。 区别只在领域和终点。

所有的拆分都沿边界切:入口与业务之间是变化边界,业务与网络之间是职责/生命周期边界。边界的完整类型(职责/变化/生命周期/依赖/物理)有独立的线展开。

业务系统的模块和网络系统的组件是同一种东西的不同称呼——都是「数据结构 + 算法 + 接口」的封装,只是业务系统强调功能职责,网络系统强调运行机制。