知其然,知其所以然
网络协议原理 2026-10-15

SACK:选择性确认让重传更精确

丢包下接收方的 ACK 带 SACK 块(例子:累积确认号 212317,块 [228649, 244981) 长 16332 字节),72 / 72 条在累积确认号之上;3 个洞发送方 3 / 3 从洞的开头重传整段,没有重发洞之后已到的数据;DSACK 这次没有出现。

网络协议 TCP SACK 选择性确认 DSACK 丢包恢复 真实验证 动画演示
网络协议原理 2026-10-15

滑动窗口与流量控制:接收窗口通告

接收方不读时窗口从 342,976 缩到 15,360 字节、发送方停发,send() 一共收下约接收缓冲区加发送缓冲区(474,460 字节);读速决定发送速率;SO_RCVBUF 小于约 256 KiB 的设置在 macOS 上不生效,吞吐 ≈ 窗口 ÷ RTT;窗口更新一次从 7,688 变成 269,000 字节。

网络协议 TCP 滑动窗口 流量控制 接收窗口 窗口更新 真实验证 动画演示
网络协议原理 2026-10-15

零窗口与窗口探测:接收方处理不过来时

接收窗口缩到 976 字节(小于一次写入)后发送方停发,窗口为 0 后每 5.0 秒发一个 1 字节的探测包(固定间隔,不设上限);接收方一读数据,窗口更新 ACK 0.21 ms 就发出,被阻塞的 send() 在 1.3 ms 内返回。

网络协议 TCP 零窗口 窗口探测 persist 窗口更新 真实验证 动画演示
网络协议原理 2026-10-15

窗口缩放选项:为什么 64 KiB 不够

窗口缩放把 16 位窗口(最大 65535)扩到 2^s 倍:本机 s = 6(最大 4,194,240),example.com s = 13;接收缓冲区 autotune 在约 1 秒内把窗口放大到 4 MiB 上限,往返 100 ms 的吞吐稳定在约 41.8 MB/s ≈ 窗口 ÷ RTT;不缩放时到 example.com 只有 0.27 MB/s。

网络协议 TCP 窗口缩放 接收缓冲区 autotune BDP 真实验证 动画演示
网络协议原理 2026-10-15

拥塞控制 1:慢启动与拥塞避免

初始拥塞窗口 10 个 MSS;慢启动每个 RTT 约翻倍(10 → 190 个 MSS);超时重传使 cwnd 掉到 1 个 MSS,快速恢复降后 / 降前中位数 0.70(CUBIC),拥塞避免阶段每 RTT 只涨 0.4~0.8 个 MSS。

网络协议 TCP 拥塞控制 慢启动 拥塞避免 cwnd CUBIC 真实验证 动画演示
网络协议原理 2026-10-15

拥塞控制 2:Reno、CUBIC、BBR 的行为差异

本机默认 CUBIC(macOS 没有按套接字切换算法的接口);实测 CUBIC 的恢复曲线与公式 W(t) = 0.4·(t − K)³ + W_max 吻合(中位误差 6%);在 100 ms 往返、0.1%~3% 丢包下吞吐是 Reno 公式预测的 1.04~2.73 倍;Reno / CUBIC / BBR 的行为差异用玩具模拟说明思路。

网络协议 TCP 拥塞控制 CUBIC Reno BBR Mathis公式 真实验证 动画演示
网络协议原理 2026-10-15

Nagle 算法与延迟 ACK:40 ms 的由来

实测 Nagle 与延迟 ACK:本机 delayed_ack=3,回环上孤立小包 0.1 ms 就回 ACK,经典 40 ms 冲突没复现;但对 example.com 把 HTTP 请求分两次 write,Nagle 开 403 ms 对 TCP_NODELAY 201 ms,差约一个 RTT;2 万次 100 字节小写入 Nagle 合并成 908 段,NODELAY 是 20000 段。

网络协议 TCP Nagle 延迟ACK TCP_NODELAY 真实验证 动画演示
网络协议原理 2026-10-15

TCP_NODELAY、TCP_CORK 与小包合并

实测 macOS 上 TCP_NODELAY 与 TCP_NOPUSH:读回值是内部标志(4 与 4096)而非 1;分两次 write 的消息 400 个段,拼接或 sendmsg 一次发只要 200 个;NOPUSH 开着 300 字节一个段都不发,拔塞后也不发,要再写 1 字节才以 301 字节一个段冲出。

网络协议 TCP TCP_NODELAY TCP_NOPUSH TCP_CORK sendmsg 小包合并 真实验证 动画演示
网络协议原理 2026-10-15

TCP keepalive:探测空闲连接是否还活着

实测 TCP keepalive:macOS 默认 2 小时才探测(最长 7800 秒才判死,公式);探测包是 0 字节、序号减 1 的纯 ACK;idle=2、intvl=1、cnt=3 的连接在 50% 丢包下最短 5.0 秒判死(ETIMEDOUT);对 example.com 的探测 3 个 3 个回应。

网络协议 TCP keepalive SO_KEEPALIVE TCP_KEEPALIVE 死连接检测 真实验证 动画演示
网络协议原理 2026-10-15

半开连接:对端消失时本端多久才知道

实测半开连接:进程被 kill -9 内核会发 FIN,本端立刻知道;SO_LINGER(1,0) 发 RST;在 50% 丢包下关闭 40 个连接,18 个客户端毫无察觉成为半开,发数据后 14 个收到 ECONNRESET;connect 到黑洞 75.0 秒后 ETIMEDOUT。

网络协议 TCP 半开连接 RST SO_LINGER keepalive 真实验证 动画演示
网络协议原理 2026-10-15

同时打开与自连接:两端同时 connect 会怎样

实测 TCP 同时打开与自连接:macOS 拒绝自连接(四种写法都是 EINVAL,线上 0 包);两个套接字互相 connect 在 150 ms 延迟下 SYN、SYN+ACK、ACK 各双向一个,中途都是 SYN_RCVD;向临时端口连 60000 次,4 次因源端口等于目标端口被 EINVAL 拒绝。

网络协议 TCP 同时打开 自连接 EINVAL SYN_RCVD 真实验证 动画演示
网络协议原理 2026-10-15

SO_REUSEADDR 与 SO_REUSEPORT:端口复用的两种含义

实测 macOS 上 SO_REUSEADDR 与 SO_REUSEPORT:REUSEADDR 绕过 TIME_WAIT 但不能让两个监听者共存;共存要两边都设 REUSEPORT;但 4 个监听者 400 个连接全部给了最后绑定的一个,UDP 也一样;多播要 REUSEADDR+REUSEPORT 或绑组地址才能多个套接字都收到。

网络协议 TCP UDP SO_REUSEADDR SO_REUSEPORT EADDRINUSE TIME_WAIT 真实验证 动画演示
京ICP备2026067502号-1京公网安备11010502063487号