1.9 搞清楚Socket
沿 socket 端点、绑定监听、连接建立、字节收发和关闭回收追踪文件描述符与连接状态。
学习目标
- 能沿创建端点、绑定监听、建立连接、收发字节和关闭回收追踪 socket 生命周期
- 能区分监听 socket、accept 返回的已连接 socket、文件描述符和连接五元组
- 能在积压、短读、对端关闭和描述符泄漏场景中定位首个状态偏离并重放
1.9 搞清楚Socket
本页依据刘欣《码农翻身》(2018 年第 1 版)及出版社公开书目信息,独立重构 1.9 搞清楚Socket。正文、代码、图示、实验和练习都是本课程重新设计的教学材料,不复制原书正文、插图、练习答案或代码。
socket 是进程进入网络协议栈的端点接口,不是“网络上的一根管子”。服务器通常创建 socket、bind 地址、listen、accept 连接;客户端创建 socket、connect,然后双方通过已连接端点读写字节。监听端点和每个已连接端点有不同的状态、文件描述符和回收责任。
三个会让 socket 模型失真的陷阱
socket 端点合同
↡本页把网络故事重构为由文件描述符承接地址、端口、协议状态和字节流的进程网络端点;监听端点与已连接端点有不同生命周期。不是把 socket 拟人化,而是要求每个 fd 都能回答它属于哪个进程、处于什么状态、关联哪个五元组、能读写什么以及由谁关闭。
连接身份可写成:
五元组区分连接身份,但不替代 fd 生命周期。监听 socket 可以对应多个 accept 返回的连接 fd;同一连接的读写也可能因短读、缓冲和关闭方向进入多个状态。
五个节点到机制证据
创建端点
↡为进程创建 socket 对象和文件描述符,并准备协议族、类型与协议参数的阶段。产生一个尚未绑定或连接的 fd。记录进程、fd、地址族、类型和错误路径;创建失败时确认部分资源没有留在表中。
绑定监听
↡把本地地址和端口绑定到 socket,并把端点置于接收连接请求的监听状态的阶段。建立本地身份和 backlog 边界。监听 fd 不承载某个客户端的应用字节;地址占用、队列容量和重启策略都应有证据。
建立连接
↡客户端 connect 与服务器 accept 配合,把连接请求转成带有独立 fd 和五元组的已连接端点。产生新的连接状态。记录监听 fd、连接 fd、对端地址、握手结果、队列出入和所属线程;多个客户端不能共享一个模糊的“连接成功”标记。
收发字节
↡通过已连接 socket 读写有序字节流,并由应用层协议恢复消息边界和处理短读短写的阶段。不保证一次调用完成一条消息。保存调用长度、实际处理长度、缓冲、应用帧和返回值,区分阻塞、暂时无数据与 EOF。
关闭连接
↡按关闭方向、未完成字节、EOF、等待者和最后引用结束 socket 生命周期并释放文件描述符的阶段。不是一个瞬间的布尔开关。记录 half-close、对端 EOF、错误、线程引用和释放顺序,避免 fd 泄漏或过早复用。
最小可重放实现
listener = socket()
bind(listener, address)
listen(listener, backlog=2)
connection = accept(listener)
write_all(connection, framed_message)
read_frame(connection)
close(connection)
close(listener)
assert reset() == empty_socket_table这段草图只表达端点生命周期合同,不复制书中叙事或代码。实际复核应保存进程、fd、状态、五元组、队列、每次读写长度、EOF、关闭方向和最终 fd 表。
五步复核 socket 生命周期
1. 创建端点并固定 fd 所有者
记录进程、fd、地址族、类型和初始状态。先预测创建失败、重复关闭和资源回收时的 fd 表。
Lab
fd、队列与关闭实验
只改变一个队列或回收条件,观察监听端点、连接 fd 和字节状态。
监听 fd 接收连接,accept 产生独立连接 fd
listen fd=3 → accept fd=4, tuple A → framed read/write → close fd=4
判定
通过:监听、连接和回收台账闭合
当前场景:基线连接;记录监听 fd、连接 fd、五元组、实际字节、EOF 和引用回收。
正常、边界与故障证据
| 样本 | 只改变的变量 | 预期判定 | 必存证据 |
|---|---|---|---|
| 正常 | 地址可用、队列有空间、消息完整关闭 | fd、五元组、字节与回收闭合 | fd 表、队列、五元组、读写和关闭记录 |
| 边界 | backlog、短读、缓冲容量或半关闭 | 在对应边界等待或分段,不丢失所有权 | 阈值、实际长度、EOF、剩余缓冲和引用 |
| 故障 | 一次 accept、读写、关闭或回收动作失败 | 首个状态拒绝可定位,修复后可重放 | 首差、fd、返回值、所有者、复位结果 |
故障诊断:先找 fd 状态的首个矛盾
- 核对对象:确认当前 fd 是监听端点还是已连接端点,查看进程所有者、地址族、类型和状态。
- 核对身份:比较五元组、监听队列、connect/accept 事件和连接 fd,排除多个客户端被压成一条记录。
- 核对字节边界:按调用顺序排列请求长度、实际长度、缓冲、短读、EOF 和应用帧,找首个不满足协议的返回值。
- 核对回收闭合:检查 shutdown/close、等待者、未发送数据、引用计数和 fd 复用;修复后从空 fd 表重放。
术语表
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 1.9 搞清楚Socket
由文件描述符承接地址、端口、协议状态和字节流的进程网络端点模型。
- 创建端点
为进程创建 socket 对象和文件描述符,并准备协议参数的阶段。
- 绑定监听
绑定本地地址和端口并进入接收连接请求状态的阶段。
- 建立连接
通过 connect 与 accept 产生带独立 fd 和五元组的已连接端点的阶段。
- 收发字节
通过已连接 socket 读写字节流并由应用协议恢复消息边界的阶段。
- 关闭连接
按关闭方向、EOF、等待者和最后引用结束生命周期并释放 fd 的阶段。
练习
练习
问题 1: 服务器调用 accept 后,监听 socket 是否变成了客户端连接 socket?
问题 2: 一次 write 发送 100 字节,接收端 read 只得到 40 字节,最应该怎样处理?
问题 3: 本地 close 成功但进程的 fd 表仍在增长,应该先查什么?
本页小结
1.9 搞清楚Socket的关键不是记住一串调用名,而是能说明每个 fd 的状态、所有者、五元组、字节边界和关闭方向。完成标准是从创建端点重放到回收,在积压、短读或对端关闭中定位首个矛盾,并证明复位后监听、连接和 fd 台账重新闭合。