深入解析TCP核心机制:连接管理、流量与拥塞控制
目录
一、三次握手与四次挥手:可靠连接的建立与终止
1. 三次握手 - 建立连接
为什么是三次?
2. 四次挥手 - 终止连接
为什么需要TIME_WAIT状态?
二、流量控制与滑动窗口:解决收发速度不匹配
核心机制:滑动窗口协议
滑动窗口过程:
流程步骤:
三、拥塞控制:解决网络路径拥堵问题
1. 慢启动
2. 拥塞避免
3. 快重传
4. 快恢复
TCP协议是互联网的可靠传输保障,其核心在于如何建立/终止连接、如何确保数据有序无误地送达,以及如何在复杂的网络环境中高效传输。理解这些机制,是掌握网络编程和进行网络故障排查的关键。
一、三次握手与四次挥手:可靠连接的建立与终止
TCP是面向连接的协议,通信前必须建立一条虚拟链路,通信后要有序地释放它。
1. 三次握手 - 建立连接
目的是确认双方的发送和接收能力正常,并同步初始序列号(ISN)。
-
第一次握手 (SYN=1, seq=x):客户端发送SYN包(同步位SYN=1)到服务器,并随机生成一个初始序列号(ISN)
x
。客户端进入SYN_SENT
状态。 -
第二次握手 (SYN=1, ACK=1, seq=y, ack=x+1):服务器收到SYN包,必须确认客户的SYN。同时自己也发送一个SYN包(SYN=1)以及确认号(ACK=1, ack=x+1)。服务器随机生成自己的ISN
y
。服务器进入SYN_RCVD
状态。 -
第三次握手 (ACK=1, ack=y+1):客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ACK=1, ack=y+1)。此包发送完毕后,客户端和服务器都进入
ESTABLISHED
状态,完成连接建立。
为什么是三次?
主要是为了防止已失效的连接请求报文突然又传到了服务器,从而产生错误。
假设只有两次握手:一个延迟的旧SYN请求到达服务器,服务器会误以为客户端要建立新连接并响应,但客户端实际并无此意,导致服务器空等,浪费资源。三次握手机制下,客户端不会对那个延迟的确认包再进行确认,连接便无法建立。
2. 四次挥手 - 终止连接
TCP连接是全双工的,因此每个方向必须单独地进行关闭。
-
第一次挥手 (FIN=1, seq=u):客户端(主动关闭方)发送一个FIN包,用来关闭客户端到服务器的数据传送。客户端进入
FIN_WAIT_1
状态。 -
第二次挥手 (ACK=1, ack=u+1):服务器收到FIN后,发回一个ACK包,确认序号为收到的序号加1。服务器进入
CLOSE_WAIT
状态,客户端收到后进入FIN_WAIT_2
状态。此时,从客户端到服务器的连接已关闭,但服务器到客户端的连接仍然可用。 -
第三次挥手 (FIN=1, seq=v, ack=u+1):服务器准备好关闭连接时,发送一个FIN包给客户端。服务器进入
LAST_ACK
状态。 -
第四次挥手 (ACK=1, ack=v+1):客户端收到FIN后,发回ACK确认,并进入TIME_WAIT 状态。服务器收到ACK后,关闭连接,进入
CLOSED
状态。客户端等待2MSL
(最大报文段生存时间)后,也关闭连接。
为什么需要TIME_WAIT状态?
-
可靠地终止连接:确保客户端最后的ACK能到达服务器。如果ACK丢失,服务器会重发FIN,客户端在 TIME_WAIT 状态下能再次响应ACK。
-
让旧连接的所有报文在网络中消逝:防止之前连接的延迟报文段被之后新建的、相同四元组(源IP、源端口、目的IP、目的端口)的连接错误接收。
二、流量控制与滑动窗口:解决收发速度不匹配
如果发送方发送得太快,接收方的缓冲区可能会被填满,导致数据丢失。流量控制就是解决发送方和接收方速度不匹配的问题。
核心机制:滑动窗口协议
-
接收窗口 (rwnd):接收方通过TCP首部中的窗口大小字段,告知发送方自己当前接收缓冲区还有多少可用空间。这个值动态变化。
-
工作原理:
-
发送方维护一个发送窗口,其大小不能超过接收方通告的接收窗口大小。
-
窗口内的报文段可以连续发送出去,而无需等待确认。
-
每当收到一个确认ACK,发送窗口就向前“滑动”。
-
接收方处理完数据,腾出新的缓冲区后,会在下一次ACK中更新窗口大小通告给发送方。
-
举个例子:接收方说:“我的窗口现在大小是3000字节”。发送方就可以连续发送3000字节的数据,而不用每发一个包就停下来等ACK。这极大地提高了信道利用率。
滑动窗口过程:
流程步骤:
-
【客户端请求】
客户端向服务端发送请求数据报文。 -
【服务端发送数据 80B】
服务端收到请求报文后,发送确认报文和80
字节的数据。-
结果:可用窗口
Usable
减少为120
字节。 -
发送指针
SND.NXT
向右偏移80
字节,指向321
(下次发送数据的序列号)。
-
-
【客户端确认 80B】
客户端收到80
字节数据后:-
接收窗口向右移动
80
字节,接收指针RCV.NXT
指向321
(期望的下一个报文序列号)。 -
客户端发送确认报文给服务端。
-
-
【服务端发送数据 120B】
服务端再次发送120
字节数据。-
结果:可用窗口耗尽,
Usable = 0
,服务端无法继续发送数据。
-
-
【客户端确认 120B】
客户端收到120
字节数据后:-
接收窗口向右移动
120
字节,RCV.NXT
指向441
。 -
客户端发送确认报文给服务端。
-
-
【服务端收到 80B 的确认】
服务端收到对80
字节数据的确认报文后:-
已发送未确认指针
SND.UNA
向右偏移,指向321
。 -
结果:可用窗口
Usable
增大到80
。
-
-
【服务端收到 120B 的确认】
服务端收到对120
字节数据的确认报文后:-
已发送未确认指针
SND.UNA
向右偏移,指向441
。 -
结果:可用窗口
Usable
增大到200
。
-
-
【服务端发送数据 160B】
服务端可以继续发送,于是发送了160
字节的数据。-
结果:发送指针
SND.NXT
指向601
。 -
可用窗口
Usable
减少到40
。
-
-
【客户端确认 160B】
客户端收到160
字节数据后:-
接收窗口向右移动
160
字节,RCV.NXT
指向601
。 -
客户端发送确认报文给服务端。
-
-
【服务端收到 160B 的确认】
服务端收到对160
字节数据的确认报文后:-
发送窗口向右移动
160
字节,SND.UNA
指针指向601
。 -
结果:可用窗口
Usable
增大至200
。
-
三、拥塞控制:解决网络路径拥堵问题
如果网络本身已经拥堵,此时大量重发数据只会让情况恶化。拥塞控制就是防止过多的数据注入到网络中,避免网络中的路由器或链路过载。它是一个全局性的过程。
TCP的拥塞控制主要基于四个核心算法:
1. 慢启动
-
连接刚建立时,发送方并不清楚网络状况,需要一种探针机制。
-
拥塞窗口(cwnd)从1个MSS(最大报文段长度)开始。
-
每收到一个ACK,cwnd就指数增长(cwnd *= 2)。这就像启动时猛踩油门,速度增长很快。
-
当
cwnd
达到慢启动门限(ssthresh) 时,进入下一个阶段。
2. 拥塞避免
-
当窗口变大后,指数增长变得危险。此时进入线性增长阶段。
-
规则变为:每经过一个往返时延(RTT),cwnd只加1(即每收到一个ACK,cwnd增加 1/cwnd)。
-
这个过程是在小心翼翼地探测网络的最大可用容量。
3. 快重传
-
传统超时重传太慢。快重传要求接收方每收到一个失序报文段就立即发出重复确认。
-
如果发送方连续收到3个重复的ACK,就认为该报文段已经丢失,立即重传它,而不必等待超时计时器到期。
4. 快恢复
-
在快重传之后触发,而非直接进入慢启动。
-
发送方认为网络状况尚可(因为还能收到3个ACK),只是个别包丢失。
-
将
ssthresh
设置为当前cwnd
的一半,并将cwnd
设置为新的ssthresh
值(有的实现会加3),然后直接进入拥塞避免阶段。
拥塞控制的整体过程可以概括为:加法增大乘法减小,即在不拥塞时线性增加窗口,在出现拥塞时(超时或收到3个重复ACK)将窗口乘性减半。