三次握手,四次挥手

285 阅读8分钟

这个问题在面试中属于高频了,当然对于非科班的理解起来就会有难度,当然科班不认真学的话,难度也不小。首先要理解什么是三次握手,四次挥手,我们要知道一些前置知识。比如Rdt是什么,rdt实现的协议具体有哪些?GBN(后退N帧协议)SR(选择重传协议)。这两种我们都要知道,包括运行机制。

接下来我们首先看Tcp协议内部结构。我们这里着重解释说明几个点,对于后续理解有更好的帮助。

序号(seq):

序号:首先在tcp段中的序号不是按照0,1,2,3这样的序号进行排序的。想必大家在之前学习GBN和SR这种协议传输过程中,咱们的认识的tcp段的序号都是已十进制这种整数依次递增的。但是,真实的tcp段中并不是存的这种,真实存储的是字节偏移量。

要清楚字节偏移量,我们就要知道,在用户层 是将数据报文变成字节流形式传入传输层,如果字节流太长,那么传输层就会将这一长串,分为多个小的字节流,每一个字节流就被称为sdu,然后传输层再加上tcp头部,这样组成的就是tcp段。如图~ 一个字节流可以分为多个tcp段。那么字节偏移量就是该tcp段中的body部分也就是sdu中的第一个字节,相对于原字节流的起始位置的偏移量。假如起始位置为x,那么第二个tcp段的序号就是x+mss。

确认号:

当发送方发送数据后,接收方会返回ack(确认号),假设这里接收方返回ACk = 555,意思就代表包括554及之前的所有接收方都已经全部收到,发送方就需要从555开始发送。这里和我们之前学GBN和SR有些区别,要分清楚。

标志位:

在后面tcp建立连接,断开连接,他们的标志位的组合是不一样的。后面细讲,这里先有个概念。

为什么在建立连接要三次握手?

我们都知道tcp是面向连接的协议,就是传输数据要先打招呼。就好比,人物A要问人物B,要先说你好!B也要回复你好! 那就有人会问,两次握手不行吗? 答案是肯定不行,两次握手会出现以下问题。

  • 服务器虚假连接过多(半连接)

我们来假设这样一个场景,当C(客户端)发送第一个连接请求,并且安全到达S(服务端)。服务端也返回相应的确认报文,但是由于网络延迟问题,确认报文迟迟没有到达C端。如果超过了等待时间,那么就会导致超时重发机制,此时C端将会再发送一个连接请求。当连接请求发出去后,C端收到了之前S端返回的确认报文。那么两次握手的话,此时就会建立连接了。此时就开始通讯了,当C端发送完数据,然后关闭连接。此时他们的连接就断了。如果这时之前由于C端的超时重发机制的连接请求报文发送到了S。两次握手的机制会让S端维护该连接,为该连接准备好资源。如果这种半连接多了,那么就会很占用服务器的资源。

  • 旧数据被服务器当作新数据接收

还是以上面的场景为基础,此时仍然还会出现上面的情况,但是我们现在多一个步骤,就是当连接后,C端发送数据,此时也可能会出现连接时相同的问题。就是C端没有收到S端返回回来的ack。时间一到会触发超市等待机制,会再次重新发一遍。这边发了后,上一次的ack就到了。那么此时就再断开连接。这时会出现什么情况了,就会造成上面的半连接,并且再连接后,由于刚刚重发的那条数据,现在网络好了,恰巧S端又收到了。这个就是不仅是半连接,还把断开连接之前发的数据接收到了。

三次握手

搞清楚两次握手的缺陷后,我们就来看看三次握手是如何进行的,并解决了两次握手的问题的。

  • 第一次握手:客户端发送一个SYN报文,并指明客户端的初始化序列号 ISN(x)。此时客户端处于SYN_SENT状态。
    • 此时头部的标志位SYN = 1 ,初始序号seq=x。该报文段虽然不能携带数据,但是还是会消耗掉1个序号。
  • 第二次握手:服务端接收到客户端的SYN报文后,会以自己的SYN报文作为应答。并且也会自己随机一个初始化序列号ISN(y)。同时会把客户端的seq+1的值作为ACK的值。来表示服务端已经收到了客户端的SYN报文。此时服务端处于SYN_REVD状态。
    • 此时确认报文的头部的标志位SYN = 1,初始化序号seq=y,ACK=1,确认号ack=x+1。
  • 第三次握手:客户端在收到服务端的SYN报文后,会发送一个ACK报文,当然同样的会把服务端的seq+1作为确认号ack的值,表示已经收到了服务端的SYN。此时客户端处于ESTABLISHED状态。服务器收到ACK报文后,也处于ESTABLISHED状态。此时连接建立完毕
    • 确认报文头部信息:ACK = 1 ,确认号ack = y + 1,seq = x+1(第一个SYN报文段消耗了一个序号)

解释ACK和ack:我们在上面描述头部信息都是将ACK和ack同时描述的。我们解释以下,ack就是我们理解的那个意思。ACK就是当ACK=1时,ack这个确认号才有效,所以我们在需要使用ack时,都需要将ACK置为1.

上面已经很详细的为大家说明了 三次握手的大致流程。下面来说说是如何解决的两次握手的问题的。

半连接解决:

场景还是和最开始的一样哈,当连接断开后,服务器接收到了由于网络延迟原因的SYN报文,此时服务端将会发送一个SYN确认报文,这时服务端还要等待客户端的ACK报文。如果这时没有,那么就不会建立连接。也不会出现半连接状况了。

旧数据被当作新数据接收:

这个问题,我们上面举得前提是可以半连接的前提下。现在半连接问题解决了,自然这个问题也解决了。但还有一种情况,就是同样的主机,同样的端口。重复连接两次,那么如果第一次连接中,由于网络问题导致有些数据没有即使到达,却在第二次连接时,这个数据成功被服务端接收到了,这种也算旧数据被当作新数据接收了。那么如何解决呢。这里就用到了随机的ISN,由于每次连接客户端和服务端都是随机的ISN,当服务端检测出这个seq不是在本连接这个范围,就会丢掉。这也是为啥ISN是随机的原因了。

四次挥手

简单说下挥手。 四次挥手,我们可以分为两个发送数据方向的部分

  • 第一次挥手:客户端向服务端发送一个FIN报文。
  • 第二次挥手:服务端收到客户端的FIN报文,会发送一个ACK报文。此时客户端收到该报文。那么客户端将不能再向服务端发送数据,但是此时服务端还可以给客户端发送数据。
  • 第三次挥手:在第二次挥手后,客户端接收到ACK报文后,就在等待服务端这边的连接释放报文段(FIN)。如果此时服务器也想断开连接,那么将会和客户端的第一次挥手一样,发送一个FIN报文。等待客户端的确认
  • 第四次挥手:客户端在收到服务端的FIN报文段时,也会发送一个ACK确认报文。这个时候客户端不会立刻进入close状态,还会等待一定时间,以便于ACK确认报文被服务端成功接收到。服务端在接收到ACK确认报文后,就处于关闭连接状态了,close。此时服务端也不能发送数据了。

以上是通过客户端主动开始断开连接的程序,以此类比,服务端主动断开连接也是这个过程。