ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

TCP可靠传输原理

TCP可靠传输原理 1. 引言为什么我们需要“可靠传输”TCPTransmission Control Protocol传输控制协议是互联网中应用最广泛的传输层协议。无论是浏览网页、发送邮件、传输文件还是通过 SSH 远程登录服务器绝大多数面向“可靠性”的应用都建立在 TCP 之上。要理解 TCP 的可靠传输原理首先必须认识到一个关键事实TCP 之下的网络层是“不可靠”的。在经典的 TCP/IP 分层模型中IP 协议提供的只是“尽力而为”best effort的数据报服务。也就是说IP 报文在传输过程中可能出现以下四类问题丢包路由器队列溢出、链路故障或信号干扰都可能导致数据报被丢弃。乱序同一连接的不同报文可能经过不同路径到达后发送的反而先到达。重复网络中某些机制或重传行为可能导致同一个数据报被复制。损坏物理层的噪声或干扰可能导致数据在传输过程中出现比特错误。如果应用程序直接基于 IP 进行通信就必须自己处理以上所有问题这显然非常复杂且容易出错。TCP 的核心价值就在于它在不可靠的 IP 网络之上为应用程序提供了一条“可靠、有序、面向字节流”的逻辑传输通道。所谓“可靠”在 TCP 语境下通常包含四层含义数据不丢失、数据不重复、数据不乱序、数据不被错误地接收。为了达到这些目标TCP 组合使用了一整套机制包括序列号、确认应答、超时重传、快速重传、滑动窗口、流量控制、拥塞控制以及校验和等。本文将以这四条可靠性要求为主线系统拆解 TCP 可靠传输的完整原理。我们会从 TCP 报文段结构出发逐步分析序列号与确认号、停止等待与滑动窗口、超时重传与 RTT 估计、快速重传、流量控制、拥塞控制、连接管理以及差错检测等机制并在最后通过抓包视角观察这些机制在实际网络中的运作。阅读建议本文面向已经了解 TCP/IP 基本概念、希望深入理解 TCP 可靠传输细节的读者。文中涉及的算法会给出伪代码或公式建议结合 Wireshark 抓包实验加深理解。2. TCP 可靠传输的整体设计目标在深入具体机制之前我们先明确 TCP 可靠传输需要解决的核心问题。计算机网络教材通常把可靠传输定义为“接收方能够按发送方发送的顺序正确、完整地接收到全部数据”其反面就是丢包、乱序、重复和损坏。TCP 的设计目标可以归纳为以下几点2.1 数据完整交付发送方交给 TCP 的每一个字节最终都必须被对端 TCP 层完整地递交给上层应用任何字节都不能凭空丢失也不能凭空多出来。这里的“交付”指的是对端 TCP 接收缓冲区层面的交付而不是应用层主动读取。2.2 数据顺序保证TCP 必须保证数据按发送顺序被提交给接收方应用程序。即使网络中出现了乱序到达TCP 接收方也要负责缓存并重新排序只有排在前面的字节被收到后才能把连续的字节流交给应用层。2.3 数据不重复同一个数据段在网络中可能因为超时重传而发送多次。TCP 接收方必须能够识别并丢弃重复数据保证应用层不会收到重复的字节。2.4 数据无差错如果数据在传输过程中发生了比特错误TCP 接收方需要通过校验和发现错误并丢弃该报文段随后通过确认与重传机制让发送方重新发送正确数据。TCP 本身并不纠错而是“检错 重传”。2.5 面向字节流TCP 不维护消息边界它把上层传来的数据看成一条连续的字节流。应用层调用一次 send 写入的数据对端不一定用一次 recv 就能全部读到。字节流语义是 TCP 可靠传输的重要前提也直接引出了序列号按字节编号的设计。综合来看TCP 可靠传输并不是某一种单一技术而是多种机制协同工作的结果。下面我们从最基础的报文段结构开始理解这些机制赖以运行的“信息载体”。3. TCP 报文段结构与可靠性相关字段TCP 的所有可靠性机制都依赖于报文段头部中的特定字段。理解这些字段是理解后续确认、重传、窗口等机制的前提。一个 TCP 报文段由 20 字节固定首部和可选部分选项 数据组成。与可靠性直接相关的字段包括源端口、目的端口、序列号、确认号、数据偏移、标志位、窗口大小、校验和、紧急指针以及可选的选项字段。下表汇总了 TCP 首部各字段及其与可靠传输的关系字段长度作用与可靠传输的关系源端口16 位标识发送方应用进程用于区分连接间接支持可靠性管理目的端口16 位标识接收方应用进程同上序列号 Seq32 位本报文段第一个数据字节的编号实现有序交付、乱序重组和重复检测确认号 Ack32 位期望收到的下一个字节序号实现确认应答是重传机制的基础数据偏移4 位首部长度单位 4 字节定位选项与数据边界保留位4 位或更多保留后续扩展与 ECN 等机制相关标志位8-9 位URG/ACK/PSH/RST/SYN/FIN 等控制连接建立、释放与确认行为窗口大小16 位接收方通告的可用缓冲区大小实现流量控制防止接收方被淹没校验和16 位覆盖首部、数据和伪首部检测传输过程中的比特错误紧急指针16 位仅在 URG 置位时有效标记紧急数据位置可靠性影响较小选项可变MSS、窗口缩放、时间戳、SACK 等影响窗口精度、RTT 测量和选择性确认3.1 序列号字段序列号Sequence Number简称 Seq是 TCP 可靠传输的基石。TCP 将数据流中的每个字节都编上一个序号报文段首部的 Seq 字段填写的是该报文段所携带数据的第一个字节的序号。例如当连接建立时假设初始序列号Initial Sequence NumberISN为 1000发送方连续发送三个报文段分别携带 0、100、200 字节的数据则这三个报文段的 Seq 分别为 1000、1100、1300。注意序号不是按报文段编号而是按字节编号这是 TCP 与其他按分组编号的协议的重要区别。3.2 确认号字段确认号Acknowledgment Number简称 Ack的含义是接收方期望收到的下一个字节的序列号同时隐含地确认了“该序号之前的所有字节都已成功接收”。这种确认方式称为“累计确认”。沿用上例如果接收方成功收到 Seq1000 的 100 字节数据它会回复一个 Ack1100 的确认报文表示“我已经收到了 1100 之前的所有数据请从 1100 开始发送”。3.3 标志位TCP 首部包含多个标志位其中与可靠连接管理最相关的是SYN、ACK、FIN、RST。SYN用于建立连接在三次握手中同步双方的初始序列号。ACK表示确认号字段有效。连接建立后绝大多数报文都会置位 ACK。FIN用于释放连接表示发送方已经没有数据要发送。RST用于异常重置连接向对端表明当前连接出现无法恢复的错误。PSH提示接收方尽快把数据交给应用层不强制改变可靠传输顺序。URG表示存在紧急数据由紧急指针指出位置。3.4 窗口大小字段窗口大小Window Size是接收方通告给发送方的“接收窗口”大小单位是字节。发送方发送“在途未确认”的数据总量不能超过该窗口。窗口字段是 16 位默认最大只能表示 65535 字节因此在高速网络中常配合“窗口缩放Window Scale”选项来扩大有效窗口。3.5 校验和字段校验和用于检测报文段在传输过程中是否发生比特错误。TCP 校验和覆盖的范围不只是 TCP 首部和数据还包括一个由源 IP、目的 IP、协议号和 TCP 长度组成的“伪首部”。后文会专门介绍 TCP 校验和的计算方式及其局限。4. 序列号与确认号机制详解序列号和确认号是 TCP 可靠传输的“坐标系统”。没有它们接收方无法判断数据是否连续发送方也无法知道哪些数据已经到达。下面我们深入分析这两个字段的工作方式。4.1 初始序列号为何不能从 0 开始TCP 连接的双方不会简单地从序号 0 开始编号而是各自生成一个随机的初始序列号ISN。这样设计的主要原因有两点防止旧连接的分组干扰新连接如果同一对地址和端口反复建立连接而每次连接都从固定序号开始网络中延迟到达的旧分组可能与新连接的数据序号重合导致接收方错误地将其当作新数据。提升安全性随机 ISN 使攻击者更难猜测序列号从而提高连接劫持的难度。ISN 的生成通常涉及时钟和随机化算法。现代操作系统会使用随机数生成器并结合时间信息使 ISN 足够不可预测。4.2 序号空间与回绕序列号字段是 32 位因此取值范围为 0 到 4294967295。当序号超过最大值后会回绕到 0 继续使用。TCP 协议通过序号比较规则来处理回绕问题。在同一个连接中“在途未确认”的数据量受窗口大小限制通常远小于序号空间的一半因此发送方和接收方可以安全地判断序号的先后关系而不会因为回绕产生二义性。4.3 SYN 和 FIN 也占用序号一个容易被忽略的细节是SYN 和 FIN 报文段虽然通常不携带应用数据但它们也要占用一个序号。因此连接建立时发送 SYN 的一方会消耗一个序号。连接释放时发送 FIN 的一方也会消耗一个序号。例如客户端发送 SYN 报文其中 Seq500ISN 为 500且在 SYN 报文中携带了 0 字节数据。从概念上看这个 SYN 标志会“占用”序号 500。因此服务器回复的 ACK 报文中的确认号为 501表示期望收到序号 501 的字节。4.4 确认号的计算规则确认号的计算遵循一个非常简单的规则Ack 连续收到的最后一个字节序号 1。举例说明假设接收方已经连续接收到序号 0 至 999 共 1000 个字节的数据那么它发送的 ACK 报文中确认号就是 1000表示“0-999 都已收到请发送 1000 及之后的字节”。如果接收方收到的是乱序数据例如它已经收到 0-999但随后先收到了 1500-1999中间 1000-1499 缺失那么它会继续发送 Ack1000而不会确认乱序到达的 1500-1999。这是累计确认的重要特性确认号只反映连续收到的最高字节跳过的空洞不会被确认。4.5 全双工与两个方向的序号TCP 是全双工的通信双方可以同时发送和接收数据。因此连接的两端各自维护一个独立的序号空间和确认空间A 发送的数据用 A 的 Seq 编号B 回复的 ACK 确认的是 B 从 A 收到的数据反过来B 发送的数据用 B 的 Seq 编号A 回复的 ACK 确认的是 A 从 B 收到的数据。这意味着通信双方发送的数据序号彼此独立、互不干扰而每一方的“发送窗口”和“接收窗口”也是各自维护的。5. 从停止等待协议理解可靠传输的基本逻辑要理解 TCP 的滑动窗口与重传机制我们可以先从一个最简单的可靠传输模型入手停止等待协议Stop-and-Wait。它是所有可靠传输协议的理论起点。5.1 停止等待的基本流程停止等待协议的核心思想非常朴素发送方每发送一个分组就停下等待接收方的确认只有收到对该分组的确认后才发送下一个分组。其工作过程可以用下面的伪代码描述# 停止等待协议的发送方简化模型 def sender_stop_and_wait(data_units): for unit in data_units: send(unit) # 发送当前数据单元 start_timer() # 启动超时定时器 while True: ack receive() # 等待接收确认 if ack is not None: if ack expected_ack(unit): stop_timer() break # 收到正确确认发送下一个 else: ignore() # 重复或错误的确认忽略 elif timer_expired(): resend(unit) # 超时重新发送 restart_timer() return all data sent接收方的工作则相对简单收到正确且按序的分组后回复确认收到重复分组时丢弃数据但再次回复确认收到错误分组时直接丢弃。5.2 停止等待中的三种异常情况停止等待协议通过“超时重传”和“序号”来处理网络中的异常。下面三种情况最能体现可靠协议的基本设计思想。场景一数据分组丢失。发送方发出数据后分组在网络中丢失接收方什么也没收到自然不会回复确认。发送方等待一段时间后超时重传该分组。超时定时器是可靠传输的“兜底”机制。场景二确认分组丢失。数据分组成功到达接收方也回送了确认但确认分组在网络中丢失。发送方在超时后重传数据分组。此时接收方发现这是重复分组于是丢弃数据并再次回复确认。发送方收到重复确认后知道数据其实已经被正确接收继续发送下一个分组。场景三数据或确认发生延迟。分组没有丢失只是延迟了。发送方超时后重传随后又收到延迟到达的原始确认。此时发送方需要能够区分“这是对哪个分组的确认”。如果协议没有序号发送方就无法判断收到的确认是对第一次发送还是重传的响应。因此分组必须携带序号确认也必须携带对应的序号这是可靠传输协议的最基本要求。5.3 停止等待的效率问题停止等待协议的可靠性没有问题但效率极低。因为发送方每发送一个分组后都要停下来等待确认信道利用率非常低。假设链路的往返时间RTT为 100 毫秒分组大小为 1KB那么停止等待协议的有效吞吐量只有约 10KB/s无论底层带宽多大都无法提高。为了提高利用率就需要允许发送方在未收到确认的情况下连续发送多个分组这就是“流水线”pipelining思想也是滑动窗口协议的核心。TCP 正是采用流水线式的滑动窗口协议来提升吞吐量。6. 滑动窗口协议与累计确认滑动窗口Sliding Window是 TCP 可靠传输的核心机制。它允许发送方在收到确认之前连续发送多个报文段从而大幅提高链路利用率同时仍然保证可靠性和有序性。6.1 发送窗口与接收窗口TCP 连接的两端分别维护发送窗口和接收窗口。发送窗口指的是发送方“允许发送但尚未收到确认”的字节范围。发送窗口的大小由两个因素共同决定接收方通告的接收窗口rwnd和拥塞控制计算出的拥塞窗口cwnd。实际发送窗口取两者的较小值。接收窗口指的是接收方“愿意接收并缓存”的字节范围。接收方只缓存落在接收窗口内的字节窗口外的数据会被丢弃。发送窗口可以分为四个部分从左到右依次为已发送并已确认的数据、已发送但未确认的数据、允许发送但尚未发送的数据、当前不允许发送的数据。其中第二和第三部分合起来就是发送窗口的大小。6.2 累计确认TCP 默认采用累计确认Cumulative Acknowledgment。确认号 n 表示“序号 n 之前的所有字节都已正确、连续地收到”。累计确认的优点是实现简单、节省确认报文并且即使个别确认丢失后续确认也能覆盖前面的信息。累计确认也有代价如果数据中间出现空洞接收方只能不断重复发送对空洞起点之前数据的确认而无法告知发送方“空洞之后的数据其实已经到了”。下例可以说明这一点发送方依次发送序列号为 1000、1100、1200、1300、1400 的五个数据段每段 100 字节。假设 1200 段丢失其余都到达。接收方会依次回复 Ack1100收到 1000、Ack1100收到 1100。当 1300、1400 乱序到达时接收方发现中间缺少 1200因此仍然回复 Ack1200因为 1100-1199 已连续收到。发送方此时收到多个重复的 Ack1200就能推断出“1200 段可能丢失”。直到 1200 段重传并到达后接收方才能确认所有连续数据回复 Ack1500。6.3 选择确认 SACK累计确认在“单个报文段丢失但后续大量报文段成功到达”的场景下会造成不必要的重传因为发送方只知道某个点之前收到了不知道之后有哪些已经到达。为此TCP 引入了选择确认Selective AcknowledgmentSACK选项。SACK 允许接收方在 ACK 报文中附加“已接收的非连续数据块”的区间信息。发送方收到 SACK 后可以知道哪些数据已经到达从而只重传真正丢失的部分而不是盲目地重传所有未确认数据。需要注意的是SACK 中文全称是“选择性确认”它仅提供“哪些块已收到”的信息真正决定重传策略的仍然是发送方。通常 SACK 会与重传策略结合提高丢包恢复的效率。6.4 窗口滑动过程滑动窗口的“滑动”体现在两方面发送方每收到一个有效确认发送窗口的左边界就向右移动同时右边界也可能随之向右扩展从而把新的数据纳入可发送范围接收方每接收并交付一批连续数据接收窗口也向右滑动。假设发送窗口大小为 4 个报文段发送方已发送 4 个段窗口满了收到对第一段的累计确认后窗口左边界右移 1 段右边界也右移 1 段于是发送方又能向网络发出第 5 个段。整个过程像一扇窗户沿着数据流不断向右移动因此得名“滑动窗口”。6.5 滑动窗口与流水线效率滑动窗口允许“在途未确认”的数据量维持在窗口大小以内发送方不需要每发一个段就等待一次确认。只要窗口允许发送方就可以持续发送这就是流水线。相比停止等待滑动窗口可以显著提高吞吐量。在理想情况下发送窗口大小应当等于带宽与延迟的乘积BDPBandwidth-Delay Product以使链路始终被数据填充。7. 超时重传与重传超时时间RTO的估算只要有超时机制就必须回答一个问题超时时间设置多长才合适如果 RTO 太短正常的网络延迟就会触发不必要的重传如果 RTO 太长真正的丢包要等很久才会被重传吞吐量会大幅下降。TCP 对 RTO 的估算经历了从简单到复杂、从粗糙到精细的演进过程。7.1 RTT 与 RTO 的基本关系RTTRound-Trip Time是指一个报文段从发出到收到对应确认所经历的时间。RTORetransmission Timeout是发送方在发出数据后等待确认的最长时间。理想情况下RTO 应该稍大于 RTT并且能随着网络状况的变化动态调整。一个朴素的想法是直接测量最近一次的 RTT然后设置 RTO 为它的一个固定倍数。但这种做法在网络抖动较大的场景下效果很差因为单次测量可能恰好非常小或非常大。7.2 经典加权平均算法早期 TCP 使用“指数加权移动平均”来平滑 RTT 的估算。该算法维护一个平滑往返时间SRTT每获得一个新的 RTT 样本就按如下公式更新# alpha 通常取 0.125 SRTT (1 - alpha) * SRTT alpha * RTT_sample 早期 RTO 计算beta 通常取 2 RTO beta * SRTT这种简单的平滑在 RTT 方差较大时并不理想。如果网络延迟波动剧烈固定倍数可能仍然不足以覆盖真实的延迟变化。7.3 Jacobson 算法与 RTT 方差Van Jacobson 在 1988 年提出了改进方案引入了 RTT 方差的概念使 RTO 能够根据延迟的波动幅度自动放大。改进后的算法在每次获得 RTT 样本时进行如下更新# 第一次测量时的初始化 SRTT RTT_sample RTTVAR RTT_sample / 2 RTO SRTT 4 * RTTVAR 后续测量alpha 通常取 0.125beta 通常取 0.25 RTTVAR (1 - beta) * RTTVAR beta * abs(SRTT - RTT_sample) SRTT (1 - alpha) * SRTT alpha * RTT_sample RTO SRTT 4 * RTTVAR这个公式的含义是RTO 不仅考虑平均 RTT还考虑 RTT 的波动程度。当 RTT 非常稳定时RTTVAR 很小RTO 接近 SRTT当网络抖动很大时RTTVAR 增大RTO 被相应拉长从而减少虚假重传。7.4 Karn 算法重传样本的歧义RTT 测量面临一个经典问题当发送方重传某个报文段后收到了确认这个确认到底是对第一次发送的响应还是对重传的响应无法判断的事实导致测量出的 RTT 可能明显偏大或偏小这种现象称为“重传歧义”。Karn 算法给出的解决方案是对于重传过的报文段不采用其 RTT 样本更新 SRTT一旦发生重传就在现有 RTO 的基础上将超时时间加倍。这种“指数退避”策略在持续拥塞时尤其有效可以避免发送方在崩溃的网络中不断以过短的超时时间重传。当后续成功收到一个非重传报文段的确认时再恢复正常计算。Karn 算法与 Jacobson 算法通常结合使用共同保证 RTT 估算的稳定性。7.5 时间戳选项消除重传歧义Karn 算法简单有效但代价是放弃了对重传报文的 RTT 观测。TCP 时间戳选项TSopt提供了一个更精细的解决方案发送方在每个报文段中携带一个时间戳值接收方在确认中回显该时间戳。这样即使报文段被重传发送方也能根据时间戳区分确认对应的是哪一次发送从而准确测量 RTT。时间戳选项还同时用于 PAWSProtection Against Wrapped Sequence numbers机制防止高速网络中序号回绕造成的数据混淆。7.6 定时器的粒度与实现实际实现中TCP 通常不会为每个报文段单独维护一个高精度定时器而是使用一个统一的超时定时器来管理最早未确认的报文段。当新的确认推进了窗口左边界后定时器会重新设定。定时器的粒度受操作系统时钟影响早期实现中可能导致 RTO 被取整到比较大的单位现代实现通常使用高精度时钟来避免这一问题。8. 快速重传不等超时的智能重传超时重传虽然可靠但往往反应太慢。在许多情况下单个报文段丢失后后续报文段仍然能够到达接收方。此时接收方会不断发送重复的累计确认以提示发送方“某个序号之前的数据是连续的但这个位置之后出现了空洞”。8.1 重复 ACK 的含义重复 ACK 本身并不一定意味着丢包。它可能是由于网络乱序造成的某个报文段先于它前面的报文段到达接收方无法推进确认号只能重复发送当前期望的确认号。如果乱序很快被补齐那么重复 ACK 的数量通常很少。但当发送方连续收到三个相同的重复 ACK 时TCP 认为“该数据段很可能已经丢失”于是在超时定时器触发之前立即重传对应的报文段。这一机制称为快速重传Fast Retransmit。8.2 为什么是三个重复 ACK选择“三个重复 ACK”是经验性的平衡。一个或两个重复 ACK 很可能只是网络乱序引起的立即重传容易造成不必要的冗余而等待更多重复 ACK 又会拖慢丢包恢复。三个重复 ACK 在实际网络中是一个常见的折中阈值。快速重传只依赖重复 ACK不需要等待 RTO 超时因此能显著缩短单次丢包的恢复时间尤其在高延迟链路上效果明显。8.3 快速重传与快速恢复快速重传通常与快速恢复Fast Recovery配合使用。传统拥塞控制算法在发生超时后会把拥塞窗口直接重置到很小的值并进入慢启动而快速重传场景下网络仍然在流通数据说明拥塞程度可能并不严重因此可以采用更温和的策略执行快速重传后不把拥塞窗口降到初始值而是将其减半并直接从拥塞避免阶段开始增长。后文拥塞控制部分会继续展开。8.4 快速重传的局限快速重传依赖于“后续有足够多的数据段到达”这一前提。如果丢包发生在数据流的尾部或者窗口内后续数据太少接收方可能无法产生足够的重复 ACK发送方就只能在 RTO 超时后依靠超时重传来恢复。对于这种情况SACK 可以提供额外帮助即使重复 ACK 数量不足SACK 信息也能帮助发送方尽早识别丢失的数据块。9. TCP 流量控制接收方的守护机制可靠传输不仅要防止数据在网络中丢失还要防止“发送方发送太快导致接收方来不及处理”。后者属于流量控制Flow Control的范畴。流量控制是 TCP 可靠性的重要组成部分它保证接收方的缓冲区不会被淹没从而避免因为缓冲区溢出而产生不必要的丢包。9.1 接收窗口 rwndTCP 通过报文段首部的“窗口大小”字段实现流量控制。接收方在每次发送报文时都会通告当前可用的接收缓冲区空间。这个值就是接收窗口 rwndreceive window。发送方保证“在途未确认”的数据总量不超过 rwnd。接收方维护一个接收缓冲区假设缓冲区总大小为 64KB已缓存但尚未被应用读取的数据为 4KB那么可用空间还剩 60KB接收方就会通告 rwnd61440。发送方看到这个值后最多只能再有 60KB 的数据未被确认。9.2 窗口缩放解决高速网络问题窗口字段只有 16 位最大只能表示 65535 字节。在高带宽高延迟网络中这个窗口远不足以填满带宽延迟积。为此TCP 引入了“窗口缩放”选项。双方在建立连接时协商一个缩放因子shift count实际窗口大小为字段值左移该因子位。例如缩放因子为 7则字段值 65535 实际表示约 8MB 的窗口从而满足高速网络的需求。9.3 零窗口与持续探测当接收方缓冲区被填满、暂时无法接收新数据时它会通告 rwnd0即“零窗口”。发送方收到零窗口通告后必须停止发送数据。这里存在一个隐患接收方恢复空间后会发送一个窗口更新报文通知发送方但假如这个更新报文丢失了发送方将永远等待接收方也在等待数据形成死锁。为解决这个问题TCP 引入了持续计时器Persist Timer。当发送方收到零窗口通告后会启动持续计时器超时后发送一个仅 1 字节的“窗口探测”报文段迫使接收方重新通告当前窗口。如果仍然是零窗口就继续周期性探测直到窗口重新打开。9.4 糊涂窗口综合症与 Nagle 算法流量控制如果处理不当会导致“糊涂窗口综合症”Silly Window Syndrome。它表现为发送方每次只发送很小的一段数据或者接收方每次只通告很小的窗口导致大量小报文在网络中传输有效吞吐率急剧下降。为了避免发送方的糊涂窗口可以使用 Nagle 算法当一个连接中有未确认的数据时发送方不会立刻发送小的数据段而是把小数据积累起来直到收到之前数据的确认或者积累到足以组成一个最大段MSS。这样可以减少大量小报文的出现。但 Nagle 算法在延迟敏感或交互式场景下可能引入额外时延因此常与 TCP_NODELAY 选项配合调整。接收方也应当避免“每次腾出一点空间就立刻通告一点窗口”的行为。通常接收方会等到缓冲区可用空间达到较大阈值时才通告窗口更新避免诱导发送方发送过小的报文段。10. 拥塞控制确保网络不被压垮流量控制关注的是接收方的处理能力而拥塞控制关注的是整个网络的承载能力。即使接收方缓冲区足够大如果网络中的路由器队列已经溢出数据仍然会丢失。拥塞控制是 TCP 可靠传输的另一个关键支柱它在“不压垮网络”与“充分利用带宽”之间寻找动态平衡。10.1 拥塞窗口 cwnd 与慢启动阈值发送方实际可以使用的发送窗口是 rwnd 和 cwnd 中的较小值。rwnd 由接收方通告而 cwnd 由发送方根据网络状况自行维护。拥塞控制算法通过动态调整 cwnd 来控制注入网络的数据量。慢启动阈值 ssthresh 是一个关键分界点当 cwnd 小于 ssthresh 时使用慢启动算法当 cwnd 大于等于 ssthresh 时进入拥塞避免阶段。10.2 慢启动连接建立初期发送方对网络状况一无所知不能一上来就发送大量数据。慢启动从较小的 cwnd如 1 个 MSS甚至 10 个 MSS 的起步值开始每收到一个确认就将 cwnd 增加一个 MSS因此 cwnd 在慢启动阶段呈指数增长。“慢启动”这个名字有些误导实际上它的窗口增长速度并不慢只是起点低。指数增长的好处是能快速探测可用带宽但也意味着如果持续下去会很快造成拥塞因此必须有退出条件。10.3 拥塞避免当 cwnd 达到 ssthresh 后TCP 切换到拥塞避免阶段。为了避免指数增长过快导致拥塞拥塞避免每经过一个 RTT 才将 cwnd 增加约一个 MSS实现近似线性增长。拥塞避免阶段又分为两个子阶段拥塞避免阶段本身以及快速恢复阶段。当网络状况良好、没有丢包时cwnd 以线性方式缓慢增长直到遇到丢包。10.4 拥塞发生时的处理TCP 无法直接观察路由器队列状态而是通过丢包或重复 ACK 来间接推断拥塞。发生拥塞时有两种典型信号超时意味着较严重的拥塞可能已经丢失了多个报文段后续也没有足够的重复 ACK 触达发送方。三个重复 ACK意味着单个报文段丢失但网络仍在传递数据拥塞程度可能较轻。经典 Reno 算法在超时发生时执行ssthresh max(cwnd // 2, 2 * MSS) cwnd 1 * MSS # 回到慢启动起点 enter_slow_start()而在收到三个重复 ACK 时执行快速重传和快速恢复ssthresh max(cwnd // 2, 2 * MSS) cwnd ssthresh 3 * MSS # 适当收缩但不归零 enter_fast_recovery() # 直接进入拥塞避免/快速恢复10.5 快速恢复快速恢复的核心思想是既然三个重复 ACK 表明网络还能传递数据说明拥塞并不严重因此不需要像超时那样把窗口清零并重走慢启动。进入快速恢复后发送方每收到一个重复 ACK就将 cwnd 暂时增加一个 MSS当收到新的确认时退出快速恢复把 cwnd 恢复为 ssthresh 并进入拥塞避免阶段。10.6 Tahoe、Reno 与 NewRenoTCP 拥塞控制算法经历了多个版本演进Tahoe早期版本无论是超时还是三个重复 ACK都统一将 cwnd 置为 1 个 MSS 并进入慢启动恢复较慢。Reno区分超时和快速重传快速重传后进入快速恢复减少了对单次丢包的过度反应。NewReno针对 Reno 在“一个窗口中丢失多个报文段”时频繁退出快速恢复的问题做了改进通过记录“恢复点”和区分部分确认来更准确地处理多重丢包。10.7 现代拥塞控制算法CUBIC 与 BBR面向高速、高延迟网络的现代算法对传统 AIMD加性增、乘性减模型做了进一步优化。CUBIC使用三次函数而不是线性函数来调整窗口。丢包后窗口快速回落但在远离上次拥塞点的阶段加快探测速度更适合高带宽延迟积网络。CUBIC 是 Linux 系统中长期默认的拥塞控制算法之一。BBR由 Google 提出它不再把丢包当作唯一的拥塞信号而是通过持续测量瓶颈带宽和最小 RTT 来建模“带宽延迟积”主动将发送速率控制在不产生排队积压的范围内在有一定丢包的链路上也能保持较高吞吐。无论采用哪种算法拥塞控制都是 TCP 可靠传输不可分割的一部分如果没有拥塞控制发送方可能因发送过多数据而引发网络拥塞进而造成大量丢包可靠传输的效率将急剧恶化。11. 连接管理可靠传输的前提保障TCP 是面向连接的协议。在真正传输数据之前双方需要通过一定的握手过程建立连接同步状态和初始序号在数据传输结束后也需要有序地释放连接。连接管理虽然不是数据重传机制本身但它为可靠传输提供了必要的状态同步与安全前提。11.1 三次握手TCP 建立连接采用三次握手其过程如下客户端发送 SYN 报文携带自己的初始序号 Client ISN。服务器回复 SYNACK 报文确认客户端的 SYN同时携带自己的初始序号 Server ISN。客户端发送 ACK 报文确认服务器的 SYN。从可靠性角度看三次握手解决了两个关键问题其一双方都确认了彼此的收发能力其二双方同步了各自的初始序列号后续数据序号才能正确对齐。为什么需要三次而不是两次核心原因在于防止“已失效的连接请求报文”突然到达服务器导致服务器错误地建立一条无人使用的连接。如果只有两次握手服务器收到一个延迟到达的旧 SYN 后就会建立连接并分配资源而客户端其实早已放弃这次连接。增加第三次握手后服务器只有在收到客户端对服务器 SYN 的确认时才真正建立连接从而降低这种风险。11.2 SYN 攻击与防护由于服务器在收到 SYN 后需要为该连接分配状态和资源恶意的攻击者可以发送大量伪造源地址的 SYN 报文使服务器资源耗尽这就是著名的 SYN Flood 攻击。它与可靠性原理相关的地方在于连接建立阶段的状态管理必须在“安全”与“资源占用”之间取得平衡。现代实现常采用 SYN Cookie 等技术在三次握手完成前尽量少占用服务器资源。11.3 四次挥手由于 TCP 是全双工的每个方向的数据流都需要独立关闭因此连接释放通常需要四次交互主动关闭方发送 FIN表示自己不再发送数据。被动关闭方回复 ACK确认对方的 FIN。被动关闭方随后也发送 FIN表示自己也不再发送数据。主动关闭方回复 ACK确认对方的 FIN。从可靠性角度出发连接释放过程必须确保双方尚未到达的数据都被完整交付。FIN 和 ACK 的这些交互正是为了处理“可能还有数据在途”以及“对端尚未通告结束”的情况。11.4 TIME_WAIT 状态的意义主动关闭方在发送最后一个 ACK 后会进入 TIME_WAIT 状态并等待 2MSL两倍报文段最大生存时间才真正关闭连接。TIME_WAIT 有两个重要作用确保最后一个 ACK 能够被对端收到。如果该 ACK 丢失对端会重传 FIN主动关闭方在 TIME_WAIT 期间仍能识别并重新回复 ACK。让旧连接的残余分组在网络中自然消亡避免这些旧分组干扰之后在同一地址端口对上建立的新连接。这正是连接管理与序列号机制协同保障可靠性的典型例子。12. 校验和与差错检测前面讨论的序列号、确认、重传机制解决的更多是“丢包、乱序、重复”问题而“数据损坏”的检测依靠的是校验和。校验和让接收方能够识别在传输过程中发生比特错误的报文段并将其丢弃等待发送方重传。12.1 TCP 校验和的计算范围TCP 校验和覆盖三部分内容TCP 首部、TCP 数据以及一个用于额外的错误检测的“伪首部”。伪首部并不真正在网络上传输它只是在计算校验和时临时构造其中包含如下字段源 IP 地址、目的 IP 地址、保留字节、协议号TCP 为 6和 TCP 报文段总长度。把 IP 地址纳入校验和的好处是即使 TCP 报文段由于某种原因被错误地投递到错误的主机或端口对接收方计算校验和时也会因为源、目的信息不匹配而发现错误。12.2 反码求和算法TCP 校验和采用“16 位二进制反码求和再取反”的方式计算。发送方把校验和字段先置为 0将首部、数据和伪首部按 16 位分组求和结果取反码后填入校验和字段。接收方收到报文后把包括校验和在内的所有 16 位组做二进制反码求和若结果为全 1则认为数据没有发生可检测的错误。下面是一个简化版本的反码求和校验示例def checksum(data_bytes): if len(data_bytes) % 2 ! 0: data_bytes b\x00 # 奇数长度时补一个字节 0 total 0 for i in range(0, len(data_bytes), 2): word (data_bytes[i] 8) data_bytes[i 1] total word total (total 0xFFFF) (total 16) # 将进位回卷 return (~total) 0xFFFF校验和算法计算成本较低适合高速网络设备在软件或硬件中实现。12.3 校验和的局限需要明确的是TCP 校验和只能检测错误不能纠正错误也无法保证 100% 检出所有错误。理论上只要被改变的比特恰好使得校验和结果仍然一致错误就会被漏检。虽然这种情况概率很低但在数据传输要求极高的场景下通常还需要由更上层的协议提供端到端的完整性校验。此外校验和检测到错误后的处理方式是把整个报文段丢弃然后依靠后续的确认与重传机制恢复。因此差错检测与重传机制是紧密配合的。13. TCP 可靠性的边界与常见误区理解了 TCP 的各项机制之后我们还需要清醒地认识到它的可靠性边界。许多对 TCP 的误解都源于把这些边界扩大或缩小。13.1 TCP 可靠不代表应用一定成功读取TCP 保证的是“接收方的 TCP 协议栈正确、有序地收到了字节”并不保证“接收方应用程序已经成功读取并处理了这些字节”。比如接收方应用因为崩溃、死锁或处理太慢而没有及时调用 recv数据仍然停在接收缓冲区里但站在 TCP 层的角度看这些数据已经被可靠交付了。13.2 TCP 不保证消息边界由于 TCP 是字节流协议它不保留应用层消息的边界。发送方调用两次 send 各发送 100 字节接收方可能一次 recv 就收到 200 字节也可能分三次才收完。如果需要区分消息边界必须在应用层增加长度前缀、分隔符或自定义协议。所谓“TCP 粘包、拆包”本质上是应用层对字节流边界的处理问题而不是 TCP 协议本身的缺陷。13.3 TCP 不负责数据加密与身份认证TCP 只解决传输的可靠性不解决安全性。数据在传输过程中是否被窃听、篡改或伪造来源TCP 本身无法保证。安全性需要 TLS、IPSec 等上层或伙伴协议来提供。13.4 TCP 的重传可能降低吞吐但不会破坏正确性重传机制在极端情况下可能导致同一数据被发送多次但接收方凭借序列号能够识别并丢弃重复数据因此应用层看到的数据仍然是正确且不重复的。代价只是带宽的浪费和延迟的上升。13.5 序号的“顺序”不等于业务顺序TCP 保证的是“字节按发送顺序到达接收方 TCP”并不等同于业务语义上的顺序。如果应用层采用多线程、异步发送或者不同连接之间相互竞争那么业务事件的实际发生顺序需要由应用层自己定义。14. 实战观察用抓包理解 TCP 可靠传输理论分析之外通过 Wireshark 或 tcpdump 观察真实网络流量是理解 TCP 可靠传输的有效方法。下面我们结合一个典型文件下载场景说明如何从抓包结果中识别各个可靠性机制。14.1 观察三次握手抓包列表中首先会出现三个连续报文客户端的 SYN、服务器的 SYNACK、客户端的 ACK。展开每个报文可以看到双方的序列号以及确认号。通过握手双方同步了初始序号之后的数据序号都会在此基础上递增。14.2 观察数据传输与累计确认数据传输阶段发送方会连续发出多个携带数据的报文段而接收方通常并非对每个报文段都单独回复而是采用累计确认一段时间后回一个确认覆盖多个数据段。观察 ACK 中的确认号可以发现它随着接收方连续收到的字节而不断向前推进。14.3 观察乱序与重复 ACK在网络质量较差的链路上抓包时可能看到接收方连续发送多个相同确认号的 ACK。这就是报文段乱序或丢失的典型表现。如果发送方接着触发快速重传你会在抓包中看到它比预期更早地重新发送了某个报文段而不是等待超时。14.4 观察超时重传人为设置丢包率后可以观察到某个报文段发出后在约等于 RTO 的时间之后被重新发送。重传报文的内容与原始报文相同但抓包时间戳可以清楚地区分“原始发送”和“重传”。结合时间序列图还能直观地看到发送方的重传超时时间如何随着连续重传而按指数退避增大。14.5 观察窗口缩放与流量控制在高速下载场景中展开 TCP 选项可以看到双方协商的窗口缩放因子。接收方在 ACK 中通告的窗口值会随着应用读取速度的变化而波动当应用读取变慢时窗口逐渐变小发送方发送速率随之下降形成明显的动态调节过程。通过抓包抽象的“序列号、确认号、窗口、重传”都变成了可观察的时间序列这也往往是最快掌握 TCP 可靠传输的方式。15. 总结TCP 的可靠传输不是单一魔法而是一整套机制协同作用的结果。回顾全文可以将其归纳为以下几个方面序列号与确认号按字节编号为有序交付、乱序重组和重复检测提供基础坐标。累计确认与 SACK用最少的确认信息告知发送方哪些数据已连续收到SACK 进一步精确描述乱序区间。滑动窗口以流水线方式提高吞吐量同时限制在途数据量兼顾效率与接收方能力。超时重传与 RTO 估算通过平滑 RTT 和方差动态计算合理的超时时间并用 Karn 算法和时间戳解决重传歧义。快速重传与快速恢复利用重复 ACK 提前发现单段丢失缩短恢复时间。流量控制通过接收窗口防止发送方淹没接收方借助零窗口探测避免死锁。拥塞控制动态调整拥塞窗口在避免网络崩溃的同时尽量利用带宽。连接管理通过三次握手和四次挥手同步状态与初始序号保护连接生命周期。校验和检测比特错误配合重传完成差错恢复。需要再次强调的是TCP 的“可靠”是建立在“不可靠 IP 网络”之上的端到端可靠。它保证数据在传输层被正确、有序、完整地交付但应用层仍需自行处理消息边界、业务语义和安全性等问题。只有理解了这些机制各自的作用以及它们之间的配合关系才能真正掌握 TCP 可靠传输的完整原理也才能在实际网络编程、性能调优和故障排查中做出准确的判断。
返回列表