TCP核心特性: 面向连接的、可靠的、字节流、全双工、点对点。
一、TCP连接:不仅仅是握手
面向连接 (Connection-Oriented):
- 握手 (Handshake): 在数据传输前,通信双方(客户端和服务器)必须通过“三次握手”建立连接。
- 目的:
- 交换初始序号 (ISN)。
- 同步双方状态,如MSS、窗口缩放选项等。
- 分配必要的内核资源(缓存、变量)。
- 过程简述:
- 客户端
SYN(seq=client_isn)
-> 服务器 - 服务器
SYN(seq=server_isn), ACK(ack=client_isn+1)
-> 客户端 - 客户端
ACK(ack=server_isn+1)
-> 服务器 (此报文可携带数据)
- 客户端
- 目的:
- 逻辑连接: TCP连接是端到端系统TCP程序中的状态,中间网络设备(路由器)不感知或维护TCP连接状态,它们只处理IP数据报。
- 状态变量: 连接双方都会初始化和维护一系列状态变量,如序号、确认号、窗口大小、RTT估计等。
- 握手 (Handshake): 在数据传输前,通信双方(客户端和服务器)必须通过“三次握手”建立连接。
全双工 (Full-Duplex): 数据可以在两个方向上同时独立传输。每个方向都有自己的序号和确认号。
点对点 (Point-to-Point): 一条TCP连接只涉及两个端点,不支持多播或广播。
二、TCP报文段结构 (Segment Structure)
一个TCP报文段包含首部 (Header) 和数据 (Data Payload)。
- 源端口号 (Source Port) 和 目标端口号 (Destination Port) (各16位):
- 用于在端系统中实现多路复用 (Multiplexing) 和多路分解 (Demultiplexing),将数据导向正确的应用进程。
- 序号 (Sequence Number) (32位):
- 标识该报文段数据部分第一个字节在整个字节流中的位置。
- TCP将数据视为一个无结构的、有序的字节流。
- ISN (Initial Sequence Number) 是随机选择的,以增加安全性。
- 确认号 (Acknowledgment Number) (32位):
- 期望从对方接收的下一个字节的序号。
- 只有当
ACK
标志位为1时才有效。 - TCP提供累计确认 (Cumulative Acknowledgment):确认号
y
表示序号小于y
的所有字节都已正确按序接收。TCP只确认该流中至第一个丢失字节为止的字节
- 首部长度 (Header Length / Data Offset) (4位):
- 表示TCP首部的长度,以32位字(4字节)为单位。
- 最小值为5(20字节,无选项),最大值为15(60字节)。
- 保留 (Reserved) (通常为6位,RFC 3168后有调整用于ECN):
- 未来使用,必须为0。
- 标志位 (Flags) (通常为6位,加上ECN后为8位):
URG
: 紧急指针字段有效。ACK
: 确认号字段有效。PSH
: 推送功能,接收方应尽快将数据交给应用层。(实践中少用)RST
: 重置连接,通常在出现错误或异常时使用。SYN
: 同步序号,用于发起连接。FIN
: 终止连接,发送方已无数据发送。CWR
(Congestion Window Reduced) 和ECE
(ECN-Echo): 用于明确拥塞通告 (ECN)。
- 接收窗口 (Receive Window /
rwnd
) (16位):- 用于流量控制。
- 表示接收方当前愿意接收的字节数量(可用的缓存空间)。
- 单位是字节。可通过窗口缩放选项扩展。
- 校验和 (Checksum) (16位):
- 对TCP首部、TCP数据以及一个伪首部(包含源IP、目标IP、协议号、TCP长度)进行计算,用于差错检测。
- 紧急指针 (Urgent Pointer) (16位):
- 当
URG
标志位为1时有效,指向紧急数据最后一个字节的下一个字节的序号。(实践中少用)
- 当
- 选项 (Options) (可变长度,最多40字节):
- 目的: 扩展TCP功能,协商参数。
- 常见选项:
- MSS (Maximum Segment Size): 在SYN报文段中协商,通常基于路径MTU设置,避免IP分片。
- Window Scale (RFC 7323): 允许接收窗口
rwnd
超过65535字节,用于高带宽延迟积网络。 - SACK (Selective Acknowledgment, RFC 2018, 2883): 允许接收方指明已收到的失序报文段,提高重传效率。
- Timestamps (RFC 7323):
- RTTM (Round-Trip Time Measurement): 更精确的RTT测量。
- PAWS (Protection Against Wrapped Sequence numbers): 防止序号回绕问题。
- NOP (No-Operation): 用于选项字段的字节对齐。
- MSS (Maximum Segment Size): 在SYN报文段中协商,通常基于路径MTU设置,避免IP分片。
- 数据 (Data Payload): 应用层数据。其长度受MSS限制。
三、可靠数据传输 (Reliable Data Transfer)
TCP使用多种机制确保数据可靠、按序到达:
- 序号和确认号机制:
- 累计确认: 如前所述。
- 失序处理: 接收方通常会缓存失序到达的报文段,等待缺失的报文段填补间隔。
序号建立在传送的字节流之上,而不是建立在传送的报文段的序列之上。因此==一个报文段的序号是该报文段首字节的字节流编号。==引用https://kiprey.github.io/2021/05/cnatda-1/#4-%E9%9D%A2%E5%90%91%E8%BF%9E%E6%8E%A5%E7%9A%84%E8%BF%90%E8%BE%93%EF%BC%9ATCP
- 举例说明:
以下是交互过程的分解说明:
1) **用户在主机 A 上输入字符 'C':**
* 主机 A 向主机 B 发送一个包含字符 'C' 的 TCP 报文段。
* **报文段详情 (A -> B):**
* `Seq=42`: 这是主机 A 发送的(第一个也是唯一一个)数据字节 'C' 的序号。这暗示主机 A 在此流中已经发送了42个字节,或者其初始序号 (ISN) 是41。
* `ACK=79`: 这是主机 A 的确认号,表明它期望从主机 B 收到的下一个字节的序号是79。
* `data='C'`: 报文段的载荷是单个字符 'C'。
2) **主机 B 收到 'C' 并将其回显:**
* 主机 B 上的 Telnet 服务器通常会将字符回显给客户端(主机 A)。
* 主机 B 首先确认收到了 'C',然后将 'C' 发送回去。
* **报文段详情 (B -> A):**
* `Seq=79`: 这是主机 B 发送的字符 'C' 的序号。这与主机 A 之前发送的 ACK(ACK=79)相吻合。
* `ACK=43`: 主机 B 确认收到了来自主机 A 的数据。因为 'C' 是1个字节且起始序号是42,所以期望从主机 A 收到的下一个字节是 42 + 1 = 43。
* `data='C'`: 载荷是回显的字符 'C'。
3) **主机 A 收到回显的 'C' 并进行确认:**
* 主机 A 收到了主机 B 回显的 'C'。
* 主机 A 向主机 B 发回一个确认。如果用户没有输入其他内容,这个报文段可能不包含新的数据。
* **报文段详情 (A -> B):**
* `Seq=43`: 这是主机 A 当前的序号。因为它之前发送到字节42 ('C'),所以它的下一个数据字节将从43开始。即使此报文段中没有发送新数据,序号也必须是有效的。
* `ACK=80`: 主机 A 确认收到了来自主机 B 的回显 'C'。因为主机 B 以序号79发送了 'C'(且 'C' 是1个字节),所以期望从主机 B 收到的下一个字节是 79 + 1 = 80。
超时重传机制 (Retransmission Timeout / RTO):
- TCP使用超时/重传机制来处理报文段的丢失问题。超时时间间隔长度必须大于该连接的往返时间(RTT),即从一个报文段发出到它被确认的时间,否则会造成不必要的重传。
- RTT估计:
SampleRTT
: 从报文段发出到收到其确认之间的时间。- E s t i m a t e d R T T = ( 1 − α ) ∗ E s t i m a t e d R T T + α ∗ S a m p l e R T T ( α ≈ 0.125 ) EstimatedRTT = (1-α) * EstimatedRTT + α * SampleRTT (α ≈ 0.125) EstimatedRTT=(1−α)∗EstimatedRTT+α∗SampleRTT(α≈0.125)
- D e v R T T = ( 1 − β ) ∗ D e v R T T + β ∗ ∣ S a m p l e R T T − E s t i m a t e d R T T ∣ DevRTT = (1-β) * DevRTT + β * |SampleRTT - EstimatedRTT| DevRTT=(1−β)∗DevRTT+β∗∣SampleRTT−EstimatedRTT∣ (β ≈ 0.25)
- T i m e o u t I n t e r v a l ( R T O ) = E s t i m a t e d R T T + 4 ∗ D e v R T T TimeoutInterval (RTO) = EstimatedRTT + 4 * DevRTT TimeoutInterval(RTO)=EstimatedRTT+4∗DevRTT
- 初始
RTO
通常为1秒。 - Karn算法: 不对重传报文段的ACK进行
SampleRTT
采样,避免重传歧义。
- 超时事件: 若在
RTO
内未收到确认,则认为报文段丢失,进行重传。 - 超时间隔加倍 (Exponential Backoff): 每次超时重传后,
RTO
会翻倍,以应对持续的网络拥塞或长时间的延迟。而不是由EstimatedRTT和DevRTT推断出的值。
快速重传 (Fast Retransmit):
超时重传的周期相对较长,可以用快速重传来解决超时重发的时间等待问题。
不以时间为驱动,而以数据为驱动进行重传- 当发送方收到3个或更多针对同一数据的冗余ACK (Duplicate ACKs) 时,不等超时定时器到期就立即重传该数据之后的报文段。
- 原理: 冗余ACK表明接收方收到了后续的报文段,但之前的某个报文段丢失了。
选择确认 (SACK - Selective Acknowledgment):
快速重传解决了超时问题,还有一个问题就是重传的时候是重传一个还是重传所有问题,所以引出了SACK.
小林coding
- 作为TCP选项,允许接收方明确告知发送方哪些不连续的数据块已收到。
- 发送方可以据此只重传真正丢失的报文段,而不是从第一个丢失点开始的所有数据。
校验和 (Checksum): 用于检测报文段在传输过程中是否发生比特错误。
TCP的差错恢复机制可以看作是 GBN (Go-Back-N) 和 SR (Selective Repeat) 协议的混合体。 基础是累计确认(类似GBN),但通过缓存失序报文段和SACK选项(类似SR)来提高效率。
四、流量控制 (Flow Control)
- 目的: 防止发送方发送数据过快,超出接收方处理能力,导致接收方缓存溢出。
- 机制: 使用接收窗口 (
rwnd
)。- 接收方在其发送给发送方的报文段的
rwnd
字段中通告其当前可用的缓存空间。 - 发送方维护的有效窗口 (Effective Window) =
min(cwnd, rwnd) - (LastByteSent - LastByteAcked)
。 - 发送方发送的数据量不能超过接收方的
rwnd
。
- 接收方在其发送给发送方的报文段的
- 零窗口问题与持续探测:
- 当
rwnd=0
时,接收方缓存已满。 - 若此时接收方没有数据要发给发送方,发送方可能不知道何时
rwnd
变非零。 - TCP规范要求: 当接收窗口为0时,发送方会周期性地发送只包含1字节数据的探测报文段 (Persist Timer)。接收方必须确认这些探测报文,并在确认中包含当前的
rwnd
值。
- 当
五、拥塞控制 (Congestion Control)
目的: 防止过多数据注入网络,导致网络拥塞(路由器缓存溢出、高延迟、丢包)。
机制: TCP发送方通过维护一个拥塞窗口 (
cwnd
) 来动态调整发送速率。- 实际发送速率受限于
min(cwnd, rwnd) / RTT
。
- 实际发送速率受限于
拥塞感知:
- 丢包事件 (Packet Loss): 主要的拥塞信号。
- 超时 (Timeout)
- 3个冗余ACK (Fast Retransmit)
- ECN (Explicit Congestion Notification): 网络辅助机制,路由器可以在IP头中标记拥塞,接收方通过TCP头反馈给发送方。
- 丢包事件 (Packet Loss): 主要的拥塞信号。
TCP拥塞控制算法 (核心思想:AIMD - Additive Increase, Multiplicative Decrease):
慢启动 (Slow Start):
- 初始
cwnd = 1 MSS
(或2-4 MSS,根据RFC)。 - 每收到一个ACK,
cwnd
增加1 MSS (指数增长,每RTT翻倍)。 - 目的: 快速探测网络可用带宽。
- 当
cwnd
达到慢启动阈值 (ssthresh
) 时,或发生丢包时,转换状态。
- 初始
拥塞避免 (Congestion Avoidance):
- 当
cwnd >= ssthresh
时进入。 cwnd
每RTT增加1 MSS (线性增长)。- 目的: 谨慎增加发送速率,避免迅速再次拥塞。
- 当
拥塞处理 (对丢包事件的反应):
- 超时丢包 (TCP Tahoe/Reno):
ssthresh = cwnd / 2
cwnd = 1 MSS
- 重新进入慢启动。
- 3个冗余ACK (TCP Reno):
ssthresh = cwnd / 2
cwnd = ssthresh + 3 MSS
(快速重传后,inflate窗口以应对冗余ACK)- 进入快速恢复 (Fast Recovery) 状态。
- 超时丢包 (TCP Tahoe/Reno):
快速恢复 (Fast Recovery - TCP Reno):
图片引用自:小林coding- 每收到一个额外的冗余ACK,
cwnd
增加1 MSS (继续inflate)。 - 当收到对丢失报文段的新ACK时:
cwnd = ssthresh
- 进入拥塞避免状态。
- 若发生超时,则按超时丢包处理(
cwnd=1 MSS
,进入慢启动)。
ssthresh (Slow Start Threshold):
- 初始值可以很高。
- 发生拥塞时,通常设置为当前
cwnd
的一半。
拥塞控制算法的演进:
- Tahoe: 早期版本,任何丢包都导致
cwnd=1
。 - Reno: 引入快速重传和快速恢复。
- NewReno: 改进Reno对一个窗口内多个丢包的处理。
- SACK-TCP: 结合SACK选项,更精确重传,改进恢复。
- CUBIC: Linux默认,基于三次函数,高BDP网络友好。
- BBR (Bottleneck Bandwidth and Round-trip propagation time): Google开发,不依赖丢包,基于带宽和RTT估计。
- Tahoe: 早期版本,任何丢包都导致
公平性: 理想情况下,多个TCP连接共享瓶颈链路时,应能公平分配带宽。但UDP等无拥塞控制的协议可能挤占TCP流量。
六、TCP连接管理 (Connection Management)
三次握手 (Three-Way Handshake): 建立连接。
- (已在第一部分详述)
- SYN洪泛攻击 (SYN Flood Attack): 攻击者发送大量SYN请求但不完成握手,耗尽服务器资源。防御机制包括SYN Cookies、防火墙限制等。
四次挥手 (Four-Way Handshake): 拆除连接。
- TCP连接是全双工的,每个方向的关闭都是独立的。
- 过程简述 (A主动关闭,B被动关闭):
- A
FIN(seq=x)
-> B (A表示不再发送数据) - B
ACK(ack=x+1)
-> A (B确认收到A的FIN)
- 此时B可能仍有数据要发送给A,连接处于半关闭 (Half-Close) 状态。
- B
FIN(seq=y)
-> A (B表示也不再发送数据) - A
ACK(ack=y+1)
-> B (A确认收到B的FIN)
- A
- 状态机与
TIME_WAIT
状态:- 主动关闭方在发送最后一个ACK后会进入
TIME_WAIT
状态。 - 目的:
- 确保最后一个ACK能到达对方(若丢失,对方会重传FIN,
TIME_WAIT
状态的A可以重发ACK)。 - 确保网络中所有与此连接相关的旧的、延迟的报文段都已消失,防止它们干扰后续使用相同四元组(源IP、源端口、目标IP、目标端口)的新连接。持续时间通常为 2*MSL (Maximum Segment Lifetime)。
- 确保最后一个ACK能到达对方(若丢失,对方会重传FIN,
- 主动关闭方在发送最后一个ACK后会进入
七、其他重要机制与概念
Nagle算法 (RFC 896):
- 目的: 减少网络中的小报文段("糊涂窗口综合症"的一种表现),提高网络效率。
- 机制: 如果有已发送但未确认的数据,并且新产生的数据小于MSS,则缓存这些小数据,直到收到ACK或积累到MSS再发送。
- 影响: 可能引入小延迟,对某些交互式应用(如Telnet、SSH按键回显)不利,可禁用 (TCP_NODELAY socket选项)。
延迟ACK (Delayed ACKs):
- 目的: 减少纯ACK报文的数量,提高效率。
- 机制: 接收方不必为每个收到的数据段立即发送ACK,可以等待一小段时间(如200ms)或等待有数据要捎带时再发送。
- 影响: 可能引入小延迟。与Nagle算法同时作用时,可能导致更明显的延迟。
字节流服务 (Byte-Stream Service):
- TCP向上层应用提供的是一个无边界的、有序的字节流。应用层写入的数据可能会被TCP分割成多个报文段,或者多个小写入可能会被TCP合并成一个报文段。应用层需要自己处理消息边界。
最大报文段长度 (MSS - Maximum Segment Size):
- TCP报文段中数据部分的最大长度。
- 通常根据路径MTU(最大传输单元)减去TCP/IP首部长度来设置,以避免IP层分片。
- 在三次握手期间通过TCP选项协商。
TCP状态机 (TCP State Machine):
- 描述TCP连接从建立到关闭过程中的各种状态及其转换。
- 关键状态:
CLOSED
,LISTEN
,SYN_SENT
,SYN_RCVD
,ESTABLISHED
,FIN_WAIT_1
,FIN_WAIT_2
,CLOSE_WAIT
,LAST_ACK
,TIME_WAIT
。
八、TCP的局限性与未来
- 队头阻塞 (Head-of-Line Blocking): 在单个TCP连接中,一个报文段的丢失会阻塞后续所有报文段的交付,即使它们已正确到达。
- 连接建立延迟: 三次握手至少引入1个RTT的延迟。
- 对某些应用不理想: 实时音视频、游戏等对低延迟要求高、能容忍少量丢包的应用,可能更适合UDP。
- 移动网络和高丢包环境下的表现: 传统的TCP拥塞控制将丢包视为拥塞,可能在无线等非拥塞丢包环境下表现不佳。
- 演进方向 (例如QUIC):
- QUIC运行在UDP之上,解决了TCP的一些问题:
- 多路复用流,避免队头阻塞。
- 更快的连接建立 (0-RTT或1-RTT)。
- 集成的TLS加密。
- 独立的流级拥塞控制。
- QUIC运行在UDP之上,解决了TCP的一些问题:
滑动窗口直接看小林coding的写的很清楚了,参考以下图例,cs144 lab0、lab1
参考
- 自顶向下第八版
- 小林coding
- kiprey.github.io