TCP (传输控制协议) 深度详解:可靠、面向连接的字节流基石
TCP (Transmission Control Protocol),即传输控制协议,是 Internet 协议套件 (Internet Protocol Suite) 中的核心协议之一,位于传输层。它提供可靠的、面向连接的、基于字节流的全双工通信服务。TCP 协议确保了数据能够按序、无差错地从一个应用进程传输到另一个应用进程。
核心思想:在不可靠的 IP 层之上,通过一系列机制(如序号、确认、重传、流量控制、拥塞控制)构建一个高度可靠、有序的数据传输通道。
一、为什么需要 TCP?
在网络模型中,IP 协议(网络层)提供了尽力而为 (best-effort) 的数据报服务,它不保证数据包的到达、顺序或不重复。然而,大多数应用(如网页浏览、文件传输、电子邮件)都需要一个可靠的数据传输服务。TCP 正是为了弥补 IP 协议的这些不足而设计的,它在应用层和网络层之间提供了一个可靠的、虚拟的通信管道。
TCP 的主要职责包括:
- 可靠性:确保数据无损、无错地到达目的地。
- 有序性:确保数据包以正确的顺序交付给接收方。
- 流量控制:防止发送方发送数据过快,导致接收方缓冲区溢出。
- 拥塞控制:防止发送方发送数据过快,导致网络整体性能下降甚至崩溃。
- 面向连接:在数据传输前,发送方和接收方需要建立连接;传输结束后,需要释放连接。
二、TCP 报文段结构
TCP 报文段 (segment) 是 TCP 层进行数据传输的基本单位。每个 TCP 报文段都包含一个 TCP 头部和数据部分。
1 | +-------------------------------------------------------------+ |
2.1 头部字段详解
- 源端口号 (Source Port, 16 bits):发送方的端口号,用于标识发送数据的应用进程。
- 目的端口号 (Destination Port, 16 bits):接收方的端口号,用于标识接收数据的应用进程。
- 序号 (Sequence Number, 32 bits):
- TCP 是面向字节流的,一个 TCP 连接传输的数据流的每个字节都编有序号。
- 序号字段的值指的是本报文段所发送的数据的第一个字节的序号。
- 用于解决网络包乱序和重复问题。
- 确认号 (Acknowledgement Number, 32 bits):
- 期望收到对方下一个报文段的第一个字节的序号。
- 若确认号为
N,则表示发送方已成功接收到序号N-1之前的所有数据。 - 只有当 ACK 标志位为 1 时,确认号字段才有效。
- 数据偏移 (Data Offset, 4 bits):
- 表示 TCP 头部长度,以 4 字节为单位。
- 最小值为 5 (无选项字段),最大值为 15 (即 15 * 4 = 60 字节)。
- 保留 (Reserved, 6 bits):保留为今后使用,目前必须置为 0。
- 标志位 (Flags, 6 bits):
- URG (Urgent):紧急指针有效。表示此报文段包含紧急数据,应优先处理。
- ACK (Acknowledgement):确认号字段有效。这是最常用的标志。
- PSH (Push):推送功能。请求接收方立即将数据交付给应用层,而不必等待缓冲区满。
- RST (Reset):复位连接。用于异常终止连接或拒绝非法的报文段。
- SYN (Synchronize):同步序号。用于在建立连接时同步序号,即发起连接请求。
- FIN (Finish):终止连接。用于释放一个连接。
- 窗口大小 (Window Size, 16 bits):
- 发送方用于通知接收方,自己当前可接受的数据量 (字节数)。
- 用于实现流量控制,防止发送方发送速度过快导致接收方来不及处理。
- 校验和 (Checksum, 16 bits):
- 由发送方计算,接收方验证。
- 用于检测报文段在传输过程中是否出现差错。
- 覆盖整个 TCP 报文段 (头部和数据)。
- 紧急指针 (Urgent Pointer, 16 bits):
- 只有当 URG 标志位为 1 时才有效。
- 指出紧急数据在报文段中的偏移量,配合序号字段指示紧急数据结束的位置。
- 选项 (Options, 可变):
- 常见选项:最大报文段长度 (MSS)、窗口扩大因子 (Window Scale)、时间戳 (Timestamps)、选择性确认 (SACK) 等。
- 填充 (Padding, 可变):确保 TCP 头部长度是 4 字节的整数倍。
三、TCP 的核心机制
3.1 面向连接:三次握手建立连接 (Three-Way Handshake)
在数据传输之前,TCP 需要在客户端和服务器之间建立一个逻辑连接。这个过程通过三次握手完成。
sequenceDiagram
participant Client
participant Server
Client->>Server: SYN (seq=x) (第一次握手:客户端发送连接请求)
Server->>Client: SYN-ACK (seq=y, ack=x+1) (第二次握手:服务器确认并发送自己的连接请求)
Client->>Server: ACK (ack=y+1) (第三次握手:客户端确认,连接建立)
步骤详解:
- SYN (同步):客户端发送一个
SYN报文段,包含一个随机生成的初始序号 (ISN)x。客户端进入SYN_SENT状态。 - SYN-ACK (同步-确认):服务器收到
SYN后,发送一个SYN-ACK报文段。其中包含服务器的 ISNy,同时ACK字段置 1,确认号为x+1(确认已收到客户端的SYN)。服务器进入SYN_RCVD状态。 - ACK (确认):客户端收到
SYN-ACK后,发送一个ACK报文段。其中ACK字段置 1,确认号为y+1(确认已收到服务器的SYN)。客户端和服务器都进入ESTABLISHED状态,连接建立成功,可以开始数据传输。
为什么是三次握手而不是两次?
主要是为了防止历史连接请求(旧的重复连接请求报文段)干扰。如果只有两次握手,客户端发送的连接请求在网络中滞留,服务器收到后会回复 ACK。如果客户端在等待 ACK 时超时重发了 SYN,并且之前的 SYN 报文段在网络中游荡了一段时间后也到达服务器,服务器会误认为客户端要建立新的连接,发送 ACK。但此时客户端可能已经关闭了连接,或者正在与另一个服务器通信。这会导致服务器单方面建立一个实际上不存在的连接,浪费资源。三次握手可以确保客户端和服务器都明确对方能够正常收发数据,避免了这种“死锁”情况。
Golang 示例:简单 TCP 客户端和服务器
1 | // server.go |
1 | // client.go |
3.2 可靠数据传输:序号、确认与重传
TCP 通过以下机制确保数据可靠传输:
- 序号 (Sequence Number):
- TCP 为每个发送的字节分配一个序号。报文段中的序号是该报文段第一个数据字节的序号。
- 接收方使用序号来识别重复的数据包,并按照正确的顺序重新组装乱序的数据包。
- 确认 (Acknowledgement - ACK):
- 接收方成功收到数据后,会发送一个确认报文段 (
ACK标志位为 1),其中包含期望接收的下一个字节的序号(即已收到数据最后一个字节的序号加 1)。这被称为累积确认 (Cumulative ACK)。 - 例如,如果收到的 ACK 号为
N,表示N-1及其之前的所有数据都已成功接收。
- 接收方成功收到数据后,会发送一个确认报文段 (
- 重传 (Retransmission):
- 超时重传 (Timeout Retransmission):发送方发送一个报文段后,会启动一个定时器 (RTO - Retransmission Timeout)。如果在定时器到期前没有收到对应的 ACK,发送方会认为该报文段丢失,并重传该报文段。RTO 是动态计算的,通常基于往返时间 (RTT - Round Trip Time)。
- 快速重传 (Fast Retransmit):当发送方收到三个重复的 ACK (即收到了四次对同一个数据包的 ACK) 时,会立即重传丢失的报文段,而不需要等待 RTO 超时。这通常意味着网络中某个报文段丢失了,但后续的报文段到达了接收方。
3.3 流量控制 (Flow Control)
流量控制是为了防止发送方发送数据过快,导致接收方的缓冲区溢出。TCP 使用滑动窗口协议 (Sliding Window Protocol) 来实现流量控制。
- 窗口大小 (Window Size):TCP 头部中的 16 位窗口字段,表示接收方当前可接收的字节数。
- 接收方通告窗口 (Receiver Advertised Window - rwnd):接收方在 ACK 报文段中向发送方通告自己的接收缓冲区可用空间大小,即
rwnd。发送方被限制只能发送不超过rwnd字节的数据。 - 零窗口探测 (Zero Window Probe):如果接收方通告的窗口大小为 0,发送方会停止发送数据。为了防止接收方窗口一直为 0 导致死锁,发送方会周期性地发送零窗口探测报文段,询问接收方窗口是否已打开。
3.4 拥塞控制 (Congestion Control)
拥塞控制是为了防止过多的数据注入到网络中,导致网络过载,从而降低吞吐量甚至造成网络崩溃。拥塞控制是全局性的,关注整个网络的承载能力;流量控制是端到端的,关注接收方的处理能力。
TCP 拥塞控制主要包括以下四个算法:
- 慢启动 (Slow Start):
- 连接建立初期,为了避免立即向网络中发送大量数据导致拥塞,发送方会从一个很小的拥塞窗口 (Congestion Window -
cwnd) 开始。 - 初始
cwnd通常为 1-10 MSS (Maximum Segment Size)。 - 每当收到一个 ACK,
cwnd就会翻倍式增长 (指数增长)。 - 直到
cwnd达到慢启动阈值 (ssthresh)。 - 公式表示:
cwnd = cwnd * 2(每个 RTT)
- 连接建立初期,为了避免立即向网络中发送大量数据导致拥塞,发送方会从一个很小的拥塞窗口 (Congestion Window -
- 拥塞避免 (Congestion Avoidance):
- 当
cwnd达到ssthresh后,慢启动阶段结束,进入拥塞避免阶段。 - 此时
cwnd增长方式变为线性增长:每收到一个 ACK,cwnd增加1/cwnd个 MSS。或者说,每个 RTT 周期cwnd增加 1 MSS。 - 公式表示:
cwnd = cwnd + MSS / cwnd(每个 ACK) 或cwnd = cwnd + MSS(每个 RTT)
- 当
- 快速重传 (Fast Retransmit):
- 前面提到,当发送方收到三个重复 ACK 时,立即重传丢失的报文段。
- 这表明网络可能发生了轻微的拥塞,但并非严重到需要慢启动。
- 快速恢复 (Fast Recovery):
- 与快速重传结合使用。当触发快速重传时(收到 3 个重复 ACK):
- 将
ssthresh设置为当前cwnd的一半。 - 将
cwnd设置为ssthresh+ 3 * MSS (因为收到了 3 个重复 ACK,每个 ACK 表示一个报文段离开了网络)。 - 每收到一个重复 ACK,
cwnd增加 1 MSS。 - 当收到新的 ACK (确认了重传的数据包),
cwnd设置为ssthresh,进入拥塞避免阶段。
- 将
- 如果发生超时事件(而不是 3 个重复 ACK),则认为拥塞更严重:
ssthresh设置为cwnd的一半。cwnd设置为 1 MSS。- 重新进入慢启动阶段。
- 与快速重传结合使用。当触发快速重传时(收到 3 个重复 ACK):
这些算法的协同工作,使得 TCP 能够在网络拥塞时自动调整发送速率,从而维护网络的稳定性和效率。
3.5 全双工通信
TCP 连接是全双工的,意味着数据可以在两个方向上同时传输。每个方向都拥有独立的发送和接收缓冲区。
3.6 面向字节流
TCP 不关心应用层发送的数据块边界,它将所有数据视为一个无结构的字节流。当应用进程向 TCP 传输数据时,TCP 会将数据切分成合适的报文段大小 (通常受限于 MSS),然后向下传递给 IP 层。接收方 TCP 收到数据后,将其放入接收缓冲区,应用进程可以以任意大小读取这些字节。
四、TCP 连接的释放:四次挥手 (Four-Way Handshake)
当数据传输完成,双方需要关闭 TCP 连接,这个过程称为四次挥手。
sequenceDiagram
participant Client
participant Server
Client->>Server: FIN (seq=u) (第一次挥手:客户端通知没有数据要发送了)
Server->>Client: ACK (ack=u+1) (第二次挥手:服务器确认收到客户端的FIN)
Note over Server,Client: 服务器可能还有数据要发送,可以继续发送数据
Server->>Client: FIN (seq=v, ack=u+1) (第三次挥手:服务器发送完数据,通知关闭)
Client->>Server: ACK (ack=v+1) (第四次挥手:客户端确认收到服务器的FIN)
Note over Client: 客户端进入 TIME_WAIT 状态,等待 2MSL 后关闭
Note over Server: 服务器收到 ACK 后关闭
步骤详解:
- FIN (终止):客户端应用进程通知 TCP 准备关闭连接。客户端发送一个
FIN报文段,包含一个序号u。客户端进入FIN_WAIT_1状态。 - ACK (确认):服务器收到
FIN后,发送一个ACK报文段,确认号为u+1。服务器进入CLOSE_WAIT状态。此时,服务器仍可以向客户端发送数据(半关闭状态)。 - FIN (终止):当服务器也没有数据要发送时,服务器应用进程也通知 TCP 准备关闭连接。服务器发送一个
FIN报文段,包含一个序号v。服务器进入LAST_ACK状态。 - ACK (确认):客户端收到服务器的
FIN后,发送一个ACK报文段,确认号为v+1。客户端进入TIME_WAIT状态。- 客户端在
TIME_WAIT状态会等待2MSL(Max Segment Lifetime - 最大报文段寿命) 的时间,以确保服务器收到了最后的 ACK 报文段,并处理网络中可能存在的延迟或重传的报文段。 - 2MSL 之后,客户端才真正关闭连接。
- 服务器收到最后的 ACK 报文段后,立即关闭连接。
- 客户端在
为什么是四次挥手而不是三次?
因为 TCP 是全双工的,每个方向的传输都需要独立关闭。客户端发送 FIN 只是表示它没有数据要发送了,但服务器可能还有数据要发送给客户端。因此,服务器会先回复一个 ACK (表示收到客户端的关闭请求),然后等待自己发送完所有数据后,再发送 FIN 请求关闭自己的发送方向。所以,关闭通常需要两个 FIN 和两个 ACK,共四次挥手。
五、TCP 与 UDP 对比
| 特性 | TCP (传输控制协议) | UDP (用户数据报协议) |
|---|---|---|
| 连接类型 | 面向连接 (Connection-Oriented) | 无连接 (Connectionless) |
| 可靠性 | 可靠 (Reliable):有确认、重传、序号 | 不可靠 (Unreliable):无确认、无重传 |
| 有序性 | 有序 (Ordered):保证数据按序到达 | 无序 (Unordered):数据可能乱序到达 |
| 数据边界 | 面向字节流 (Byte Stream):无消息边界 | 面向数据报 (Datagram-Oriented):保留消息边界 |
| 流量控制 | 有 (使用滑动窗口) | 无 |
| 拥塞控制 | 有 (慢启动、拥塞避免、快速重传、快速恢复) | 无 |
| 头部开销 | 较大 (至少 20 字节) | 较小 (8 字节) |
| 速度 | 较慢 (因可靠性机制和连接建立/关闭开销) | 较快 (传输效率高) |
| 适用场景 | 文件传输、网页浏览 (HTTP/HTTPS)、电子邮件 (SMTP/POP3) | 实时应用 (视频会议、VoIP)、DNS 查询、网络管理 (SNMP) |
六、安全性考虑
尽管 TCP 自身提供了可靠性,但它并非天生安全。一些常见的攻击包括:
- SYN Flood 攻击:攻击者发送大量伪造源 IP 的 SYN 报文段,使得服务器创建大量半开连接,耗尽资源。
- TCP RST 攻击:攻击者伪造 RST 报文段,强制终止正常的 TCP 连接。
- 会话劫持 (Session Hijacking):攻击者通过窃听 TCP 序号和确认号,伪装成合法用户,劫持现有连接。
- IP Spoofing (IP 欺骗):攻击者伪造源 IP 地址,但 TCP 的三次握手和序号机制使得伪造完整的连接相对困难。
为了增强 TCP 连接的安全性,通常会在应用层或传输层之上使用加密协议,如 TLS/SSL (Transport Layer Security / Secure Sockets Layer),它提供数据加密、身份认证和消息完整性验证。
七、总结
TCP 协议是互联网的基石之一,其复杂而精巧的机制在不可靠的网络环境中构建了可靠、高效的通信服务。它通过序号、确认、重传确保数据无错有序;通过滑动窗口实现流量控制;通过慢启动、拥塞避免等算法实现拥塞控制。理解 TCP 的工作原理对于网络编程、性能优化以及问题排查至关重要。
