运输层¶
约 4327 个字 14 行代码 4 张图片 预计阅读时间 22 分钟
运输层的作用是实现进程之间的可靠传输
总的来说,这一层的协议主要有两个,TCP与UDP.
-
UDP (用户数据报协议): 无连接, 尽最大努力交付, 面向报文. 适用于对实时性要求高、对丢包不敏感的应用(如视频会议).
-
TCP (传输控制协议): 面向连接, 提供可靠交付, 面向字节流. 适用于要求数据准确无误的应用(如文件传输、邮件).
不同操作系统上,进程标识符的格式是不一样的.为了解决这个问题,运输层使用了端口号,来统一标识进程.
端口号¶
端口号是一个16位的整数, 范围是 0 ~ 65535. 它用于在主机中唯一标识一个应用进程.
端口号只具有本地意义, 即端口号只是为了标志本计算机应用层中的各进程. 在因特网中不同计算机的相同端口号是没有联系的.
端口号的分类¶
端口号分为以下三类:
-
熟知端口号 (Well-known Ports):
- 范围: 0 ~ 1023
- 由 IANA (互联网号码分配机构) 指派给 TCP/IP 最重要的一些应用程序.
- 例如: FTP (21), SSH (22), Telnet (23), SMTP (25), DNS (53), HTTP (80), HTTPS (443).
-
登记端口号 (Registered Ports):
- 范围: 1024 ~ 49151
- 为没有熟知端口号的应用程序使用. 使用这类端口号必须在 IANA 登记, 以防止重复.
- 例如: MySQL (3306), Redis (6379), Tomcat (8080).
-
客户端口号 (Ephemeral Ports / Dynamic Ports):
- 范围: 49152 ~ 65535
- 仅在客户进程运行时才动态选择, 因此又称为短暂端口号.
- 当服务器进程收到客户进程的报文时, 就知道了客户进程所使用的端口号, 因而可以把数据发送给客户进程. 通信结束后, 该端口号可供其他客户进程以后使用.
访问网站的全过程
sequenceDiagram
participant User as 用户主机
participant DNS as 本地DNS服务器
participant Web as 网站服务器
Note over User: 1. 用户在浏览器输入 www.example.com
%% DNS 解析过程 (UDP)
rect rgb(240, 248, 255)
Note right of User: DNS解析阶段 (通常使用UDP 53端口)
User->>User: 检查本地hosts和缓存
User->>DNS: 发送DNS查询请求 (UDP)
Note right of User: 源端口:随机, 目的端口:53
DNS->>DNS: 递归查询 (Root -> TLD -> Authoritative)
DNS-->>User: 返回IP地址 (例如 1.2.3.4)
end
%% HTTP 请求过程 (TCP)
rect rgb(255, 250, 240)
Note right of User: HTTP请求阶段 (使用TCP 80/443端口)
%% TCP 三次握手
User->>Web: SYN (建立连接请求)
Note right of User: 源端口:随机, 目的端口:80
Web-->>User: SYN + ACK (确认并同意)
User->>Web: ACK (确认)
Note over User, Web: TCP连接建立完成 (三次握手)
%% 数据传输
User->>Web: HTTP GET 请求
Web-->>User: HTTP 响应 (网页内容)
%% TCP 四次挥手 (简化)
User->>Web: FIN (断开连接请求)
Web-->>User: ACK
Web-->>User: FIN
User->>Web: ACK
Note over User, Web: TCP连接释放 (四次挥手)
end
TCP¶
TCP的首部格式¶
-
源端口 (Source Port): 16位。发送方的端口号。
-
目的端口 (Destination Port): 16位。接收方的端口号。
-
序号 (Sequence Number): 32位。本报文段所发送数据的第一个字节的序号。在建立连接时,SYN报文段会携带一个初始序号。
-
确认号 (Acknowledgment Number): 32位。期望收到对方下一个报文段的第一个字节的序号。若确认号为N,则表示N-1及以前的数据都已正确收到。
-
数据偏移 (Data Offset): 4位。表示TCP报文段的数据部分的起始位置距离TCP报文段的起始位置有多远,即TCP首部长度。单位是4字节(32位字)。因此,TCP首部最大长度为 (2^4 - 1) * 4 = 15 * 4 = 60字节。
-
保留 (Reserved): 6位。保留为今后使用,目前必须置为0。
-
标志位 (Flags): 6位。
- URG (Urgent): 紧急指针有效。
- ACK (Acknowledgment): 确认号有效。
- PSH (Push): 立即发送数据。
- RST (Reset): 重置连接。
- SYN (Synchronize): 同步序号,用于建立连接。
- FIN (Finish): 终止发送方数据,用于释放连接。
-
窗口大小 (Window Size): 16位。接收方告知发送方自己还能接收多少字节的数据,用于流量控制。
-
校验和 (Checksum): 16位。对整个TCP报文段(包括首部和数据)进行校验,由发送方计算,接收方验证。
计算方法:
-
将 TCP 首部中检验和字段的值置为 0.
-
将 伪首部、TCP 首部 以及 数据载荷 这三部分划分成若干个 2 字节的字.
-
若这三部分的总长度不是偶数个字节, 则在最后添加 1 个 "全 0" 字节.
-
对划分出的全部 2 字节的字进行 反码算数运算求和, 并将求和结果 取反码.
-
将结果写入 TCP 首部中的检验和字段.
校验和计算举例 (简化)
假设我们有 3 个 4 位二进制数需要通过反码算术运算求和 (实际 TCP 使用 16 位, 原理相同):
数据:
0111(7),1011(11),1100(12)步骤 1: 前两个数相加
将进位加到末尾 (回卷, End-around carry):步骤 2: 加上第三个数
步骤 3: 取反码 (Checksum)
什么是伪首部 (Pseudo Header)
伪首部 是在计算校验和时,临时加在 TCP 首部前面的 12 个字节。它 不 会被发送到网络上,只是为了计算用的。
目的: 验证数据包是否真的到达了正确的 IP 地址 (防止 IP 层路由错误)。
结构 (12 字节):
字段 长度 说明 源 IP 地址 4 字节 发送方的 IP 目的 IP 地址 4 字节 接收方的 IP 保留位 1 字节 置 0 协议号 1 字节 TCP 的协议号是 6 TCP 长度 2 字节 TCP 首部 + 数据的长度 校验和包含了伪首部,意味着 TCP 不仅校验了数据本身,还校验了 IP 地址,增加了可靠性。
-
-
紧急指针 (Urgent Pointer): 16位。当URG标志位为1时有效,指出紧急数据在当前报文段中的偏移量。
-
选项 (Options): 长度可变,最长40字节。例如,最大报文段长度(MSS)、窗口扩大因子、时间戳等。
-
填充 (Padding): 长度可变。用于确保TCP首部长度是4字节的整数倍。
TCP的运输管理¶
TCP的连接与传输分为三个阶段:
-
三次握手建立连接
-
传输数据
-
四次挥手释放连接
三次握手建立连接 (Three-Way Handshake)¶
TCP 建立连接的过程叫做握手,握手需要在客户和服务器之间交换三个 TCP 报文段。
图解
sequenceDiagram
participant C as 客户端 (Client)
participant S as 服务器 (Server)
Note left of C: CLOSED (关闭)
Note right of S: LISTEN (监听)
Note over C,S: 第一次握手: 建立连接请求
C->>S: SYN=1, seq=x
Note left of C: SYN-SENT (同步已发送)
Note right of S: 收到 SYN
Note over C,S: 第二次握手: 确认并同意连接
S->>C: SYN=1, ACK=1, seq=y, ack=x+1
Note right of S: SYN-RCVD (同步收到)
Note left of C: 收到 SYN+ACK
Note over C,S: 第三次握手: 确认确认
C->>S: ACK=1, seq=x+1, ack=y+1
Note left of C: ESTABLISHED (已建立连接)
Note right of S: ESTABLISHED (已建立连接)
-
第一次握手 (Client -> Server):
-
动作: 客户端发送 连接请求报文段.
-
标志位:
SYN=1(表示这是一个连接请求),ACK=0. - 序号:
seq=x(随机生成一个初始序号 ISN).这个初始序号会作为服务器端记录收到字节流的初始序号
-
状态变迁: 客户端进入 SYN-SENT 状态.
-
注意: SYN 报文段不能携带数据,但要消耗掉一个序号.
-
-
第二次握手 (Server -> Client):
- 动作: 服务器收到请求后,如果同意连接,则发送 确认报文段.
-
标志位:
SYN=1(同步),ACK=1(确认). -
序号:
seq=y(服务器也随机生成自己的初始序号). -
确认号:
ack=x+1(期望收到客户端的下一个字节序号). -
状态变迁: 服务器进入 SYN-RCVD 状态.
-
注意: 这个报文段也不能携带数据,也要消耗掉一个序号.
-
第三次握手 (Client -> Server):
- 动作: 客户端收到确认后,还要向服务器给出确认.
-
标志位:
ACK=1(表示确认有效),SYN=0. -
序号:
seq=x+1. -
确认号:
ack=y+1. -
状态变迁: 客户端进入 ESTABLISHED 状态. 服务器收到后也进入 ESTABLISHED 状态.
-
注意: ACK 报文段可以携带数据,如果不携带数据则不消耗序号.
为什么要三次握手?(Why 3-way?)
主要为了 防止已失效的连接请求报文段突然又传送到了服务器 (Preventing Stale Duplicates),从而产生错误。
-
场景: 客户端发的第一个连接请求
SYN在网络中滞留了,客户端超时重传了新的SYN并建立了连接。 -
问题: 等连接释放后,那个滞留的旧
SYN终于到达服务器。 - 后果 (如果是两次握手): 服务器收到旧
SYN就误以为客户端又要建连接,于是发出确认并建立连接,一直等待客户端发数据,导致服务器资源白白浪费。 - 三次握手的作用: 服务器收到旧
SYN并回复SYN+ACK后,客户端发现这个确认号不对 (不是自己刚才发的),就不会理睬 (不发第三次ACK)。服务器收不到确认,就知道连接没建立。
四次挥手释放连接 (Four-Way Handshake for Termination)¶
数据传输结束后,通信的双方都可以释放连接。现在客户端和服务器都处于 ESTABLISHED 状态。
图解
sequenceDiagram
participant C as 客户端 (Client)
participant S as 服务器 (Server)
Note over C,S: 第一次挥手: 客户端请求断开
C->>S: FIN=1, seq=u
Note left of C: FIN-WAIT-1 (终止等待1)
Note right of S: 收到 FIN
Note over C,S: 第二次挥手: 服务器确认断开
S->>C: ACK=1, seq=v, ack=u+1
Note right of S: CLOSE-WAIT (关闭等待)
Note left of C: FIN-WAIT-2 (终止等待2)
Note right of S: 此时服务器仍可发送剩余数据
Note over C,S: 第三次挥手: 服务器请求断开
S->>C: FIN=1, ACK=1, seq=w, ack=u+1
Note right of S: LAST-ACK (最后确认)
Note left of C: 收到 FIN
Note over C,S: 第四次挥手: 客户端确认断开
C->>S: ACK=1, seq=u+1, ack=w+1
Note left of C: TIME-WAIT (时间等待)
Note right of S: CLOSED (关闭)
Note left of C: 等待 2MSL 后
Note left of C: CLOSED (关闭)
-
第一次挥手 (Client -> Server):
- 动作: 客户端发送 连接释放报文段,停止发送数据。
- 标志位:
FIN=1. - 序号:
seq=u(等于前面已传送数据的最后一个字节序号 + 1). - 状态: 客户端进入 FIN-WAIT-1 状态.
-
第二次挥手 (Server -> Client):
- 动作: 服务器收到 FIN,发出确认。
- 标志位:
ACK=1. - 序号:
seq=v. - 确认号:
ack=u+1. - 状态: 服务器进入 CLOSE-WAIT 状态. 客户端收到后进入 FIN-WAIT-2 状态.
- 注意: 此时连接处于 半关闭 (Half-Close) 状态。客户端不再发数据,但服务器如果还有数据要发,客户端仍需接收。
-
第三次挥手 (Server -> Client):
- 动作: 服务器数据发完后,发送连接释放报文段。
- 标志位:
FIN=1,ACK=1. - 序号:
seq=w. - 确认号:
ack=u+1(重复上次的确认). - 状态: 服务器进入 LAST-ACK 状态.
-
第四次挥手 (Client -> Server):
- 动作: 客户端收到 FIN,发出确认。
- 标志位:
ACK=1. - 序号:
seq=u+1. - 确认号:
ack=w+1. - 状态: 客户端进入 TIME-WAIT 状态,服务器收到后进入 CLOSED 状态.
为什么 Client 主要等待 2MSL? (TIME-WAIT)
-
保证最后一个 ACK 报文段能够到达服务器: 如果第四次挥手丢失, 服务器会超时重传 FIN. 客户端必须保留这段时间来处理可能的重传.
-
防止"已失效的连接请求报文段"出现在本连接中: 等待 2MSL (Maximum Segment Lifetime), 可以使本连接持续时间内所产生的所有报文段都从网络中消失.
TCP 流量控制¶
TCP 采用 滑动窗口 (Sliding Window) 机制来实现流量控制,目的是防止发送方发送数据过快,接收方来不及处理。在TCP首部里就有窗口字段,
接收窗口 (rwnd)¶
-
机制: 在 TCP 报文段首部的 窗口大小 字段中,接收方告诉发送方自己当前的接收缓存还可以接收多少字节数据。
-
作用: 发送方维护一个 发送窗口,其大小不能超过接收方告知的 接收窗口 (rwnd)。
-
动态调整: 随着接收方缓存被应用程序读取或填满,rwnd 会动态变化。
接受方在发送数据时,会带上rwnd,这个值的含义要结合ack的序号一起来看.它代表了接受方在收到ack-1的那一刻,还能接收多少字节的数据.
发送窗口 (Send Window)¶
发送窗口实际上就是\(min\{rwnd, cwnd\}\),其中\(cwnd\)是拥塞窗口.
发送方的发送窗口由 前沿 (Front Edge) 和 后沿 (Rear Edge) 定义。发送窗口内的数据是 允许发送 的 (包括已发送但未收到确认的,和允许发送但尚未发送的)。
-
后沿 (Rear Edge / Left Edge):
- 含义: 表示已发送但尚未收到确认的最小序号。
- 移动: 只能向 前 (右) 移动。
- 触发: 当收到接收方发来的 新的确认 (New ACK) 时,后沿向前移动。
- 不动: 如果没有收到新的确认,后沿不动。
-
前沿 (Front Edge / Right Edge):
- 含义: 表示允许发送的最大序号 + 1。
- 计算:
前沿 = 后沿 + 接收窗口(rwnd)。 - 移动: 通常向 前 (右) 移动。
- 触发:
- 收到新的 ACK (后沿前移),且 rwnd 不变或变大。
- 后沿不动,但接收方通知的 rwnd 变大。
- 不动:
- 没收到新 ACK,且 rwnd 不变。
- 收到新 ACK (后沿前移),但 rwnd 变小了相同幅度 (这种情况不常见)。
- 向后 (左) 移动 (不建议):
- 这被称为 窗口收缩 (Window Retraction)。即接收方突然缩小了 rwnd,导致前沿不得不左移 (甚至跑到后沿左边)。TCP 标准强烈不建议这样做。
死锁场景 (Deadlock)¶
-
接收方缓存满了,发送
rwnd = 0(零窗口通知) 给发送方。 -
发送方收到后,停止发送数据,等待接收方通知新的窗口大小。
-
接收方应用程序读取了数据,缓存有了空间。接收方发送一个新的报文段通知
rwnd = 400。 -
死锁: 如果这个通知报文段 在网络中丢失了。
-
发送方还在苦苦等待接收方的非零窗口通知。
-
接收方以为发送方收到了通知,正在等待发送方发数据。
- 双方互相等待,产生死锁。
-
为了解决上述死锁,TCP 为每个连接设有一个 坚持定时器。
-
触发: 当发送方收到一个
rwnd = 0的报文段时,就启动该定时器。 -
动作: 定时器到期后,发送方会发送一个 1字节 的探测报文段 (零窗口探测报文, Zero Window Probe, ZWP),询问接收方现在的窗口大小。
-
如果接收方回复仍是
rwnd = 0,则重置定时器,继续等待。 -
如果接收方回复
rwnd > 0,则死锁解除,恢复数据发送。
-
TCP 拥塞控制¶
拥塞控制 与 流量控制 的区别:
-
流量控制: 是点对点的通信量控制, 主要解决接收方来不及接收的问题
-
拥塞控制: 是全局性的过程, 涉及到所有的主机、路由器, 主要解决网络中链路过载的问题.
TCP 拥塞控制基于 窗口 机制, 发送方维护一个 拥塞窗口 (cwnd, congestion window).
发送方实际能发送的数据量 = min(rwnd, cwnd). (取接收窗口和拥塞窗口的最小值).
TCP 拥塞控制主要有四种算法:
什么是 RTT (往返时间) 与 传输轮次?
- RTT (Round-Trip Time): 从发送方发送数据开始,到发送方收到接收方确认 (ACK) 为止所经历的时间。
- 传输轮次 (Transmission Round): 拥塞控制中常把 "往返时间 RTT" 称为一个 "传输轮次"。
- 也就是把 当前拥塞窗口 cwnd 内所允许发送的全部报文段,都发送出去,并且 收到了对已发送的最后一个字节的确认,就算经过了一个传输轮次。
慢开始 (Slow Start)¶
这里,我们说cwnd的1,指的是1个MSS大小.比如,TCP报文段的最大长度为1KB,那么cwnd=1时,表示每次最多发送1KB的数据.
-
原理: 一开始不发送大量数据, 而是先探测一下网络的拥塞程度, 由小到大逐渐增大拥塞窗口.
-
增长规则: 每收到一个 ACK, cwnd 加 1 (意味着每个RTT轮次, cwnd 加倍, 呈指数增长).
-
门限 (ssthresh): 当 cwnd 达到 慢开始门限 (ssthresh) 时, 改用拥塞避免算法.
拥塞避免 (Congestion Avoidance)¶
-
原理: 让拥塞窗口 cwnd 缓慢增大.
-
增长规则: 每经过一个往返时间 RTT, cwnd 就加 1 (呈 线性增长), 而不是加倍.
-
拥塞发生时: 只要出现超时重传 (认为网络拥塞了):
ssthresh设为当前 cwnd 的一半.cwnd重置为 1.- 重新执行 慢开始.
快重传 (Fast Retransmit)¶
- 机制: 接收方每收到一个失序的报文段, 就立即发出重复确认 (Duplicate ACK).
- 触发: 发送方只要连续收到 3个重复确认 (3-Duplicate ACKs), 就应当立即重传对方尚未收到的报文段, 而不必等待超时重传定时器.
- 作用: 大大提高了信道利用率.
快恢复 (Fast Recovery)¶
- 触发: 当发送方连续收到 3 个重复确认时 (说明是个别报文丢失, 网络可能没拥塞).
- 动作:
ssthresh设为当前 cwnd 的一半.- 关键点:
cwnd设为新的 ssthresh 值 (而不是 1). - 直接执行 拥塞避免 算法 (线性增长).
TCP 超时重传时间的选择¶
TCP 采用一种自适应算法,根据测量的往返时间 RTT 动态地计算重传时间 RTO (Retransmission Time-Out)。
加权平均往返时间 (SRTT, Smoothed RTT)¶
第一个SRTT的值来自于第一个RTT,即SRTT = RTT
RTT 的值会随网络状况波动。TCP 不直接使用某一次的 SampleRTT,而是计算 加权平均往返时间 SRTT。
- 公式: \(SRTT = (1 - \alpha) \times Old\_SRTT + \alpha \times New\_RTT\)
- 参数: 标准推荐值 \(\alpha = 0.125\) (即 1/8)。
- 意义: 这样得出的 SRTT 更加平滑,不会因为一次偶然的 RTT 波动而剧烈变化。
RTT 的偏差的加权平均值 (RTTD, Deviation RTT)¶
第一个RTTD的值来自于第一个RTT的一半,即RTTD = RTT/2
为了如实反映 RTT 的波动程度,TCP 还需要计算 RTT 的偏差 (即 RTT 波动了多少)。
-
公式: \(RTT_D = (1 - \beta) \times Old\_RTT_D + \beta \times |SRTT - New\_RTT|\)
-
参数: 标准推荐值 \(\beta = 0.25\) (即 1/4)。
超时重传时间 (RTO) 的计算¶
RTO 应略大于 SRTT。根据 RFC 6298 建议,RTO 的计算公式为:
Karn 算法 (Karn's Algorithm)¶
在重传报文段时,如果发送方收到了确认,就无法知道这个确认是对 刚才重传的报文 的确认,还是对 原先发送的报文 的确认。这会导致 RTT 测量不准。
Karn 算法规则:
-
只要报文段重传了,就不采用其计算 SRTT (即不采样这次的 RTT)。
-
报文段每重传一次,就把 RTO 增大一些 (典型的做法是取新的 RTO = 旧的 RTO \(\times\) 2, 这称为 指数退避)。



