从函数到网络系统——软件组织的第二条演化线

同样的演化路径——函数、文件、分层、系统——但终点不同。这次的终点是网络系统:由有状态组件 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 数据结构,各有职责边界
封装与调用 框架化(入口启动)/ 接口化(上层调用接口)

每一步都是上一步「重复 / 扩展 / 边界 / 职责」四种驱动力之一逼出来的——和业务系统的演化是同一种逻辑,只是领域不同。

下一篇看第三条演化线:业务系统 + 网络系统 + 入口系统如何组合成完整的网络通信软件。