12TCP/IP详解(卷1:协议)

TCP

传输控制协议

TCP连接三次握手四次挥手流量控制

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第12章:TCP(Transmission Control Protocol)

传输控制协议

一、导读

1.1 本章学习目标

第12章是全书最重要的章节,深入讲解TCP(传输控制协议)——Internet上最重要的传输层协议。TCP提供可靠的、面向连接的字节流服务,是HTTP、FTP、SMTP、SSH等几乎所有应用层协议的传输基础。理解TCP的工作原理,是编写高效网络程序、诊断网络问题、优化网络性能的基石。

本章的核心学习目标包括:

  • 掌握TCP报文段的完整格式:每个字段的含义和作用
  • 深入理解三次握手建立连接:为什么是三次?每个报文的作用?
  • 深入理解四次挥手释放连接:为什么是四次?TIME_WAIT状态的作用?
  • 掌握TCP的可靠传输机制:序号、确认、重传、校验和
  • 深入理解流量控制:滑动窗口机制如何防止发送方淹没接收方
  • 深入理解拥塞控制:慢启动、拥塞避免、快重传、快恢复

1.2 为什么本章如此重要

TCP是Internet上使用最广泛的传输层协议。当你浏览网页、下载文件、收发邮件、远程登录时,底层几乎都在使用TCP。TCP的设计精妙之处在于:它在不可靠的IP服务之上,实现了可靠的数据传输。

理解TCP对于以下方面至关重要:

  • 网络编程:理解socket API的行为、处理连接异常
  • 性能优化:理解延迟的来源、如何调优TCP参数
  • 故障诊断:分析连接建立失败、数据传输中断、性能下降等问题
  • 协议分析:使用tcpdump/Wireshark分析TCP交互

1.3 核心问题引导

在阅读本章时,请带着以下问题思考:

TCP三次握手中,如果第三次ACK丢失了,会发生什么?为什么需要三次而不是两次?

TCP的序号和确认号是如何工作的?它们如何保证数据的可靠传输?

滑动窗口的大小是固定的吗?它是如何动态调整的?

TIME_WAIT状态为什么要持续2MSL?如果TIME_WAIT太多会有什么问题?

TCP的拥塞控制是如何感知网络拥塞的?丢包和超时分别触发什么行为?

Nagle算法解决了什么问题?它有什么副作用?


二、核心概念详解

2.1 TCP的特点

TCP提供以下服务:

特性说明
面向连接通信前必须建立连接(三次握手)
点对点每条连接只有两个端点
可靠交付保证数据无差错、不丢失、不重复、按序到达
全双工双方可以同时发送和接收
字节流TCP把数据视为无结构的字节流

2.2 TCP报文段格式

0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          Source Port          |        Destination Port       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        Sequence Number                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Acknowledgment Number                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Data |       |C|E|U|A|P|R|S|F|                               |
| Offset|  Res. |W|C|R|C|S|S|Y|I|          Window               |
|       |       |R|E|G|K|H|T|N|N|                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Checksum            |         Urgent Pointer        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Options (if Data Offset > 5)                |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           Data                                |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

2.2.1 关键字段

字段长度说明
Source Port16位源端口号
Destination Port16位目的端口号
Sequence Number32位序号(发送数据的第一个字节的编号)
Acknowledgment Number32位确认号(期望收到对方的下一个字节编号)
Data Offset4位头部长度(以4字节为单位)
Window16位窗口大小(接收方通告的接收窗口)
Checksum16位校验和(覆盖头部、数据和伪头部)
Urgent Pointer16位紧急指针

2.2.2 控制标志

标志名称说明
SYNSynchronize建立连接
ACKAcknowledge确认号有效
FINFinish释放连接
RSTReset重置连接
PSHPush请求推送数据
URGUrgent紧急指针有效

2.3 TCP连接管理

2.3.1 三次握手

TCP使用三次握手(three-way handshake)建立连接:

客户端                              服务器
  │                                     │
  │  ① SYN (seq=x)                      │
  │  客户端进入SYN_SENT状态              │
  │ ──────────────────────────────────→ │
  │                                     │  服务器进入SYN_RCVD状态
  │  ② SYN+ACK (seq=y, ack=x+1)        │
  │ ←────────────────────────────────── │
  │                                     │
  │  客户端进入ESTABLISHED状态           │
  │  ③ ACK (ack=y+1)                    │
  │ ──────────────────────────────────→ │
  │                                     │  服务器进入ESTABLISHED状态
  │                                     │
  │         连接建立,可以传输数据        │

为什么需要三次握手?

核心原因:同步双方的初始序号(ISN),并确认双方的发送和接收能力。

  • 第一次:客户端证明"我能发送",服务器证明"我能接收"
  • 第二次:服务器证明"我能发送",客户端证明"我能接收"
  • 第三次:服务器确认"我知道你能接收"

如果只有两次握手,服务器无法确认客户端收到了自己的SYN+ACK。如果该报文丢失,服务器以为连接已建立,但客户端认为没有。服务器后续发送的数据会被客户端视为无效报文。

初始序号(ISN)的选择:ISN不是随机的0,而是基于时钟的伪随机数。这是为了防止"旧连接的报文被新连接误收"(segment confusion)。

2.3.2 四次挥手

TCP使用四次挥手(four-way handshake)释放连接。由于TCP是全双工的,每个方向的关闭是独立的:

主动方                              被动方
  │                                     │
  │  ① FIN (seq=u)                      │
  │  主动方进入FIN_WAIT_1               │
  │ ──────────────────────────────────→ │
  │                                     │  被动方进入CLOSE_WAIT
  │  ② ACK (ack=u+1)                    │
  │ ←────────────────────────────────── │
  │                                     │
  │  主动方进入FIN_WAIT_2               │
  │         (被动方可能继续发送数据)     │
  │                                     │
  │  ③ FIN (seq=w)                      │
  │ ←────────────────────────────────── │
  │                                     │  被动方进入LAST_ACK
  │  主动方进入TIME_WAIT                 │
  │  ④ ACK (ack=w+1)                    │
  │ ──────────────────────────────────→ │
  │                                     │  被动方进入CLOSED
  │  (等待2MSL后)                      │
  │  主动方进入CLOSED                    │

为什么需要四次而不是三次?

因为TCP是全双工的。收到FIN只表示对方不再发送数据,但本方还可以继续发送。所以ACK和FIN不能合并(不像三次握手中SYN+ACK可以合并)。

TIME_WAIT状态:

主动关闭方进入TIME_WAIT状态,持续2MSL(Maximum Segment Lifetime,通常为60秒,所以TIME_WAIT持续约120秒)。

原因:

确保最后的ACK到达:如果ACK丢失,被动方会重发FIN,主动方需要重发ACK

等待网络中旧报文消亡:防止新连接收到旧连接的报文

2.4 TCP的可靠传输

2.4.1 序号和确认号

TCP为每个字节编号。序号(Sequence Number)表示本报文段数据的第一个字节的编号。确认号(Acknowledgment Number)表示期望收到对方的下一个字节的编号。

发送方发送:seq=100, 数据100字节(字节100-199)
接收方确认:ack=200(期望收到字节200,表示100-199已全部收到)

2.4.2 超时重传

发送方发送数据后启动定时器,如果在超时时间内没有收到确认,就重传数据。

超时时间(RTO, Retransmission Timeout)的计算:

  • 基于RTT(往返时间)的估计
  • 使用Jacobson/Karels算法动态调整
  • RTO = SRTT + max(G, 4×RTTVAR)

2.4.3 快速重传

如果收到3个重复ACK(即同一个ACK收到4次),发送方立即重传丢失的报文段,而不等待超时。

发送方发送:seq=100, seq=200, seq=300, seq=400
接收方收到:100, 300, 400(200丢失)
接收方发送:ack=200, ack=200, ack=200(3个重复ACK)
发送方收到3个重复ack=200 → 立即重传seq=200

2.5 流量控制

流量控制防止发送方发送过快,导致接收方缓冲区溢出。

2.5.1 滑动窗口

接收方在TCP头部中的Window字段通告自己的接收窗口大小(rwnd)。发送方维护一个"发送窗口",窗口内的数据可以发送但尚未确认:

发送窗口:
|---已发送未确认---|---可以发送---|---不能发送---|
                  ↑              ↑              ↑
               窗口左沿       窗口右沿        序号空间

窗口大小 = rwnd - (已发送未确认的字节数)

当窗口为0时,发送方停止发送数据,但会定期发送探测报文(zero window probe)。

2.6 拥塞控制

拥塞控制防止过多的数据注入到网络中,导致路由器或链路过载。

2.6.1 拥塞窗口

TCP维护一个拥塞窗口(cwnd),发送窗口的大小取 rwnd 和 cwnd 的最小值:

发送窗口 = min(rwnd, cwnd)

2.6.2 慢启动(Slow Start)

连接建立初期,cwnd从1个MSS开始,每收到一个ACK,cwnd加1。因此cwnd呈指数增长:1→2→4→8→16→...

当cwnd达到慢启动阈值(ssthresh)时,转入拥塞避免阶段。

2.6.3 拥塞避免(Congestion Avoidance)

cwnd超过ssthresh后,每个RTT只增加1个MSS(线性增长)。

2.6.4 快重传和快恢复(Fast Retransmit & Fast Recovery)

收到3个重复ACK时:

立即重传丢失的报文(快重传)

ssthresh = cwnd/2

cwnd = ssthresh + 3(快恢复)

收到丢失报文的ACK后,cwnd = ssthresh

2.6.5 超时事件

超时意味着严重的拥塞:

ssthresh = cwnd/2

cwnd = 1 MSS

重新开始慢启动

2.7 TCP的性能优化

2.7.1 Nagle算法

当发送缓冲区中有少量数据且未确认时,Nagle算法延迟发送,等待更多数据积累,减少小报文的数量。

问题:可能导致延迟增加。TCP_NODELAY 选项可以禁用Nagle算法。

2.7.2 延迟确认

接收方收到数据后不立即发送ACK,等待一小段时间(通常200ms),看是否有数据要一起发送。

2.7.3 MSS协商

三次握手时,双方通告自己的MSS(Maximum Segment Size)。MSS = MTU - IP头部(20) - TCP头部(20) = 1460(以太网)。


三、实战案例

3.1 案例一:使用tcpdump观察三次握手

bash
sudo tcpdump -i eth0 -nn 'tcp port 80'

输出示例:

12:34:56.789 IP 192.168.1.100.49152 > 93.184.216.34.80:
  Flags [S], seq 1234567890, win 65535, options [mss 1460,sackOK,TS val 100 ecr 0,nop,wscale 7]

12:34:56.815 IP 93.184.216.34.80 > 192.168.1.100.49152:
  Flags [S.], seq 987654321, ack 1234567891, win 65535, options [mss 1460,sackOK,TS val 200 ecr 100,nop,wscale 7]

12:34:56.816 IP 192.168.1.100.49152 > 93.184.216.34.80:
  Flags [.], ack 987654322, win 512, options [nop,nop,TS val 101 ecr 200]

分析:

  • [S]:SYN,seq=1234567890(客户端ISN)
  • [S.]:SYN+ACK,seq=987654321(服务器ISN),ack=1234567891(确认客户端ISN+1)
  • [.]:ACK,ack=987654322(确认服务器ISN+1)
  • mss 1460:MSS协商为1460字节
  • wscale 7:窗口缩放因子为7(窗口可以扩大到65535×128)

3.2 案例二:使用ss查看TCP连接状态

bash
ss -tunap
# State    Recv-Q  Send-Q  Local Address:Port   Peer Address:Port
# ESTAB    0       0       192.168.1.100:49152  93.184.216.34:80
# TIME-WAIT 0      0       192.168.1.100:49153  93.184.216.34:80
# CLOSE-WAIT 1      0       192.168.1.100:49154  93.184.216.34:80

常见状态:

  • ESTABLISHED:连接已建立
  • SYN-SENT:已发送SYN,等待SYN+ACK
  • SYN-RECV:已收到SYN,已发送SYN+ACK
  • FIN-WAIT-1:已发送FIN,等待ACK
  • FIN-WAIT-2:已收到ACK,等待对方FIN
  • CLOSE-WAIT:已收到对方FIN,等待本方关闭
  • TIME-WAIT:已发送最后的ACK,等待2MSL
  • LAST-ACK:已发送FIN,等待最后的ACK

3.3 案例三:使用Wireshark分析TCP流

Wireshark的"Follow TCP Stream"功能可以重组完整的TCP数据流,显示应用层数据。还可以查看每个报文段的序号、确认号、窗口大小、RTT等详细信息。


四、常见误区与难点

4.1 误区一:"TCP传输的是报文段,不是字节流"

TCP协议层操作的是字节流,没有"消息边界"。发送方调用两次write各发送100字节,接收方可能一次read读到200字节,也可能分两次各读100字节。应用层需要自己处理消息边界。

4.2 误区二:"CLOSE_WAIT是正常状态"

大量CLOSE_WAIT通常说明应用程序没有正确关闭连接(收到FIN后没有调用close)。这是程序bug。

4.3 难点:理解拥塞控制的四个阶段

慢启动→拥塞避免→快重传→快恢复,四个阶段在不同条件下切换。建议通过Wireshark的TCP序列图来可视化观察cwnd的变化。


五、本章小结

第12章深入讲解了TCP协议:

TCP报文段包含源/目的端口、序号、确认号、窗口大小、控制标志等字段。

三次握手建立连接,同步双方ISN;四次挥手释放连接,双方独立关闭。

可靠传输通过序号、确认、超时重传、快速重传实现。

流量控制使用滑动窗口机制,接收方通告窗口大小。

拥塞控制包括慢启动、拥塞避免、快重传、快恢复四个阶段。

TIME_WAIT持续2MSL,确保最后ACK到达和旧报文消亡。


六、思考与练习

思考题

如果TCP三次握手的第二步(SYN+ACK)丢失了,客户端会怎么处理?服务器会怎么处理?最终连接能建立吗?

TCP的拥塞控制算法中,为什么超时后cwnd回到1而不是ssthresh?这体现了什么设计思想?

如果一台服务器有大量TIME_WAIT状态的连接,可能是什么原因?如何解决?

在卫星链路(RTT≈500ms,带宽100Mbps)上,TCP的吞吐量受什么限制?如何优化?

练习题

使用tcpdump捕获一次完整的TCP三次握手和四次挥手,记录每个报文段的序号、确认号和标志位。

使用Wireshark分析一个TCP流,绘制序号-时间图,观察拥塞控制的行为。

编写一个简单的TCP客户端和服务器,使用strace跟踪系统调用,观察connect/send/recv/close的交互过程。

12/13TCP
0%