TCP三次握手与四次挥手
TCP标志位
TCP的标志位代表当前请求的目的。分为6种:
- SYN:发送/同步标志,用来建立连接,和ACK标志位搭配使用。A请求与B建立连接时,SYN=1,ACK=0;B确认与A建立连接时,SYN=1,ACK=1
- ACK:确认标志,表示确认收到请求
- PSH:表示推送操作,指数据包到达接收端以后,不对其进行队列处理,而是尽可能的将数据交给应用程序处理
- FIN:结束标志,表示关闭一个TCP连接
- RST:重置复位标志,用于复位对应的TCP连接
- URG:紧急标志,用于保证TCP连接不被中断,并且督促中间层设备尽快处理
TCP序列号和确认号
作用
序列号和确认号是TCP实现可靠传输的依赖。TCP使用序列号来记录发送数据包的顺序。TCP传送一个数据包后,只有在指定时间里收到这个包的确认信息,才会将其从队列中删除,否则会重新发送该数据包。 对于接收方而言,通过数据分段中的序列号可以保证数据能够按照正常的顺序进行重组。
序列号
- 在SYN标志位置1时,表示当前连接的初始序列号(Initial Sequence Number, ISN)
- 在SYN标志位置0时,表示当前报文段中的第一个字节的序列号
序列号规则
- 握手阶段:SYN数据包即使没有传送数据,也会消耗一个序列号。因此建立连接后的序列号从
ISN+1
开始 - 挥手阶段:FIN/ACK数据包即使没有传送数据,也会消耗一个序列号
- 数据传输阶段:序列号=第一个报文段的序列号+已经发送的字节数
- 例如:第一个报文段的序列号为
S
,当已经发送100字节后,则下一个报文段的序列号为S+100
- 若某个报文段不携带数据,不会消耗序列号,下一个报文段用相同的序列号发送
- 正常情况下,B给A的ACK确认序列号,就是A下一个报文段的序列号
- 例如:第一个报文段的序列号为
- 客户端三次握手中第三次握手的ACK包和传输阶段的第一个报文段,有相同的序列号
确认号
- ACK标志位置1时才有效,表示接收方期待下一个报文段的序列号。一般是上次收到报文段seq+1
三次握手
过程
第一次握手:
- 客户端将SYN标志位1,向服务端发送一个同步报文
- 生成一个随机的32位序列号seq=x作为客户端的初始序列号(ISN),这个序号后可携带数据
第二次握手:
- 服务端接收到连接请求报文后,若同意建立连接,将ACK标志位置为1
- 服务端会回发一个确认序号,ack=客户端的序列号x+数据长度+SYN/FIN(按一字节)
- 服务端会向客户端发起连接请求,SYN=1
- 服务端会生成一个随机序列号,seq=y作为服务器端的初始序列号
第三次握手:
- 客户端应答服务器的连接请求,ACK=1
- 客户端回复报文的序列号seq变为x+1,若携带数据则seq=x+数据长度+1
- 客户端回复收到了服务端的数据,ack=服务端的序列号y+数据长度+SYN/FIN(按一字节)
至此完成三次握手,连接建立成功。若连接期间未传递数据,随后客户端和服务端的序列号分别从x+1和y+1开始进行传输。
为什么三次握手,而不是两次或者四次?
- 若只有两次握手,那么服务端向客户端发送SYN/ACK报文后就会认为连接建立。但若客户端没有收到服务端发送的ACK报文,那么客户端是未建立连接的。TCP是全双工的,需要两端都建立连接。这导致服务端浪费资源。
- 为什么不是4次握手呢?理论上我们可以使用更多的通信,但三次握手是最少的次数,所以耗费资源最少:
- 第一次:服务端确认自身收和对方发正常
- 第二次:客户端确认自身发,自身收和对方发,对方收正常,此时客户端认为连接已建立
- 第三次:服务端确认自身发和对方收正常,此时双方均建立连接,可以正常通信
四次挥手
过程
第一次挥手:
- 客户端将FIN标志位置1,向服务端发送连接释放报文(也可能由服务端发起断开连接申请)
- 此时连接释放报文的序列号seq=m,为客户端上次发送报文的最后一字节的序号+1
第二次挥手:
- 服务端收到连接释放报文后,将ACK置1,向客户端发送确认报文
- 此时确认报文的确认号ack=m+1
- 同时序列号为seq=n,为服务端上次发送报文最后一个字节的序号+1
经过两次挥手后,TCP连接处于半关闭状态,即客户端到服务器的连接释放,客户端进入终止等待状态2,但反之还未释放。这表示客户端已经没有数据发送了,但服务器还未可知。
第三次挥手:
- 服务端将FIN标志位置1,向客户端发送连接释放报文
- 同时将ACK置为1,主动关闭连接,等待客户端确认
- 此时连接释放报文的确认号ack=m+1,与第二次挥手相同,因为这段时间客户未发送数据
- 同时序列号为seq=p,为服务端上次发送报文最后一个字节的序号+1。若半关闭状态后,服务端未发送数据,则p==n
第四次挥手:
- 客户端收到连接释放报文后,将ACK置1,向服务端发送确认报文
- 此时确认报文号为ack=p+1
- 同时序列号seq=m+1,为客户端上次发送报文的序列号(即第一次挥手的seq)+1
此时,客户端进入TIME-WAIT
状态。注意此时客户端的TCP连接还未释放,必须经过2*MSL(最长报文段寿命)时间后,才进入CLOSED
状态。而服务端只要收到第四次挥手客户端发出的确认,就立即进入CLOSED
状态。
为什么需要四次挥手?
还是因为TCP为全双工协议,一方关闭连接后,另一方还可以继续发送数据。四次挥手,将断开连接分成两个独立的过程。
为什么第四次挥手后,客户端TIME-WAIT状态要等待2MSL才能到CLOSED状态?
分为两个原因:
- 确保第四次挥手的ACK报文能够到达服务端,从而使服务端正常关闭连接
第四次挥手的ACK不一定到达服务端,若没到达,服务端会超时重传FIN/ACK(第三次挥手)报文,若此时客户端已经断开连接,那么无法响应服务端的二次请求,这样服务端就会迟迟收不到第四次挥手的确认报文,无法正常断开连接。
MSL是报文段在网络上存活的最长时间。客户端等待2MSL,即”客户端ACK报文1MSL超时”+“服务端FIN报文1MSL传输”,就能收到服务端重传的FIN/ACK报文,然后客户端重传一次ACK报文,并重新启动2MSL计时器。如此保证服务端正常关闭。
- 防止已失效的连接请求报文段出现在之后的连接中
TCP要求在2MSL内不使用相同的序列号。客户端在发送完最后一个ACK报文后,再经过时间2MSL,就可以保证本连接持续的时间内产生的所有报文段都从网络中消失。这样下一个连接中就不会出现这种旧的连接请求报文段。或即使收到过时报文,也不会去处理它。
若已经建立连接,客户端出现故障或三次握手和四次挥手的包丢失会怎样?
简而言之,通过定时器+超时重传机制,尝试获取确认,直到最后自动断开连接。
具体而言,TCP设有一个保活计时器,通常为2小时。服务器每收到一次客户端数据,都会重新复位此计时器。若2小时还没有收到客户端任何数据,服务器就开始重试:每隔75s发送一次探测报文段,若连发10次后客户端依然没有回应,那么服务器认为连接已经断开。
TCP状态图
三次握手阶段:
客户端:
TCP连接状态 | 含义 |
---|---|
SYN_SENT | 发送了连接请求,等待服务端确认(第一次握手) |
ESTABLISHED | socket已经建立连接 |
服务端:
TCP连接状态 | 含义 |
---|---|
LISTEN | 监听来自远程应用的TCP连接请求 |
SYN_RECEIVED | 收到连接请求并发送了确认报文,等待最终确认(第二次握手) |
ESTABLISHED | socket已经建立连接,并开始数据传输 |
四次挥手阶段
客户端:
TCP连接状态 | 含义 |
---|---|
FIN_WAIT1 | 发送了连接终止请求并等待确认,通常持续时间很短 |
FIN_WAIT2 | 发送连接终止请求并收到远程确认,等待远程TCP的连接终止请求。此状态表面远程在收到socket终止请求后,未立即关闭它的socket |
CLOSING | 发送了连接终止请求后收到了远程的连接终止请求(即双方碰巧前后均要停止连接),正在等待远程对连接终止请求的确认。此状态表示双方同时进入关闭状态 |
TIME_WAIT | 等待足够的时间,以确保远程TCP收到其连接终止请求的确认 |
CLOSED | socket已经没有连接状态 |
服务端:
TCP连接状态 | 含义 |
---|---|
CLOSED_WAIT | 收到了远程的连接终止请求,正在等待本地的应用程序发出连接终止请求。 |
LAST_ACK | 等待第三次挥手发送的连接终止请求的确认(只有在发送连接终止请求前先收到了远程的连接终止请求才会进入此状态) |
CLOSED | socket已经没有连接状态 |