常规回答:“因为三次握手才能保证双方具有接收和发送的能力”
原因一:避免历史连接
三次握手的首要原因是为了防止旧的重复连接初始化造成混乱。
假设:客户端先发送了SYN(seq=90)报文,然后客户端宕机了,而且这个SYN报文还被网络阻塞了,服务端并没有收到,接着客户端重启后,又重新向服务端建立连接,发送了SYN(seq=100)报文。(这里不是重传,而是重新发送请求)
客户端连续发送多次SYN(都是同一个四元组)建立连接的报文,在网络拥堵的情况下:
- 一个[旧的SYN报文]比[最新的SYN]报文早到达了服务端,那么此时服务端就回一个SYN+ACK报文给客户端,此报文中的确认号是91(90+1)
- 客户端收到后,发现自己期望收到的确认号应该是100+1,而不是90+1,于是就会回RST报文
- 服务端收到RST报文后,就会释放连接。
- 后续最新的SYN抵达了服务端,客户端与服务端就可以正常的完成三次握手了。
上述中的「旧 SYN 报文」称为历史连接,TCP 使用三次握手建立连接的最主要原因就是防止「历史连接」初始化了连接。
TIP
如果服务端在收到 RST 报文之前,先收到了「新 SYN 报文」,也就是服务端收到客户端报文的顺序是:「旧 SYN 报文」->「新 SYN 报文」,此时会发生什么?
当服务端第一次收到 SYN 报文,也就是收到 「旧 SYN 报文」时,就会回复
SYN + ACK
报文给客户端,此报文中的确认号是 91(90+1)。然后这时再收到「新 SYN 报文」时,就会回 Challenge Ack (opens new window)报文给客户端,这个 ack 报文并不是确认收到「新 SYN 报文」的,而是上一次的 ack 确认号,也就是91(90+1)。所以客户端收到此 ACK 报文时,发现自己期望收到的确认号应该是 101,而不是 91,于是就会回 RST 报文。
如果是两次握手连接,就无法阻止历史连接,那为什么TCP两次握手就无法阻止历史连接呢?
因为在两次握手的情况下,服务端没有中间状态给客户端来阻止历史连接,导致服务端可能建立一个历史连接,造成资源浪费。
在两次握手的情况下,服务端在收到 SYN 报文后,就进入 ESTABLISHED 状态,意味着这时可以给对方发送数据,但是客户端此时还没有进入 ESTABLISHED 状态,假设这次是历史连接,客户端判断到此次连接为历史连接,那么就会回 RST 报文来断开连接,而两次握手而服务端在第一次握手的时候就进入 ESTABLISHED 状态,所以它可以发送数据的,但是它并不知道这个是历史连接,它只有在收到 RST 报文后,才会断开连接。
可以看到,如果采用两次握手建立 TCP 连接的场景下,服务端在向客户端发送数据前,并没有阻止掉历史连接,导致服务端建立了一个历史连接,又白白发送了数据,妥妥地浪费了服务端的资源。
因此,要解决这种现象,最好就是在服务端发送数据前,也就是在建立连接前,要阻止掉历史连接,这样就不会造成资源浪费,所以需要三次握手。
所以TCP使用三次握手建立连接的最主要原因是防止[历史连接]初始化了连接
原因二:同步双方初始序列号
TCP协议的通信双方,都必须维护一个[序列号],序列号是可靠传输的一个关键因素,它的作用:
- 接收方可以去除重复数据
- 接收方可以根据数据包的序列号按序接收
- 可以标识发送出去的数据包中,哪些是已经被对方收到的(通过ACK报文中的序列号知道)
可见,序列号在TCP连接中占据着非常重要的作用,所以当客户端发送携带[初始序列号]的SYN报文的时候,需要服务端回一个ACK应答报文,表示客户端的SYN报文已被服务端成功接收,那么当服务端发送[初始序列号]给客户端的时候,依然也要得到客户端的应答,这样一来一回,才能确保双方的初始序列号能被可靠的同步。
四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了[三次握手]。
两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。
原因三:避免资源浪费
如果只有[两次握手],当客户端发生的SYN报文在网络中阻塞,客户端没有收到ACK报文,就会重新发送SYN,由于没有第三次握手,服务端不清楚客户端是否收到了自己回复的ACK报文,所以服务端每收到一个SYN就只能先主动建立一个连接,这会造成什么情况呢?
如果客户端发送的SYN报文在网络中阻塞了,重复发送多次SNY报文,那么服务端在收到请求后就会建立多个冗余的无效链接,造成资源浪费。(因为两次握手的情况下,服务端在接收到SYN报文后就会建立连接)
这里两次握手是假设「由于没有第三次握手,服务端不清楚客户端是否收到了自己发送的建立连接的
ACK
确认报文,所以每收到一个SYN
就只能先主动建立一个连接」这个场景。
结论
TCP建立连接时,通过三次握手能够防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号。序列号能够保证数据包不重复、不丢弃和按序传输。
不使用两次握手和四次握手的原因:
- 两次握手:无法防止历史连接的建立,会造成双方资源的浪费,无法可靠的同步双方序列号
- 四次握手:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。