网络系统设计——从想法到实现

业务设计线走「需求→业务流程→功能点→功能流程→算法→数据结构」(做什么),CLI/GUI 设计线走「入口控制面→解析→分发→调用」(怎么被外部使用)。网络系统是第三种角色,它的设计线有自己的控制面——协议。本篇把这条线从想法(通信需求)一路走到实现(系统组装),每一步给出原子步骤清单。

这是 04-系统角色/04-通信系统 里设计路线的完整展开,也是 06-设计/05-通信程序的设计顺序 的角色视角版——那边讲「按什么顺序做」,本篇讲「每一步具体做什么」。


主线:七步从外到内

1
2
3
4
5
6
7
8
9
10
11
12
13
① 通信需求        要解决什么通信问题

② 协议设计 双方按什么规则交换什么(对端之间)

③ 组件设计 拆成哪些组件(内部怎么分)

④ 组件接口设计 每个组件暴露什么接口(组件之间)

⑤ 组件实现 按接口写实现

⑥ 框架化 组件封装成可运行的系统

⑦ 系统组装 网络系统 + 入口 + 业务 → 完整软件

每一环都是「先设计对外的,再设计对内的」:

1
2
3
4
5
6
协议(对外:两端怎么对齐)
→ 组件(对内:内部怎么拆)
→ 接口(对内:组件之间怎么约定)
→ 实现(最内:具体代码)
→ 测试(验证前面每一步)
→ 框架化 + 组装(拼成可运行系统)

① 通信需求——要解决什么通信问题

设计网络系统之前,先回答:这个程序要解决什么通信问题?

1
2
3
4
谁和谁通信?        客户端↔服务器 / 服务↔服务 / 设备↔服务器
通信什么? 登录请求 / 数据查询 / 实时消息 / 文件传输
什么时候通信? 请求-响应 / 主动推送 / 持续流式
对延迟和可靠性的要求? 实时交互 / 准时送达 / 允许丢消息

通信需求决定了后面的协议选择和组件构成:

1
2
3
4
实时交互(游戏/聊天)  → TCP 长连接 + 低延迟协议 + Session 心跳
请求-响应(HTTP API) → TCP + 请求-响应协议 + 连接池
允许丢消息(直播) → UDP + 简单封装
持续流式(文件传输) → TCP + 分块协议 + Buffer

不是所有程序都有通信系统。 纯 CLI / 纯 GUI = 入口 + 业务,没有通信系统。只有服务器、联网客户端才有——它们才需要「把外部通信转换成内部消息」。详见 04-系统角色/04-通信系统


② 协议设计(最先)——双方按什么规则交换什么

协议是网络系统的第一个设计对象——它是通信程序的对外控制面。设计起点线说「通信程序先设计协议」,因为两端各自独立运行、互不共享内存,唯一能对齐的就是协议。

协议设计的原子步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
确定通信双方          谁是 Client,谁是 Server(或对等)
确定通信方向 单向 / 双向 / 请求-响应
确定通信消息类型 LOGIN / LOGIN_RESULT / HEARTBEAT / DATA …
确定请求类型 哪些消息是请求
确定响应类型 哪些消息是响应
确定消息生命周期 一次性 / 可重发 / 有序
确定消息结构 头(Header)+ 体(Body)
确定消息字段 length / command / version / payload …
确定字段类型 uint32 / string / bytes
确定字段长度 固定 / 可变(长度前缀)
确定字段编码 大端 / 小端 / UTF-8 / 二进制
确定消息边界 长度前缀 / 分隔符 / 固定长度
确定消息序列 是否需要序号、序号怎么生成
确定请求与响应关联 序号匹配 / 同步等待 / 异步回调
确定错误消息 错误码体系 / 错误消息格式
确定协议状态 握手 / 正常 / 关闭 …
确定协议状态转换 握手成功→正常,异常→关闭
确定协议版本 1.0 / 2.0 / 向前兼容
确定协议扩展方式 新增字段 / 版本协商 / TLV
形成协议描述 文档 / IDL / Schema

例子

1
2
3
4
5
Client → Server
LOGIN { version:1, username, password }

Server → Client
LOGIN_RESULT { success, token, error_code }

关键区分

协议设计 ≠ 组件接口设计。协议是对端之间的交换规则(线上长什么样,wire format),接口是组件之间的调用契约(代码怎么调)。同一个协议可以有多种接口实现;同一个接口也可以换不同协议。详见 04-系统角色/04-通信系统

协议管「线上长什么样」,接口管「代码怎么调」——两者是独立的设计对象,不能混。


③ 组件设计——拆成哪些组件

协议定了,内部按职责拆组件。网络系统是分组件、不是分层(和业务系统的模块不同,和入口系统的分层也不同)。

组件设计的原子步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
识别网络组件          Socket / TCP-UDP / Connection / Buffer / EventLoop
确定网络组件职责 什么时候有事、字节怎么收发
确定网络组件边界 只管字节,不管消息语义
识别连接组件 Connection
确定连接组件职责 一条连接的生死(建立/维护/关闭)
识别会话组件 Session
确定会话组件职责 每个连接的状态(缓冲区/协议状态/活跃时间)
识别缓冲组件 Buffer
确定缓冲组件职责 字节暂存(接收攒够再处理、发送排队)
识别事件组件 EventLoop
确定事件组件职责 只通知「谁有事」,不关心事是什么
识别线程组件 工作线程 / IO 线程
确定线程组件职责 谁跑 EventLoop、谁跑业务
识别定时器组件 Timer
确定定时器组件职责 超时检测、心跳保活
识别编解码组件 Codec(注意:编解码在网络系统边界,解析在入口系统)
确定编解码组件职责 字节 ↔ 消息结构的转换(或交给入口系统解析层)
识别通信调度组件 Dispatch / Routing(注意:属于入口系统,不属于网络系统)
确定通信调度组件职责 消息 → Handler(入口系统的事)
建立组件关系 谁调用谁、谁持有谁

三类性质不同的东西(不是五个对等组件)

1
2
3
4
5
6
网络系统
├── EventLoop ← 有状态组件(有生命周期、独立线程)
├── Connection ← 有状态组件(有状态、有生命周期)
├── Session ← 有状态组件(有状态、有生命周期)
├── Buffer ← 无状态工具(数据结构 + 算法,被读写时操作)
└── Protocol ← 数据结构(协议格式定义,不是组件)

注意边界:解析层(Parser,字节↔消息转换)和调度层(Dispatcher,消息→Handler)属于入口系统,不属于网络系统。网络系统只负责字节进、字节出。详见 网络系统/02-组件拆分网络系统组件/01-拆分演化

每个组件的完整形态见 网络系统组件/(02 EventLoop / 03 Connection / 04 Session / 05 Buffer / 06 Protocol)。


④ 组件接口设计——每个组件暴露什么

组件拆好了,每个组件对外暴露什么接口?这是组件之间的调用契约。

组件接口设计的原子步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
确定网络接口          socket 操作封装(connect / accept / close)
确定连接接口 建立连接 / 断开 / 重连 / 状态查询
确定会话接口 创建会话 / 销毁会话 / 获取状态 / 超时
确定缓冲接口 read / write / peek / compact / 扩容
确定编码接口 消息结构 → 字节流
确定解码接口 字节流 → 消息结构
确定消息接口 消息对象的字段访问 / 校验
确定发送接口 send(msg) / send_raw(bytes) / 批量发送
确定接收接口 on_data(bytes) / on_message(msg)
确定事件接口 on_connect / on_disconnect / on_error / on_timeout
确定生命周期接口 start / run / stop / join
确定停止接口 graceful shutdown(排空缓冲、关连接)
确定错误处理接口 on_error(code, msg) / 重试策略
建立组件调用关系 EventLoop → Connection → Session → Buffer → Codec

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// EventLoop 接口
void start();
void stop();
void run_once(int timeout_ms);

// Connection 接口
bool connect(const Address& addr);
void close();
void send(const Buffer& data);
void set_callback(ICallback* cb);

// Session 接口
SessionId id() const;
ProtocolState state() const;
Buffer& recv_buffer();
void mark_active();
bool is_timeout(int seconds) const;

接口与协议的关系

1
2
3
4
协议设计  → 定义消息长什么样(对端规则)
组件接口设计 → 定义组件怎么调用(组件契约)
接口收发的是协议定义的消息
→ 所以接口设计依赖协议设计

接口设计 ≠ 协议设计——协议定义双方交换什么,接口定义组件之间怎么调用。详见 04-系统角色/04-通信系统


⑤ 组件实现——按接口写实现

前面三步(协议、组件、接口)都定清楚了,实现就是填空:

1
2
3
4
5
EventLoop 实现    epoll/kqueue 封装、事件分发、定时器管理
Connection 实现 socket 读写、连接状态机、重连逻辑
Session 实现 缓冲区管理、协议状态跟踪、活跃时间
Buffer 实现 字节数组、读写指针、扩容、compact
Protocol 实现 struct Message 定义、字段访问器

实现顺序

实现按依赖关系(拓扑排序)来,不是随意顺序:

1
2
3
4
5
Protocol(数据结构,无依赖)
→ Buffer(工具,无依赖)
→ Connection(依赖 Buffer + socket)
→ Session(依赖 Connection + Protocol)
→ EventLoop(驱动 Connection 和 Session)

实现的约束

1
2
3
4
必须遵守协议:不能改协议定义的消息格式
必须遵守接口:不能改接口说好的行为
实现可以换:epoll 换 kqueue、阻塞 IO 换非阻塞 IO
→ 前提是接口不变

⑥ 接口测试 + 框架化

接口测试(实现之后)

验证「接口说好的行为」真的成立:

1
2
单元测试    单个组件内部逻辑(Buffer 读写指针、Connection 状态机)
接口测试 组件接口按设计工作(EventLoop 事件分发、Session 超时)

测试调的是接口——接口没定就没东西可测。所以测试依赖接口设计,不能在接口没定之前就写测试。

框架化——组件封装成可运行的系统

组件实现并测试通过后,封装成可调用的系统。封装形态由组件的性质决定:

1
2
3
4
5
6
7
8
9
10
有状态组件(EventLoop / Connection / Session)
→ 类封装(框架化):有生命周期,入口启动
→ start / run / stop,入口创建对象、调 start、程序结束调 stop

无状态工具(Buffer)
→ 函数导出(接口化):即调即用,无需初始化
→ read / write / peek

数据结构(Protocol)
→ 不需要封装:一份 struct Message 定义,被直接引用

框架化的详细步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
确定框架管理对象      NetworkSystem(管理 EventLoop / Connection / Session)
确定组件创建方式 框架统一创建 / 组件自创建
确定组件初始化方式 构造函数 / init() / 配置注入
确定组件启动方式 start() → EventLoop 先跑、Connection 再连
确定组件运行方式 EventLoop 驱动、Connection 回调、Session 状态更新
确定组件停止方式 stop() → 排空缓冲 → 关连接 → 停 EventLoop
确定组件销毁方式 析构 / 释放资源
确定组件生命周期顺序 启动:EventLoop → Connection → Session
停止:Session → Connection → EventLoop(逆序)
确定框架运行循环 EventLoop.run() 阻塞等待事件
确定框架事件处理方式 on_data → 解析 → on_message → 上层
确定框架异常处理方式 组件异常 → 上报框架 → 框架决定重试/关闭
确定框架组件注册方式 add_connection / remove_connection
确定框架组件组装方式 框架持有所有组件,组件之间通过框架中转
确定框架启动流程 create → init → start → run
确定通信系统与业务系统连接方式 Handler 接口(入口系统的 Handler 指向业务)

框架化 = 入口启动(入口创建对象、调 start、stop),接口化 = 上层调用接口(即调即用)。详见 网络系统/03-组装调用

只有组件、没有封装的情况

如果网络系统只有组件、没有内部封装,需要额外封装

1
2
3
4
5
6
7
8
9
10
11
12
13
两种封装:
① 内部封装好了框架或接口
框架化 → 入口启动
接口化 → 上层调用接口化后的接口
② 只有组件、没有封装
→ 需要额外封装为框架(类)或接口(函数导出),才能被调用

封装落点由归属决定:
业务组件接口化 → 功能函数/模块(供调用层)
库接口 → 基础设施层(封装为原子化接口)
无框架全局工具 → utils(如 str / time)
框架化全局工具 → utils(如 log,有生命周期但仍是全局工具)
非全局子系统 → 入口启动(如 EventLoop / NetworkSystem)

详见 02-拆解与组织/05-组件加载


⑦ 系统组装——网络系统 + 入口 + 业务 → 完整软件

框架化之后,网络系统 + 入口系统 + 业务系统拼成完整软件。

组成不同

1
2
客户端 = 入口系统 + 网络系统(两系统,无业务系统或业务在服务器端)
服务器 = 入口系统 + 业务系统 + 网络系统(三系统)

组装流程

1
2
3
4
5
6
7
8
9
10
11
入口系统(顶层组装者)
├── 创建网络系统框架(NetworkSystem)
│ → start EventLoop / connect Connection / 创建 Session
├── 创建业务系统(Service / Domain / Algorithm)
└── 连接:入口系统的 Handler 指向业务系统的接口

完整通信流程:
客户端发请求 → 网络系统收发字节
→ 入口系统解析(字节→消息)+ 分发(消息→Handler)
→ 业务系统处理(Handler → Service → Domain)
→ 响应回传 → 网络系统发回 → 客户端收到

E2E 测试

1
2
3
客户端发请求 → 网络系统收发 → 入口系统解析分发
→ 业务处理 → 响应回传 → 客户端收到
→ 验证完整通信流程能跑通

为什么顺序不能反

1
2
3
4
5
接口依赖协议    接口收发的是协议定义的消息
测试依赖接口 测试调的是接口
实现依赖接口 实现的是接口说好的行为
框架化依赖实现 要封装的是已经实现并测试通过的组件
组装依赖框架化 要组装的是已经框架化的系统

所以顺序是强制的:

1
2
3
4
5
协议没定就写接口  → 接口收发什么都不知道
接口没定就写实现 → 实现不知道要暴露什么
实现没写就测试 → 没有东西可测
没测试就框架化 → 封装的是没验证的代码
没框架化就组装 → 组装的是散组件,不是可运行系统

先协议、再组件、再接口、再实现、再测试、再框架化、再组装——顺序不能反。


与其他线的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
本线 ←→ 06-设计/05-通信程序的设计顺序
06-设计/05 是「设计顺序」视角:按什么顺序做(七步倒推)
本篇是「角色」视角:每一步具体做什么(原子步骤清单)
两者互补,不重复

本线 ←→ 04-系统角色/04-通信系统
04 讲通信系统的角色(职责、组织单位、边界)
本篇讲通信系统的设计过程(从想法到实现)

本线 ←→ 网络系统/01-从socket到网络系统
从 socket 是演化史(怎么一步步长出来)
本篇是设计线(拿到需求后按什么步骤设计)

本线 ←→ 网络系统/02-组件拆分
组件拆分讲网络系统内部有哪些东西(构成)
本篇第 ③ 步用它的结论

本线 ←→ 网络系统/03-组装调用
组装调用讲封装形态(框架化/接口化)
本篇第 ⑥ 步用它的结论

本线 ←→ 网络系统组件/
网络系统组件讲每个组件的完整形态
本篇第 ③ 步引用该线

本线 ←→ 测试线(07-工程控制/05-测试)
网络系统的测试顺序依赖设计顺序(协议→组件→接口→实现→测试)
测试线里网络系统的测试 = 组件内部 + 组件接口 + 完整通信流程

本线 ←→ 业务分析/(业务设计线)
业务设计线走「需求→业务流程→功能点→功能流程→算法→数据结构」
网络系统设计线走「通信需求→协议→组件→接口→实现→框架化→组装」
两条线平行:业务管「做什么」,网络管「怎么通信」

收束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
网络系统设计线(从想法到实现的七步):

通信需求 → 协议设计 → 组件设计 → 组件接口设计
→ 组件实现 → 接口测试 → 框架化 → 系统组装

每一步的原子步骤清单:
① 通信需求:谁通信/通信什么/什么时候/延迟可靠性
② 协议设计:双方/方向/消息类型/请求响应/消息结构/字段/编码/边界/序列/关联/错误/状态/版本/扩展(18 步)
③ 组件设计:网络/连接/会话/缓冲/事件/线程/定时器/编解码/调度组件 + 职责 + 边界 + 关系
④ 组件接口设计:网络/连接/会话/缓冲/编码/解码/消息/发送/接收/事件/生命周期/停止/错误接口 + 调用关系
⑤ 组件实现:按依赖拓扑排序实现(Protocol → Buffer → Connection → Session → EventLoop)
⑥ 框架化:有状态→类封装(框架化、入口启动),无状态→函数导出(接口化、即调即用),含 15 步框架设计清单
⑦ 系统组装:入口(顶层组装者)创建网络框架+业务系统+连接 Handler,E2E 验证

顺序强制:接口依赖协议、测试依赖接口、实现依赖接口、框架化依赖实现、组装依赖框架化。

协议 ≠ 接口:协议管线上长什么样,接口管代码怎么调。
网络系统管字节,入口系统管消息,业务系统管业务。

网络系统的设计从「要解决什么通信问题」开始,到「拼成完整软件」结束——中间七步,每一步都是「先设计对外的,再设计对内的」。