前言
前几天一朋友去面试,回来灰头土脸的,我一打听,才知道是一道网络相关的很基础的八股没回答好,没错就是本文的标题,面试官问他为什么TCP建连需要是三次握手,我那朋友其实是背了这个问题的八股答案的,他回答道是为了防止历史TCP连接建立,面试官则继续追问,什么是历史TCP连接建立,体现在TCP报文上是什么样的形式,发生了历史TCP连接建立会产生什么影响。面试官的追问其实挺有道理,但是我那朋友并没有解释得很清楚,我今天准备就这个问题,梳理一下TCP建连为啥需要三次握手。
正文
我们考虑如下一种场景,客户端向服务端发送了SYN报文,但是由于网络差,SYN报文迟迟没有到达服务端,然后在等待SYN报文到达服务端的过程中,客户端发生了宕机重启,重启后的客户端又向服务端发送了SYN报文,现在称宕机重启前发送的SYN报文为旧SYN报文,宕机重启后发送的SYN报文旧新SYN报文,图示如下。
特别注意,旧SYN报文和新SYN报文的seq是不一样的。
在客户端将新SYN报文发出去后,此时旧SYN报文到达服务端,随即服务端会向客户端回发旧SYN报文对应的SYN+ACK报文,并且这个SYN+ACK报文的ack值等于1001,即旧SYN报文的seq值加1,客户端在收到这个SYN+ACK报文后,发现其ack值并不是期望的2001(新SYN报文的seq值加1),判断这个SYN+ACK报文属于历史TCP连接的报文,此时客户端会向服务端发送RST报文来终止历史TCP连接的建立,图示如下。
那么旧SYN报文对应的TCP连接就称为历史TCP连接,体现在报文上就是上图示意的这种形式,那么为什么TCP三次握手可以防止历史TCP连接的建立呢,还是结合上图来分析,当服务端收到旧SYN报文并且回发了SYN+ACK报文后,进入的状态是SYN_RCVD,此时连接并未真正建立,服务端不能向客户端发送数据,在服务端收到客户端的三次握手中最后一次握手的ACK报文后,服务端会进入ESTABLISHED状态,此时连接才算真正建立成功,服务端才可以向客户端发送数据,而我们也知道,客户端会发现第二次握手的SYN+ACK报文的seq值不正确,从而会给服务端发送RST报文来终止连接建立,也正是由于多了这第三次的握手的约定,让客户端能够在服务端认为连接建立前发送这个RST报文来终止历史TCP连接建立。但假如是两次握手,那么服务端在收到旧SYN报文后,就会进入ESTABLISHED状态,此时在服务端这边,历史TCP连接就算是建立了,然后我们也知道,后续客户端会根据服务端发送的SYN+ACK报文的seq值判断出历史TCP连接从而向服务端发送RST报文以终止历史TCP连接,但此时在服务端看来,连接已经是建立成功了的,所以两次握手没有办法防止历史TCP连接建立。
假如TCP连接建立真的是两次握手,示意图如下所示。
如果是两次握手,就无法防止历史TCP连接的建立,那么一旦出现历史TCP连接的建立,有什么影响呢,主要影响就是服务端会浪费资源去建立一个历史TCP连接并使用这个历史TCP连接发送数据。
总结
其实稍加思索,TCP建立连接只用两次握手肯定是有很大的问题的,因为两次握手的话,相当于服务端每收到一个SYN请求就会建立一个连接,那么万一网络差,服务端回发给客户端的SYN+ACK报文迟迟没到客户端从而触发客户端的超时重传,那么服务端就会收到很多SYN包,从而浪费资源建立很多TCP连接。
当然,TCP建立连接使用三次握手最主要的原因,还是因为要避免历史连接的建立,正是由于第三次握手的存在,才让客户端能够在服务端认为连接建立前发送RST报文来终止连接建立,从而避免服务端浪费资源去建立历史连接,也避免了服务端使用历史连接来发送数据。