文章目录
前言
TCP
和UDP
是计算机网络结构中运输层的两个协议。
- 传输控制协议 TCP(Transmission Control Protocol):提供面向连接的,可靠的数据传输服务(它通过三次握手建立连接,通过四次挥手终止连接)。
- 用户数据协议 UDP(User Datagram Protocol):提供无连接的,尽最大努力的数据传输服务(不保证数据传输的可靠性)。
- 序列号(Sequence Number):TCP报文段中的一个字段,用于标识数据流中的每个字节,确保数据的有序传输。
确认号(Acknowledgment Number):TCP报文段中的一个字段,用于确认接收方已成功接收到的数据,并指示发送方可以继续发送后续数据。
SYN标志(Synchronize Sequence Numbers):同步序列号标志,用于建立连接时同步双方的初始序列号。
ACK标志:确认标志,用于确认接收到的数据。
FIN标志:结束标志,用于终止连接时表示不再发送数据。
一、三次握手
1.1 三次握手流程
如图:
第一次握手
- 客户端发送SYN报文:客户端向服务器发送一个带有
SYN
标志的TCP
报文段,报文中包含客户端选择的初始序列号seq=x
(序列号是随机的),TCP规定,SYN
报文请求不能携带数据,但需要消耗一个序列号。此时客户端进入SYN_SENT
状态。 - 作用:客户端向服务器表明自己希望建立连接,并告知服务器自己的初始序列号,以便后续数据传输时进行同步。
第二次握手
- 服务器响应SYN+ACK报文:服务器收到客户端的
SYN
报文后,TCP服务器收到请求报文后,如果同意连接,则向客户端发送一个带有SYN
和ACK
标志的报文段。报文中包含服务器选择的初始序列号seq=y
,以及对客户端序列号的确认号ack=x+1
。这个报文也不能携带数据,但是同样要消耗一个序号。此时服务器进入SYN_RCVD
状态。 - 作用:服务器向客户端确认已收到其连接请求,并告知客户端自己的初始序列号,同时确认客户端的序列号,确保双方的序列号同步。
第三次握手
- 客户端发送ACK报文:客户端收到服务器的
SYN+ACK
报文后,向服务器发送一个带有ACK
标志的报文段,报文中包含对服务器序列号的确认号ack=y+1
,TCP规定,ACK报文段可以携带数据,但是如果不携带数据的话则不消耗序号。此时客户端和服务器都进入ESTABLISHED
状态,连接建立成功。 - 作用:客户端确认已收到服务器的
SYN
报文,并确认服务器的序列号,确保双方的序列号完全同步。
连接正式建立,双方可以开始数据传输。
1.2 tcp为什么需要三次握手建立连接?
三次握手的原因:
- 三次握手才可以阻止重复历史连接的初始化(主要原因)
- 三次握手才可以同步双方的初始序列号
- 三次握手才可以避免资源浪费
原因一:避免历史连接
简单来说,三次握手的首要原因是为了防止旧的重复连接初始化造成混乱。
我们考虑一个场景,客户端先发送了 SYN(seq = 90)
报文,然后客户端宕机了,而且这个 SYN
报文还被网络阻塞了,服务器端并没有收到,接着客户端重启后,又重新向服务端建立连接,发送了 SYN(seq = 100)
报文(注意!不是重传 SYN,重传的 SYN 的序列号是一样的)。
看看三次握手是如何阻止历史连接的:
客户端连续发送多次 SYN(都是同一个四元组)建立连接的报文,在网络拥堵情况下:
- 一个旧 SYN 报文比最新的 SYN报文早到达了服务端,那么此时服务端就会回一个
SYN + ACK
报文给客户端,此报文中的确认号是91(90+1)
。 - 客户端收到后,发现自己期望收到的确认号应该是
100 + 1
,而不是90 + 1
,于是就会回RST
报文。 - 服务端收到
RST
报文后,就会释放连接。 - 后续最新的
SYN
抵达了服务端后,客户端与服务端就可以正常的完成三次握手了。
上述中的旧 SYN 报文称为历史连接,TCP 使用三次握手建立连接的最主要原因就是防止历史连接初始化了连接。
如果是两次握手连接,就无法阻止历史连接,那为什么 TCP 两次握手无法阻止历史连接呢?
主要是因为在两次握手的情况下,服务端没有中间状态给客户端来阻止历史连接,导致服务端可能建立一个历史连接,造成资源浪费。
在两次握手的情况下,服务端在收到 SYN
报文后,就进入 ESTABLISHED
状态,意味着这时可以给对方发送数据,但是客户端此时还没有进入 ESTABLISHED
状态,假设这次是历史连接,客户端判断到此次连接为历史连接,那么就会回 RST
报文来断开连接,而服务端在第一次握手的时候就进入 ESTABLISHED
状态,所以它可以发送数据的,但是它并不知道这个是历史连接,它只有在收到 RST
报文后,才会断开连接。
可以看到,如果采用两次握手建立 TCP 连接的场景下,服务端在向客户端发送数据前,并没有阻止掉历史连接,导致服务端建立了一个历史连接,又白白发送了数据,妥妥地浪费了服务端的资源。
因此,要解决这种现象,最好就是在服务端发送数据前,也就是建立连接之前,要阻止掉历史连接,这样就不会造成资源浪费,而要实现这个功能,就需要三次握手。
所以,TCP 使用三次握手建立连接的最主要原因是防止历史连接初始化了连接。
原因二:同步双方初始序列号
TCP 协议的通信双方, 都必须维护一个序列号, 序列号是可靠传输的一个关键因素,它的作用:
- 接收方可以去除重复的数据;
- 接收方可以根据数据包的序列号按序接收;
- 可以标识发送出去的数据包中, 哪些是已经被对方收到的(通过
ACK
报文中的序列号知道);
可见,序列号在 TCP 连接中占据着非常重要的作用,所以当客户端发送携带初始序列号的 SYN
报文的时候,需要服务端回一个 ACK
应答报文,表示客户端的 SYN
报文已被服务端成功接收,那当服务端发送初始序列号给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。
四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了三次握手。
而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。
原因三:避免资源浪费
如果只有两次握手,当客户端发生的 SYN
报文在网络中阻塞,客户端没有接收到 ACK
报文,就会重新发送 SYN
,由于没有第三次握手,服务端不清楚客户端是否收到了自己回复的 ACK
报文,所以服务端每收到一个 SYN
就只能先主动建立一个连接,这会造成什么情况呢?
如果客户端发送的 SYN
报文在网络中阻塞了,重复发送多次 SYN
报文,那么服务端在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。
即两次握手造成消息滞留的情况下,服务端重复接受无用的连接请求
SYN
报文,而造成重复分配资源。
二、四次挥手
2.1 四次挥手流程
如图:
第一次挥手
- 客户端发送FIN报文:客户端向服务器发送一个带有
FIN
标志的TCP报文段,表示客户端已经没有数据要发送了,但仍然可以接收数据。报文中包含一个序列号seq=u
(等于前面已经传送过来的数据的最后一个字节的序号加1),此时客户端进入FIN_WAIT_1
状态。TCP规定,FIN
报文段即使不携带数据,也要消耗一个序列号。 - 作用:客户端主动关闭连接,通知服务器自己已经完成数据发送,等待服务器的确认。
第二次挥手
- 服务器响应ACK报文:服务器收到客户端的
FIN
报文后,向客户端发送一个带有ACK
标志的报文段,确认号为ack=u+1
,并且带上自己的序列号seq=v
。此时服务器进入CLOSE_WAIT
状态,TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT
状态持续的时间。
客户端收到该报文后进入FIN_WAIT_2
状态。 - 作用:服务器确认已收到客户端的
FIN
报文,但此时服务器可能还有数据需要发送,因此不会立即关闭连接。
第三次挥手
- 服务器发送FIN报文:当服务器完成数据发送后,向客户端发送
FIN=1,ack=u+1
报文,表示服务器也没有数据要发送了。报文中还包含一个序列号seq=w
,此时服务器进入LAST_ACK
状态。 - 作用:服务器通知客户端自己已经完成数据发送,等待客户端的最终确认。
第四次挥手
- 客户端响应ACK报文:客户端收到服务器的释放连接的
FIN
报文后,向服务器发送确认报文ACK=1
,确认号为ack=w+1
,自己的序列号是seq=u+1
此时客户端进入TIME_WAIT
状态,服务器收到该报文后进入CLOSED
状态。 - 作用:客户端确认已收到服务器的
FIN
报文,连接正式关闭。客户端进入TIME_WAIT
状态,等待一段时间(2MSL
)后,确保服务器已收到最终的ACK
报文,然后进入CLOSED
状态。
2.2 为什么是四次,不是三次?
tcp是全双工通信,服务端和客服端都能发送和接收数据。
tcp在断开连接时,需要服务端和客服端都确定对方将不再发送数据。
- 第1次挥手:由客户端向服务端发起,服务端收到信息后就能确定客户端已经停止发送数据。
- 第2次挥手:由服务端向客户端发起,客户端收到消息后就能确定服务端已经知道客户端不会再发送数据。
- 第3次握手:由服务端向客户端发起,客户端收到消息后就能确定服务端已经停止发送数据。
- 第4次挥手:由客户端向服务端发起,服务端收到信息后就能确定客户端已经知道服务端不会再发送数据。
为什么不是3次挥手?
在客服端第1次挥手时,服务端可能还在发送数据,所以第2次挥手和第3次挥手不能合并。
2.3 为什么要等待2msl?
为什么需要等待?
-
为了保证客户端发送的最后一个
ACK
报文段能够到达服务端。 这个ACK
报文段有可能丢失,因而使处在LAST-ACK
状态的服务端就收不到对已发送的FIN + ACK
报文段的确认。服务端会超时重传这个FIN+ACK
报文段,而客户端就能在2MSL
时间内(超时 +1MSL
传输)收到这个重传的FIN+ACK
报文段。接着客户端重传一次确认,重新启动2MSL
计时器。最后,客户端和服务器都正常进入到CLOSED
状态。 -
防止已失效的连接请求报文段出现在本连接中。客户端在发送完最后一个
ACK
报文段后,再经过时间2MSL
,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个连接中不会出现这种旧的连接请求报文段。
为什么等待的时间是 2MSL?
MSL 是 Maximum Segment Lifetime
,报⽂最⼤⽣存时间,它是任何报⽂在⽹络上存在的最⻓时间,超过这个时间报⽂将被丢弃。
TIME_WAIT
等待 2 倍的 MSL
,⽐较合理的解释是:⽹络中可能存在来⾃发送⽅的数据包,当这些发送⽅的数据包被接收⽅处理后⼜会向对⽅发送响应,所以⼀来⼀回需要等待 2 倍的时间。
⽐如如果被动关闭⽅没有收到断开连接的最后的 ACK
报⽂,就会触发超时重发 FIN
报⽂,另⼀⽅接收到 FIN
后,会重发 ACK
给被动关闭⽅, ⼀来⼀去正好 2 个 MSL
。
2.4 TCP的保活计时器
除时间等待计时器外,TCP 还有一个保活计时器(keepalive timer)。
设想这样的场景:客户已主动与服务器建立了 TCP 连接。但后来客户端的主机突然发生故障。显然,服务器以后就不能再收到客户端发来的数据。因此,应当有措施使服务器不要再白白等待下去。这就需要使用保活计时器了。
服务器每收到一次客户端的数据,就重新设置保活计时器,时间的设置通常是两个小时。若两个小时都没有收到客户端的数据,服务端就发送一个探测报文段,以后则每隔 75 秒钟发送一次。若连续发送 10 个探测报文段后仍然无客户端的响应,服务端就认为客户端出了故障,接着就关闭这个连接。