传输层
| 项目 | 值 |
|---|---|
| 传输方式 | TCP(持久连接) |
| 默认端口 | 5225 |
| Socket 选项 | 启用 TCP_NODELAY —— 立即发送小数据包,不受 Nagle 合并延迟影响 |
| TLS | 可选 —— 如果服务器配置了证书,整条连接会被包裹在单向 TLS(服务器认证)中 |
| 编码 | 定宽二进制字段 + UTF-8 key。整数采用大端序(网络字节序) |
| 帧终止符 | 一个 \n 字节。不处理 \r |
| 空帧 | 单独的 \n(op 位置为 \n)会被静默忽略,视为保活(keep-alive) |
| 最大帧大小 | 192字节——包含op,不含\n(超限 → E line_too_long后关闭连接) |
| 流水线 (Pipelining) | 允许 —— 响应到达顺序可能与请求顺序不同 |
| 空闲超时 | 无 —— 服务器不会主动关闭静默连接 |
| 帧进度时间 | 从收到op首个byte到fixed header与LF最多5秒;部分发送不能无限占用connection |
连接建立顺序
- TCP 连接
- 如果服务器启用了 TLS,则进行 TLS 握手 —— 以明文连接启用了 TLS 的服务器会在 此步骤断开
- 认证握手,仅执行一次
- 此后开始交换锁协议帧
分帧规则
每条消息就是一帧 = 一行。
[ op: 1 字节 ][ op 专属定长头部 ][ 变长正文 ] \n接收方必须先查看 op,按其字节数先读取定长头部,之后才扫描 \n。 这是因为定长头部是二进制数据,可能包含值恰好为 0x0A(\n)的字节 (例如 lease=10 → 0A)。\n 扫描仅适用于末尾的 key/文本部分。
| 方向 | op | 定长头部 |
|---|---|---|
| 客户端 → 服务器 | A | 10 bytes (wait:u8 + lease:u8 + owner:u64 BE) |
| 客户端 → 服务器 | R | 8 bytes (token:u64 BE) |
| 服务器 → 客户端 | A | 16 bytes (token:u64 BE + owner:u64 BE) |
| 服务器 → 客户端 | T · B | 8 bytes (owner:u64 BE) |
| 服务器 → 客户端 | R · N | 8 bytes (token:u64 BE) |
| 服务器 → 客户端 | M · L · E | 0 bytes (address/reason UTF-8 text) |
frame超过192 bytes或malformed时,服务器尽可能flush E后关闭连接。客户端也把malformed、oversized、未知op或无法关联的E视为connection-fatal。该session中已发送至少一个byte的acquire不自动重试;若无确定响应则为Indeterminate。
术语
- 帧 (Frame) —— 将连续的字节流(TCP)切分成"一条消息"的单位。在本协议中, 一个以
\n结尾的行就是一帧。 - 大端序 (Big-endian) —— 先写入整数最高有效字节的字节序。这是网络协议的 标准做法,因此也称为网络字节序。
- 流水线 (Pipelining) —— 无需等待上一个响应即可发送下一个请求。通过这种 方式重叠往返延迟可以提升吞吐量。
- 保活 (Keep-alive) —— 周期性发送的、用于宣告连接仍然存活的无意义信号。 在这里,空帧(
\n)承担了这一角色。