
上一篇说TCP 会先建立连接再为应用提供可靠、有序的字节流。“建立连接”听起来像接通电话但网络里没有一根从电脑拉到服务器的专用线。所谓连接是通信双方各自记录了这次通信的状态对方是谁、接下来期望收到哪些字节、已经确认了哪些字节。建立连接时双方需要先交换信息把这些记录对上。三次握手做的就是这件事。先认识三个标记TCP 报文里有一些控制标记。今天只需要认出三个SYN我想建立连接并告诉你我从哪个序号开始。ACK我收到了你发来的内容并告诉你下一步期望收到哪个序号。FIN我这边没有更多数据要发送了。TCP 给传输中的字节编号才能知道哪些内容到了、哪些需要重传。连接刚建立时双方还要各自选一个起始序号。下面用小数字演示真实序号不一定从 1 开始。假设客户端选100服务器选500。三次握手实际交换了什么客户端 服务器 │ │ │── SYN序号100 ─────────────────────────────→│ ① │ │ │←─ SYN ACK序号500确认号101 ───────────│ ② │ │ │── ACK确认号501 ───────────────────────────→│ ③ │ │第一次客户端发 SYN。客户端说“我想建立连接我的起始序号是 100。”第二次服务器回 SYN ACK。服务器收到后一方面确认客户端的 SYN另一方面报出自己的起始序号 500。确认号写101意思是“你的 100 我收到了接下来期望 101。”第三次客户端回 ACK。客户端收到服务器的 SYN回一个确认号501“你的 500 我也收到了接下来期望 501。”这里的1不是随手加的。SYN 虽然不装载普通应用数据但它本身会占用一个 TCP 序号。双方都要确认对方的 SYN。经过这三步客户端知道服务器收到了自己的起始信息也收到了服务器的起始信息服务器收到最后一个 ACK 后也知道客户端收到了自己的起始信息。双方才有了继续传送字节所需的共同起点。为什么两次不够假设只进行前两次客户端 → 服务器SYN 客户端 ← 服务器SYN ACK客户端已经收到服务器的回复知道服务器听见了自己。但服务器还不知道自己的 SYN ACK 有没有到达客户端如果这份回复在路上丢了服务器单凭“我发出去了”不能认为对方已经收到。第三次 ACK 正是客户端对服务器起始信息的确认。这比“握三次手是为了证明双方都有发送和接收能力”的说法更准确。TCP 需要同步的是双方的起始序号和连接状态。网络后来仍可能断开握手成功只说明当时完成了这轮交换不保证以后每个请求都成功。例如访问 HTTPS 网站时TCP 握手成功后还要进行 TLS 握手然后才谈 HTTP 请求。netstat里看见 TCP 已建立不等于网页已经加载成功。连接建好后双方都能发送数据TCP 连接是双向的。客户端可以发 HTTP 请求服务器可以回响应双方的发送方向各有自己的序号和确认进度。这一点决定了关闭连接时不能只说一句“结束”就完事。一方不再发送并不意味着另一方也已经发送完。假设客户端先决定关闭自己的发送方向常见过程是客户端 服务器 │ │ │── FIN ───────────────────────────────────────→│ ① 我发完了 │←─ ACK ────────────────────────────────────────│ ② 收到 │ │ │ 服务器仍可发送尚未发完的数据 │ │ │ │←─ FIN ─────────────────────────────────────────│ ③ 我也发完了 │── ACK ────────────────────────────────────────→│ ④ 收到第一次 FIN 表示客户端“我没有更多数据要发”服务器确认后服务器的发送方向还可以继续工作。等服务器也发完了才发送自己的 FIN。客户端再确认。这就是大家常说的“四次挥手”。但不要把“四”当成每次抓包都必须看到的固定数量。如果服务器收到客户端 FIN 时恰好也准备关闭它可以把对第一个 FIN 的 ACK 与自己的 FIN 放在同一个报文中。谁先发起关闭也不一定总是客户端。FIN 表示正常地结束一个发送方向。另一个常见标记RST则表示连接被重置或中止和双方有序地用 FIN 关闭不是一回事。为什么已经回了最后一个 ACK还可能看到TIME_WAIT你在电脑上查看 TCP 状态时可能会看到不少TIME_WAIT。初看像是连接“关不掉”其实它常是正常关闭过程的一部分。想想刚才最后一步客户端给服务器的 FIN 回了 ACK。如果这个 ACK 丢了服务器可能重发 FIN。客户端若立刻忘掉这条连接就没法按原连接正确回应这次重发。因此发送最后确认的一方通常还会保留一段时间的连接状态这就是TIME_WAIT的一个作用。它也有助于避免旧连接里迟到的报文干扰后来使用相同地址与端口的连接。看到少量或短时间存在的TIME_WAIT不必直接判定网络故障。另一个状态CLOSE_WAIT则表示本端已经收到对方的 FIN但本端应用还没有完成关闭。状态名字说的是连接走到哪一步不能只看名字里的WAIT就判断谁出了问题。抓包看看别只背那张箭头图如果电脑装有 Wireshark可以用一次新连接验证握手。选择正在使用的网卡开始抓包。在命令提示符中访问一个可打开的 HTTPS 网站例如curl.exe --http1.1 -H Connection: close https://example.com/回到 Wireshark在显示过滤器中输入tcp.flags.syn 1 tcp.flags.ack 0这会帮助你找到连接开始时客户端发出的 SYN。选中其中一条与你刚才访问相符的记录查看它所属的 TCP 流再观察同一条流里后续的SYN, ACK和ACK。Wireshark 通常可以通过右键菜单中的“追踪 TCP 流”帮你筛出同一条连接。留意两件事SYN 的来源端口通常是本机临时端口目标端口可能是443回复方向则相反。如果你通过代理上网抓到的直接通信对象可能是代理而不是网站服务器。浏览器若使用 HTTP/3也不会出现对应的 TCP 握手所以这里用curl.exe --http1.1做练习。继续往后看也许能看到 FIN 和 ACK。不过网站或中间设备如何关闭连接、抓包从何时开始都会影响你看到的结果。实验的目的不是凑齐固定的七个报文而是认出双方怎样确认连接开始又怎样分别结束发送。三次握手解决了双方从哪里开始计数的问题正常关闭让双方各自说清“我发完了”。下一篇要处理连接中间最棘手的事一段数据丢了、晚到了或者接收方来不及处理TCP 怎么办参考资料TCP 规范 RFC 9293、Wireshark TCP 显示过滤器说明。