面试官:介绍一下TCP的三次握手和四次挥手。

53 阅读4分钟

三次握手指的是客户端和服务端建立连接的过程。 三次握手这个过程需要了解TCP报文的一些关键字段如下:

  1. 序列号:本次发送的数据的号码

  2. 确认应答号:期望下一次收到的报文开始的序列号(接收方发送给发送方的,表示在这个号码之前的序列号确保都收到了)

  3. 控制位:表示报文的类型

    1. syn,建立连接时发送
    2. ack,表示报文是确认报文
    3. rst,表示连接出错,需要重新建立连接
    4. fin,表示断开连接请求

序列号用来解决包乱序问题(因为网络时延问题,包可能是不同时间到达的,根据序列号来排序)

确认应答号,用来解决可靠性问题(确认应答号表示前面的都收到了)

在linux中,是通过源地址+源端口+目标地址+目标端口来确保tcp连接的唯一性。

三次握手过程

  1. 客户端发起第一次握手,随机生成一个序列号(序列号的生成有讲究)isn,发送一个syn报文给服务端。

  2. 服务端在收到syn报文以后,也随机生成一个序列号(服务端也要发送数据,两边各自用自己的序列号,记录最新的就行),然后发送一个syn+ack报文给客户端(这里就是四次握手合并成三次握手,本来应该是ack+syn)。其中确认应答号是isn+1,序列号是isn1

  3. 客户端在收到服务端的ack+syn报文以后,发送一个ack报文给服务端,其中确认应答号是isn1+1。

自此,连接建立。

经典问题是为什么不是两次握手或者是四次握手。

四次握手上面已经解答了。

不能两次握手的原因如下:

  1. 假如只有两次握手,那么服务端在收到第一次客户端发送的syn报文以后就是连接建立状态,客户端发送的syn报文可能重复多次,也就会导致连接多次建立,浪费资源。(创建销毁)

  2. 现在三次握手,有了syn_received状态,就可以避免直接establish状态。因为客户端发现报文不对的时候,可以及时阻止连接建立。

三次握手的过程不能忽略两端的状态。两端的状态是由socket提供的。

三次握手示意图

第一次握手时:

客户端:syn_sent_start。服务端:syn_recd_start

第二次握手时:

客户端:syn_sent_end。服务端:syn_recd_start

第三次握手时:

客户端:es_start。服务端:es_start

四次挥手过程

四次挥手指的是安全关闭tcp连接的过程。

  1. 客户端和服务端都可以主动关闭连接。其中一方发起关闭请求时(成为主动关闭方),就发一个fin类型的报文给对方。
  1. 被动关闭一方收到fin报文以后,发送ack给主动关闭方,代表收到了信息。

但这个时候不是立马关闭的,因为被动关闭方还有消息可能还发完!

  1. 所以等待被动关闭方发送完消息以后,就会发送一个fin报文给主动关闭方,主动关闭方收到fin报文以后,回复ack报文给被动关闭方,被动关闭方收到以后正式关闭。
  1. 主动关闭方还要等待2msi时间才关闭(2msi表示往返时间内,数据报能存活的最大时间),因为ack报文可能丢失,要等待主动关闭方重发fin报文,重新发送ack报文,帮助被动关闭方顺利关闭。

在这个过程中同样也要注意双方的状态问题(见下图)。

面试常考的是介绍一下TIME_WAIT这个状态。(也就是为什么要等待一段时间再关闭)

  1. 显然这个机制会帮助被动关闭方正确地关闭。
  2. 更重要的是防止历史连接中的数据,被后面相同四元组的连接错误的接收。假如没有这个机制,主动关闭方发出ack以后直接关闭,网络上可能还游走着数据包。有可能重新打开的时候,数据包来了,刚好数据包序列号和应答号符合,客户端接收到了。就会造成数据错乱问题。

image.png

附:图都来自小林coding,更加详细的可以看4.1 TCP 三次握手与四次挥手面试题 | 小林coding (xiaolincoding.com)