客户端发送 SYN,序列号为 x;服务器发送 SYN-ACK,序列号为 y、确认号为 x+1;客户端发送 ACK,序列号为 x+1、确认号为 y+1。

TCP 握手建立序列号空间,不是同步两台机器的时钟

TCP 三次握手建立字节流的两个方向。每端提供自己的初始序列号,并确认另一端的序列号。这是两个独立序列空间,不是共享墙上时钟;应用建立连接不需要两台机器时钟同步。之后的序列号用于标识字节,支持连接内部的排序、重复处理、确认和重传。把序列号与时间混为一谈,会误解握手真正建立的协议状态。 图示普通握手及受控丢包场景;32 位序号按模回绕。connect 成功不等于应用已能提供有效服务。 坏掉的样子 客户端发送带初始序列号的 SYN,服务器回复 SYN-ACK,给出自己的初始序列号并确认客户端 SYN,客户端再确认服务器响应。即使没有应用数据,SYN 也占用序列空间中的一个位置,所以对应确认号增加一。将这些数值当成时间戳,会错误解释报文,还可能把排障引向无关的时钟同步问题,忽略真正缺失的确认或路由。 两端不会在完全相同的时刻改变状态。客户端收到 SYN-ACK 后,connect 可能成功返回,但最后一个确认在到达服务器前丢失。服务器仍可能停留在 SYN-RECEIVED,Linux 工具显示为 SYN-RECV。后续携带适当确认的数据也能完成交换,因此单独第三个报文缺失,并不必然导致永久失败,不能把抓包中的固定三包模式当成唯一合法过程。 TCP 建立成功也不代表应用健康或对端身份可信。服务可能接受连接却无法生成有效响应,代理可能完成客户端这一侧连接,却无法建立另一个独立上游连接。初始序列号选择有助于区分连接实例、抵御预测攻击,但不是身份认证协议,也不会加密字节流。传输状态、应用结果和安全身份需要各自对应的验证,不能由一项成功代替全部。 快速验证 将脚本保存为 check-tcp-handshake.py,准备 Docker 后运行 python3 check-tcp-handshake.py。一次性容器使用 Python 3.11.16、iproute2 6.1.0、nftables 1.0.6 和 tcpdump 4.99.3,在 Linux 6.12.76 上验证。NET_ADMIN 与 SYS_ADMIN 支持嵌套网络命名空间。客户端和目标由新建 veth 相连,不移动宿主接口或发布端口: ...

九月 10, 2027 · 3 分钟 · XB Field Notes
原文 HTTP/1.1 实验中,/one、/two、/close 共用一个套接字,/four 使用新套接字。四次响应均为 200,Content-Length: 2,正文 ok 均在下一次请求前完整读完;/close 的 Connection: close 让后续请求重新建连。这是本地顺序实验的实测结果,不是 HTTP/2 或端到端代理证据。

HTTP keep-alive:复用同一条连接

HTTP 持久连接允许多次请求与响应复用同一条连接,减少重复的 TCP 建连;使用 HTTPS 时,也可能省去新的 TLS 协商。收益取决于网络延迟、响应大小、服务端处理时间以及池中是否已经有可用连接。连接复用不会消除业务计算,也没有承诺某个套接字永远保持打开。讨论优化之前,先明确观察的是哪一段连接、哪个协议版本。 原文 HTTP/1.1 实验中,/one、/two、/close 共用一个套接字,/four 使用新套接字。四次响应均为 200,Content-Length: 2,正文 ok 均在下一次请求前完整读完;/close 的 Connection: close 让后续请求重新建连。这是本地顺序实验的实测结果,不是 HTTP/2 或端到端代理证据。 原样实验已于 2026-10-17 在 Python 3.14.4 上复验。 完整报文才允许复用 HTTP/1.1 默认允许持久连接,除非某一方发送 Connection: close,或实际关闭传输连接。通常不必额外添加 Connection: keep-alive。客户端连接池可以保留空闲连接,供符合源、代理、证书和池策略的后续请求使用。连续执行两次独立的命令行程序,通常不会共享内存中的连接池,因而不能凭两次响应成功就断言已经复用。 ...

五月 28, 2027 · 1 分钟 · XB Field Notes
仅作用于 TCP 的 MSS 限制,减小握手通告以适合包预算。TCP 可以恢复,但因 MTU 反馈仍被过滤,超大非 TCP 流量仍可能失败。

调整 MSS 可以恢复一条 TCP 路径,却不会修复 MTU 反馈

TCP 最大段大小选项 MSS,按照其定义的开销规则,通告接收端可接受的 TCP 数据大小。两个方向分别通告,数值不必相同。路由器转发 SYN 消息时可以减小这些值,使后续数据段适应受限路径。这改变的是相关连接的 TCP 行为,不会修改底层链路 MTU。把选项调整与网络本身修复区分开,才能准确说明它解决了什么。 限制只改变新连接的 TCP 通告,不改变链路 MTU 或缺失的 ICMP 反馈。应保留对端较小上限,并分别验证两个方向。 坏掉的样子 中间瓶颈可能比两个端点接口都小。如果经典路径 MTU 反馈被过滤,连接可以成功建立,却在较大数据上停滞。降低 MSS 能帮助该 TCP 流量避开超大报文,但缺失的反馈仍然缺失。若把这个变通措施当作完整网络修复,其他协议与流量模式仍可能暴露原有问题,健康检查也可能继续遗漏受影响场景。 ...

一月 24, 2027 · 4 分钟 · XB Field Notes
路径上三段链路的 MTU 依次为 1500、1280 和 1500 字节。中间链路决定路径 MTU;小交互成功并不能验证大传输。

路径 MTU 发现失效时,握手和小请求仍可能成功

路径 MTU 是某条具体路由上各链路 MTU 的最小值。发送端不能只根据自身接口确定它,因为中间链路可能只能承载更小的数据包。经典 IPv4 路径 MTU 发现设置禁止分片标志,通过 ICMP“需要分片”反馈降低估计包大小。路由能够到达目的地,与发送大小适合整条路径,是两项不同条件,前者成立不能自动证明后者。 最小链路决定包大小是否适合。应结合反馈、全新路由状态、有效 MSS,以及原故障方向上有代表性的应用交付。 坏掉的样子 隧道或其他受限中间链路,可能使大传输失败,而 TCP 握手与小请求仍然成功。小报文能够通过瓶颈,因此这些成功没有验证完整大小的数据。反复只测短 ping 或微小健康响应,就可能漏掉影响上传、大响应或持续传输的路径 MTU 问题。应让检查包含真实负载中会触发限制的大小,避免健康探针与实际业务长期脱节。 过滤必要 ICMP 反馈,会让过大报文进入黑洞。发送端观察到没有进展,却缺少指出更小 MTU 的经典信号。其他探测机制可能提供帮助,取决于传输协议和配置,因此不能断言所有 TCP 实现遇到这种过滤都会永久卡住。作出结论前,应查明实际启用了哪一种发现或黑洞恢复机制,不能把某个实验控制条件省略后推广到所有环境。 路径信息还可能被缓存。成功发现之后进行的测试,即使防火墙改变,也可能与全新客户端表现不同。复用已有状态会隐藏反馈路径损坏。路由变化和非对称返回路径进一步增加复杂性,所以需要记录方向、来源、目的地,以及估计是在什么条件下学到的。配置变化前后的结果,只有在这些前提清楚时才适合直接比较。 快速验证 以下命名空间、抓包与套接字集成原样保留,本轮未重跑;观察属于历史记录,离线检查不能复现网络结果。 历史报告使用 Linux 6.12.76、Python 3.11.16、iproute2 6.1.0、nftables 1.0.6 与 tcpdump 4.99.3。脚本名为 check-path-mtu.py,只应留给另外获得授权的可丢弃 Linux 虚拟机。Docker 共享其内核,NET_ADMIN、SYS_ADMIN、proc 挂载和命名空间参数需要这一隔离边界。后续运行的 --network none 不阻止构建下载软件包。保持断言开启,设置失败时不要放宽宿主安全策略。新建 veth、地址与路由仅应存在于实验内部: ...

十一月 14, 2026 · 3 分钟 · XB Field Notes
两个连接四元组共用本地地址 192.0.2.1 和端口 40200,但远端端口分别为 18080 和 18081。复用取决于套接字与内核规则。

TIME-WAIT 保护连接实例,不是独占一个端口号

TIME-WAIT 是 TCP 在某些有序关闭之后继续保留的协议状态。它为必要时重发最后确认、完成关闭交换留出时间,也帮助避免早先连接实例的延迟报文被误认成新连接数据。相关身份包含两端地址和端口,不能把一个 TIME-WAIT 条目简单理解成对某个本地端口号的全局锁。连接四元组和单独端口,是不同范围的概念。 TIME-WAIT 保护连接实例。判断身份要看四元组,复用结果取决于套接字路径与内核条件。 坏掉的样子 普通关闭中,首先发起关闭的一端通常在确认对端 FIN 后进入 TIME-WAIT。这一端可能是客户端,也可能是服务器;同时关闭还可能涉及双方。假定 TIME-WAIT 永远属于服务器,会掩盖真正制造连接周转的工作负载。应查看状态及两端四元组,不能从应用角色推断谁先关闭,也不能把客户端保留该状态直接视为异常。 高频连接周转可能限制到某个目的地的新连接,但 TIME-WAIT 数量很大,本身不证明资源耗尽或应用文件描述符泄漏。应用关闭套接字之后,操作系统仍可保留协议状态。源地址、可用端口、目的地多样性、显式绑定、复用选项与内核行为,都会影响实际限制。诊断资源耗尽之前,应同时测量连接失败与状态分布,而不是只盯一个总数。 混淆 TIME-WAIT 和 FIN-WAIT-2 会导致无效调参。Linux 的 tcp_fin_timeout 控制另一种孤立套接字情形,不是通用的 TIME-WAIT 时长开关。关于 tcp_tw_recycle 的旧建议,也不适用于当前 Linux。复用受到协议安全条件约束,其中包含时间戳相关检查;如果不了解真实套接字路径,仅修改一个参数,就可能产生错误的行为预期。 ...

十一月 13, 2026 · 2 分钟 · XB Field Notes
拥塞窗口与接收窗口分别约束未确认数据;另一条 pacing 时间线显示发送间隔。每个发送端选择自己的拥塞策略。

TCP 拥塞控制由每个发送端决定,两端可以使用不同算法

TCP 拥塞控制限制发送端向容量有限、可能被共享的网络注入数据。拥塞窗口约束未确认数据量,采用 pacing 时还会控制发送在时间上的分布。接收端通告窗口施加的是另一类流量控制限制。这些机制相互作用,却描述不同约束,不能笼统合并成一个“发送速度设置”,否则很难判断真正限制当前连接的是哪一部分。 数据量、发送时序与应用供给是不同约束。回读套接字实际控制器,并结合该算法解释反馈。图中文章报告值未在本轮重现。 坏掉的样子 两端不需要选择完全相同的拥塞控制算法。每端负责控制自己发送的流量,因此 Reno 客户端能够与 CUBIC 服务器通信。TCP 建立过程协商传输选项,没有要求双方发送算法名称一致。把不同控制器名称诊断成协议不匹配,会分散注意力,掩盖真正的路由、缓冲或应用故障。算法选择需要按方向和具体套接字理解。 算法是否可用,也是需要核对的运行时事实。配置字符串写了某个控制器,不证明内核支持它或允许调用进程选择它。Linux 的 TCP_CONGESTION 按套接字设置,受可用和允许算法约束。应检查调用结果,并回读实际名称,不能默认请求标签已生效。选择失败与选择成功后性能不合适,属于不同问题,需要不同处理。 丢失恢复不存在适用于所有情况的统一曲线。重传超时、快速恢复、显式拥塞通知和基于模型的估计,会因控制器不同而产生不同响应。丢失可能来自拥塞,也可能来自其他原因,发送端仍须按算法规则作出反应。增加缓冲并不能自动修复持续丢包或等待确认的连接,也不能替代对具体恢复状态和触发信号的检查。 快速验证 将脚本保存为 check-congestion-control.py,准备 Docker 后运行 python3 check-congestion-control.py。一次性环境使用 Python 3.11.16、iproute2 6.1.0 和 nftables 1.0.6,在 Linux 6.12.76 上验证。NET_ADMIN 与 SYS_ADMIN 支持嵌套命名空间。新建 veth 两个方向出口各增加十毫秒延迟,不修改宿主接口或生产流量: ...

十一月 12, 2026 · 2 分钟 · XB Field Notes
原文前三次请求复用连接一;服务端关闭、显式空闲回收与禁用复用随后产生连接二至五。

HTTP/1.1 默认允许持久连接

持久连接允许多次请求和响应共享同一条连接,减少重复的 TCP 建立和 TLS 协商。但这是协议提供的能力,不是某个套接字永远可用的承诺。下一次请求能否真正复用,还取决于消息边界是否完整、客户端连接池策略、服务端行为和连接当前状态;单看请求成功或一次响应较快,都不足以证明走了复用路径。 默认允许,不代表永不关闭 HTTP/1.1 默认允许持久连接,客户端通常不需要发送 Connection: keep-alive 才能启用它。Connection: close 则表示当前交换之后不再继续使用这条连接。HTTP/1.0 的持久连接规则不同,不能把旧版本的协商方式复制到所有现代请求里,也不能把“没有 keep-alive 字段”直接判成配置错误。 复用首先需要明确的消息结束位置。准确的 Content-Length、分块编码,或无正文响应规则,都可以界定边界。若响应只能依靠关闭连接来表示结束,自然无法留下这条连接继续使用。接收方必须先消费完当前消息,再把后续字节解释成下一条;只收到 HTTP 200 状态,还不能证明响应正文已经完整到达。 已建立的 HTTPS 连接被复用时,不会重新进行 TLS 握手;使用缓存的 TLS 会话建立另一条连接,则仍然需要握手。TCP keepalive 又是另一回事,它的空闲探测不会刷新 HTTP 消息边界,也不能否定应用主动关闭连接的决定。HTTP/2 同样可以复用连接,但并不使用 HTTP/1.x 的 Connection 字段语义。 分别管理连接池与一次操作 空闲套接字仍占用描述符、缓冲区和实现相关的状态。Go 的 MaxIdleConnsPerHost 控制每个主机保留多少空闲连接,MaxConnsPerHost 则同时限制正在拨号、活动和空闲的连接总数。前者不是总并发上限。可复用连接池由共享的 Transport 持有,连续调用若各自建立无关的 transport,就会丢掉原本可以复用的连接资源。 ...

十月 24, 2026 · 3 分钟 · XB Field Notes