网络系统设计——从想法到实现
业务设计线走「需求→业务流程→功能点→功能流程→算法→数据结构」(做什么),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
| void start(); void stop(); void run_once(int timeout_ms);
bool connect(const Address& addr); void close(); void send(const Buffer& data); void set_callback(ICallback* cb);
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 验证
顺序强制:接口依赖协议、测试依赖接口、实现依赖接口、框架化依赖实现、组装依赖框架化。
协议 ≠ 接口:协议管线上长什么样,接口管代码怎么调。 网络系统管字节,入口系统管消息,业务系统管业务。
|
网络系统的设计从「要解决什么通信问题」开始,到「拼成完整软件」结束——中间七步,每一步都是「先设计对外的,再设计对内的」。