从函数到网络系统——软件组织的第二条演化线
同样的演化路径——函数、文件、分层、系统——但终点不同。这次的终点是网络系统:由有状态组件 EventLoop/Connection/Session、无状态工具 Buffer、协议数据结构 Protocol 组成。组件和模块的演化逻辑相同,但领域不同。
一、一条 socket 调用
网络的起点和业务系统一样简单——一条调用:
1 2 3 4 5
| int sock = socket(AF_INET, SOCK_STREAM, 0); connect(sock, (struct sockaddr*)&addr, sizeof(addr)); send(sock, "hello", 5, 0); recv(sock, buf, sizeof(buf), 0); close(sock);
|
所有东西在一个函数里——创建、连接、发送、接收、关闭。和「所有东西写在 main 里」是同一个阶段。
二、循环处理
驱动力:重复。 要处理多次请求,需要循环:
1 2 3 4 5 6
| while (1) { int client = accept(server, ...); recv(client, buf, sizeof(buf), 0); send(client, response, len, 0); close(client); }
|
从「做一次」变成「反复做」。但处理一个连接时,其他连接在排队等待。
三、多连接
驱动力:扩展。 要同时处理多个连接:
1 2 3 4 5 6 7 8 9 10
| int clients[100]; while (1) { clients[count++] = accept(server, ...); for (int i = 0; i < count; i++) { if (有数据可读(clients[i])) { recv(clients[i], buf, sizeof(buf), 0); send(clients[i], response, len, 0); } } }
|
和「单文件 → 多文件」是同一步——职责从一个地方扩散到多个地方。
四、EventLoop
驱动力:边界。 遍历所有 socket 太慢,且「等事件」和「处理事件」混在一起——操作系统提供了高效通知机制:
1 2 3 4 5 6 7 8 9 10 11 12
| int epfd = epoll_create(1); while (1) { int n = epoll_wait(epfd, events, MAX_EVENTS, -1); for (int i = 0; i < n; i++) { if (events[i].data.fd == server) { int client = accept(server, ...); epoll_ctl(epfd, EPOLL_CTL_ADD, client, &ev); } else { recv(events[i].data.fd, buf, sizeof(buf), 0); } } }
|
这就是 EventLoop——把「遍历所有连接」变成「只处理有事的连接」。EventLoop 是网络系统的心脏,也是第一个有状态组件。
五、协议出现
驱动力:职责。 EventLoop 解决了并发,但收发的还是原始字节。需要统一的协议定义消息格式:
1 2 3 4 5
| struct Message { uint32_t length; uint32_t command; uint8_t payload[]; };
|
协议把「字节流」变成了「消息流」——调用者不再处理原始字节,而是处理结构化消息。
六、会话管理独立
驱动力:生命周期边界。 每个客户端连接需要维护自己的状态——缓冲区、协议状态、最后活跃时间。需要独立的会话管理:
1 2 3 4 5 6 7
| struct Session { int fd; Buffer recv_buf; Buffer send_buf; ProtocolState state; time_t last_active; };
|
EventLoop 只管事件分发,Session 管状态。
七、组件化
驱动力:职责。 网络系统最终长成三类东西:
1 2 3 4 5 6
| 网络系统 ├── EventLoop ← 有状态组件(事件循环,有自己的线程) ├── Connection ← 有状态组件(连接管理) ├── Session ← 有状态组件(会话管理) ├── Buffer ← 无状态工具(缓冲区) └── Protocol ← 数据结构(协议格式定义,不是组件)
|
和业务系统的「按功能分模块」是同一步——职责从一个 EventLoop 回调扩散到多个独立组件。
注意:解析层和分发层不属于网络系统——它们属于入口系统(通信程序)。
八、封装与调用:系统怎么被使用
驱动力:边界。 组件拆出来了,但还不能被直接调用——需要封装成可调用的系统。系统封装有两种:
1 2 3 4 5 6
| ① 内部封装好了框架或接口: 框架化 → 入口启动(入口创建对象、start/run、stop) 接口化 → 上层调用接口化后的接口(connect/send/close 等函数)
② 只有组件、没有封装: → 需要额外封装为框架(类)或接口(函数导出),才能被调用
|
框架化是入口启动,接口化是上层调用接口——系统封装成哪种形态,决定它被怎么使用。
收束:从函数到网络系统
1 2 3 4 5 6 7 8
| 一条 socket 调用 最简单的网络程序 循环处理 从一次到反复 多连接 同时处理多个客户端 EventLoop 高效事件通知(第一个有状态组件) 协议 字节流 → 消息流 会话管理 每个连接的状态独立 组件化 3 有状态组件 + 1 工具 + 1 数据结构,各有职责边界 封装与调用 框架化(入口启动)/ 接口化(上层调用接口)
|
每一步都是上一步「重复 / 扩展 / 边界 / 职责」四种驱动力之一逼出来的——和业务系统的演化是同一种逻辑,只是领域不同。
下一篇看第三条演化线:业务系统 + 网络系统 + 入口系统如何组合成完整的网络通信软件。