第一次握手:客户端请求建立连接,发送同步报文(SYN=1),同时随机一个seq=x作为初始序列号,进入SYN_SENT状态,等待服务器确认
第二次握手:服务端收到请求报文,如果同意建立连接,则发送同步确认报文 (SYN=1,ACK=1),确认号 ack=x+1,随机一个seq=y作为初始序列号,进入SYN_RECV状态
第三次握手:客户端收到服务端确认,发送确认报文(ACK=1),确认号ack=y+1,序列号seq=x+1,双方都进入ESTABLISHED状态,完成三次握手
步骤 | 发送方 | SYN | ACK | seq(序列号) | ack(确认号) | 状态变化 |
---|---|---|---|---|---|---|
① 第一次握手 | 客户端 → 服务器 | 1 | 0 | x (随机初始值) |
无 | SYN_SENT (客户端等待服务器回应) |
② 第二次握手 | 服务器 → 客户端 | 1 | 1 | y (随机初始值) |
x+1 |
SYN_RECV (服务器等待客户端确认) |
③ 第三次握手 | 客户端 → 服务器 | 0 | 1 | x+1 |
y+1 |
ESTABLISHED (连接建立成功) |
SYN
主要用于建立连接,ACK
用于确认数据,seq
和 ack
共同维护数据的顺序和完整性。
seq
(序列号)?在 TCP 通信中,seq
(序列号) 的作用是标识发送的数据字节流的顺序,确保数据包能按序到达,并且防止丢失和重复。
seq
具体作用在 TCP 三次握手 时,每一方都会选择一个随机的序列号 seq
,作为自己即将发送数据流的起始编号,具体流程如下:
1️⃣ (第一次握手,客户端 → 服务器)
x
,并发送:SYN=1, seq=x
x
,从 x
号字节开始传输数据。2️⃣ (第二次握手,服务器 → 客户端)
y
,回复:SYN=1, ACK=1, seq=y, ack=x+1
seq=y
:表示服务器选择的初始序列号是 y
,数据从 y
号字节开始传输。ack=x+1
:告诉客户端:「我已经收到你的 SYN 请求,并确认你的序列号 x
,下一次你发送数据时,请从 x+1
开始。」3️⃣ (第三次握手,客户端 → 服务器)
ACK=1, seq=x+1, ack=y+1
seq=x+1
:客户端告诉服务器:「我将从 x+1
号字节开始发送数据。」ack=y+1
:告诉服务器:「我已经收到你的 SYN 请求,并确认你的序列号 y
,你下次发送数据时,从 y+1
开始。」✅ 至此,三次握手完成,双方建立可靠连接,可以开始数据传输。
TCP 通过 seq
(序列号)保证数据按顺序传输,不丢失,不重复,是 TCP 可靠传输的核心机制。
在三次握手中,seq
主要用于确定双方通信的起始位置,并避免历史连接数据混淆。
如果只有两次握手,可能会导致历史连接请求的影响,从而引发服务器资源浪费或错误连接。
假设仅用两次握手:
这样,服务器会一直等待客户端的数据,导致资源浪费,甚至出现**半连接(Half-Open Connection)**问题。因此,第三次握手(ACK) 能确保客户端确实收到了服务器的回应,并正式建立连接,避免这种情况。
情景:在双方两次握手即可建立连接的情况下,假设客户端发送 A 报文段请求建立连接,由于网络原因造成 A 暂时无法到达服务器,服务器接收不到请求报文段就不会返回确认报文段。
客户端在长时间得不到应答的情况下重新发送请求报文段 B,这次 B 顺利到达服务器,服务器随即返回确认报文并进入 ESTABLISHED 状态,客户端在收到 确认报文后也进入 ESTABLISHED 状态,双方建立连接并传输数据,之后正常断开连接。
此时姗姗来迟的 A 报文段才到达服务器,服务器随即返回确认报文并进入 ESTABLISHED 状态,但是已经进入 CLOSED 状态的客户端无法再接受确认报文段,更无法进入 ESTABLISHED 状态,这将导致服务器长时间单方面等待,造成资源浪费。
三次握手让双方确认通信能力:
所谓确认通信能力就是双方都确认对方和自己的收发能力正常
在第一次握手 A确认自己可以发,B确认A可以发,自己可以收,发
第二次,A确认自己可以发可以收,B可以收可以发,但是此时B还不能确认A可以收,
所以有了第三次,此时,双方都确认对方和自己的收发能力正常
如果是四次握手:
额外的一次确认没有实际意义,因为三次握手已经确保了双方的通信能力。
额外的开销会降低效率,没有必要增加额外的握手步骤。
结论:
两次握手不够安全,可能导致服务器误认为连接已建立。
三次握手足够确保双方都能正常通信,并防止历史连接问题。
四次握手是多余的,没有额外的安全或可靠性提升,因此 TCP 采用三次握手。
此时,客户端就进入了 TIME-WAIT 状态。注意此时客户端到 TCP 连接还没有释放,必须经过 2*MSL(最长报文段寿命)的时间后,才进入 CLOSED 状态。而服务端只要收到客户端发出的确认,就立即进入 CLOSED 状态。可以看到,服务端结束 TCP 连接的时间要比客户端早一些。
服务器在收到客户端的 FIN 报文段后,可能还有一些数据要传输,所以不能马上关闭连接,但是会做出应答,返回 ACK 报文段.
接下来可能会继续发送数据,在数据发送完后,服务器会向客户单发送 FIN 报文,表示数据已经发送完毕,请求关闭连接。服务器的ACK和FIN一般都会分开发送,从而导致多了一次,因此一共需要四次挥手。
主要有两个原因:
确保 ACK 报文能够到达服务端,从而使服务端正常关闭连接。
第四次挥手时,客户端第四次挥手的 ACK 报文不一定会到达服务端。服务端会超时重传 FIN/ACK 报文,此时如 果客户端已经断开了连接,那么就无法响应服务端的二次请求,这样服务端迟迟收不到 FIN/ACK 报文的确认,就无法正常断开连接。
MSL 是报文段在网络上存活的最长时间。客户端等待 2MSL 时间,也就是两次报文发送时间:
假设客户端在传最后的ACK报文,但是没有发给服务端,然后服务端在这1MSL中没有接到客户端的ACK报文,于是再次发送FIN报文耗时1MSL打到客户端,刚好2MSL,客户端可以重新回应,而不至于在不等这2MSL的情况下直接关机
如果服务端重发的 FIN 没有成功地在 2MSL 时间里传给客户端,服务端则会继续超时重试直到断开连接。
防止已失效的连接请求报文段出现在之后的连接中。
TCP 要求在 2MSL 内不使用相同的序列号。客户端在发送完最后一个 ACK 报文段后,再经过时间 2MSL,就可以保证本连接持续的时间内产生的所有报文段都从网络中消失。这样就可以使下一个连接中不会出现这种旧的连接请求报文段。或者即使收到这些过时的报文,也可以不处理它。
服务端: 短时间内关闭了大量的Client连接,就会造成服务器上出现大量的TIME_WAIT连接,严重消耗着服务器的资源,此时部分客户端就会显示连接不上。
客户端:客户端TIME_WAIT过多,就会导致端口资源被占用,因为端口就65536个,被占满就会导致无法创建新的连接。
解决办法:
服务端:设置 SO_REUSEADDR 套接字选项来避免 TIME_WAIT状态,此套接字选项告诉内核,即使此端口正忙(处于TIME_WAIT状态),也请继续并
重用它。
客户端:动态选择不同的源端口,避免因 TIME-WAIT 过多导致端口耗尽
https://github.com/0voice