三次握手与四次挥手
三次握手和四次挥手是面试时常问的问题,所以在这篇文章里我们要深入的理解它们才能更好地应对面试官的问题。
在此之前,先说明一下进行三次握手四次挥手时所用到的 TCP 标志位:
- SYN(synchronous 建立联机)
- ACK(acknowledgement 确认)
- FIN(finish 结束)
三次握手
先放一张图帮助我们更好地理解三次握手的详细流程。
两端一开始都处于 CLOSED 的状态,在握手之前会转换为 LISTEN 状态。
第一次握手,客户端发送一个 syn
报文(SYN=1
),和 seq(seq=n)
。SYN=1
表示请求建立连接。seq=n
表示数据报文初始化序列号为 n
(n
为随机数)。此时客户端处于 SYN_SENT 状态,等待服务器端确认。
第二次握手,服务器接收到 syn
报文,结束 LISTEN 状态,并发给客户端应答报文。应答报文包含 SYN(SYN=1)
,ACK(ACK=1)
,seq(seq=k)
,ack(ack=n+1)
。ACK=1
表示确认应答;ack=n+1
就是把客户端发来的初始化序列号加上一表示自己收到了客户端的 syn
报文,只有当 ACK
标志位为 1 的时候,ack
才有效;seq=k
,服务器同样会初始化一个随机数 k
代表序列号。此时服务器处于 SYN_RCVD(SYN_Received) 状态。
第三次握手,客户端收到 syn
报文,明确了数据传输是正常的,结束 SYN_SENT 状态置为 ESTABLISHED,并向服务器端返回一个 syn
报文,ACK(ACK=1)
,seq(seq=n+1)
,ack(ack=k+1)
。ack=k+1
表示客户端已经收到服务器发送的 syn
报文。
服务器端接收到来自客户端的确认之后,明确了数据传输是正常的,结束 SYN_RCVD 状态置为 ESTABLISHED。
ack 和 seq 的作用
在客户端与服务器端传输的 syn
报文中,双方的确认号 ack
和序号 seq
的值,都是在彼此 ack
和 seq
值的基础上进行计算的,这样做保证了 TCP 报文传输的连贯性。一旦出现某一方发出的 syn
包丢失,便无法继续"握手",以此确保了"三次握手"的顺利完成。
ack
和 seq
的初始值是随机生成的,这是为了防止有人预测报文信息并发动攻击。
为什么要三次握手
第一点是:TCP 连接建立的先决条件就是让服务器和客户端确认双方的数据接收和发送能力正常。同时保证可靠传输。
第一次握手:客户端发送网络包,服务端收到了。这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
第二次握手:服务端发包,客户端收到了。这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。
第三次握手:客户端发包,服务端收到了。这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。
因此,需要三次握手才能确认双方的接收与发送能力是否正常。
除此之外,还指定了自己的初始化序列号 seq
,为后面的可靠传送做准备。
第二点是:为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。
假如只有一次握手,客户端给服务器发送请求信息没有得到回应,那就肯定无法判断是否发送成功然后建立连接的。
假如只有两次握手,客户端发送 SYN,然后等待 ACK;结果由于这个报文没有及时抵达服务端,在等待一段时间没有收到回应后,客户端再发送一遍 SYN,这次成功收到服务端的 ACK。但这时第一次发送的 SYN 终于也到达了服务端,服务端收到后又为这个连接申请资源,返回 ACK,然而这个 SYN 是个无效请求,客户端收到 ACK 却不会回应,而服务端会一直为这个连接维持着资源,造成资源的浪费。
三次连接就没毛病了??是的。两次握手的问题在于服务器端不知道一个 SYN 是否是无效的,有了第三次握手,服务端在长时间没有收到客户端回应时,会重新发送第二次握手的报文,如果最后还是没有收到回应,就会发送 RST 报文关闭连接。
如果已经建立了连接,但是客户端突然出现了故障,怎么办?
TCP 设有一个计时器,客户端如果出现故障,服务器端不能一直等待下去,白白浪费资源。服务器端每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为 2 小时,若两小时还没有收到客户端的任何数据,服务器端就会发送一个探测包,以后每隔 75 秒钟发送一次。若一连发送 10 个探测包仍然没反应,服务器端就认为客户端出了故障,接着就关闭连接。
三次握手过程中可以携带数据吗
第一次、第二次握手不可以携带数据,而第三次握手是可以携带数据的。
第一二次如果可以发送数据,攻击者可以在每次发送的 syn 报文中加入大量数据,并且一直重复发送,这样服务器就需要很多内存来存放报文,导致其他正常连接请求无法被处理。
第三次可以发送是因为客户端已经是 ESTABLISHED 状态,并且也已经知道服务器的接收、发送能力是正常的了,因此完全可以进行数据发送。
如果第三次握手没有发出去呢?
TCP 在所有数据包中都有一个序列号。因此,很容易知道数据包是否丢失。我们知道服务器会维护一个定时器,如果在一定时间内没有收到 ACK 就会重新发送第二步 SYN+ACK 数据包,如果客户端再次发送 ACK 成功,建立连接。
如果一直不成功,服务器肯定会有超时设置,超时之后会给客户端发 RST 报文,进入 CLOSED 状态,这个时候客户端应该也会关闭连接。
Notice
RST(Reset) 报文用于复位因某种原因引起出现的错误连接,也用来拒绝非法数据和请求。当发送 RST 包关闭连接时,不必等缓冲区的包都发出去,直接就丢弃缓冲区中的包,发送 RST;接收端收到 RST 包后,也不必发送 ACK 包来确认,因为这表示发送方不再发送也不接受数据。
SYN 攻击
SYN 攻击就是客户端在短时间内伪造大量的源 IP 地址,分别向服务器端发送大量的 SYN 包,此时服务器端会返回 SYN/ACK 包,因为源地址是伪造的,所以伪造的 IP 并不会应答,服务器端没有收到伪造的 IP 的回应,重试 3-5 次并且等待一个 SYN time,如果超时则丢弃这个连接。攻击者大量发送这种伪造源地址 SYN 请求,服务器会消耗非常多的资源来处理这种半连接,同时还要不断地对这些 IP 进行 SYN+ACK 重试。最终服务器无暇理睬正常的连接请求,导致拒绝服务。
四次挥手
还是先上图:
第一次挥手:客户端发出连接释放 syn
包(FIN=1
,seq=u
)到服务器端,并且停止再发送数据,主动关闭连接,进入 FIN-WAIT-1 状态,等待服务器端确认。seq=u
表示报文序列号(等于前面已经传送过来的数据的最后一个字节的序号加 1)。TCP 规定,FIN 报文段即使不携带数据,也要消耗一个序号。
第二次挥手:服务器端收到客户端发送的连接释放 syn
包后,即发出确认 syn
包(ACK=1
,seq=v
,ack=u+1
)到客户端,进入 CLOSE-WAIT 状态,此时 TCP 处于半关闭状态。客户端收到服务器端确认后,进入 FIN-WAIT-2 状态,等待服务器端发出的连接释放 syn
包。这时候客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个 CLOSE-WAIT 状态持续的时间。
第三次挥手:当服务器端数据传送完毕后,假定此时的序列号为 seq=w
,服务器端发送连接释放 syn
包(FIN=1
,ACK=1
,seq=w
,ack=u+1
),服务器端进入 LAST-ACK 状态,等待客户端最后确认。
第四次挥手:当客户端收到服务器端连接释放 syn
包后,客户端发出确认 syn
包(ACK=1
,seq=u+1
,ack=w+1
),客户端进入 TIME-WAIT 状态。此时 TCP 未释放,需要经过时间等待计时器设置的时间 2MSL(最长报文段寿命)后,客户端才进入 CLOSED 状态。服务器端只要收到客户端发出的确认,立即进入 CLOSED 状态,由此完成四次挥手。
为什么客户端要等待一段时间才会进入 CLOSED 状态?
客户端要确保服务器是否已经收到了我们的 ACK 报文,如果没有收到的话,服务器会重新发 FIN 报文给客户端,客户端再次收到 FIN 报文之后,就知道之前的 ACK 报文丢失了,然后再次发送 ACK 报文。
为什么不能三次挥手?
理论上可以进行三次挥手,也就是第二和第三次合成一次,但是服务器在进行第二次握手之后往往还有一些信息需要发送,因此通常要延迟一段时间再进行第三次挥手。