TCP 三次握手 与 四次挥手 及相应面试题

缩写字段含义

  序列号 seq:占4个字节,用来标记数据段的顺序。TCP 把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;

         给字节编上序号后,就给每一个报文段指派一个序号;序列号 seq 就是这个报文段中的第一个字节的数据编号

  确认号 ack:占4个字节,期待收到对方下一个报文段的第一个数据字节的序号;序列号表示报文段携带数据的第一个字节的编号;

        而确认号指的是期望接收到下一个字节的编号;因此 当前报文段最后一个字节的编号+1 即为确认号 ack。

  确认 ACK标志位,仅当 ACK=1 时,确认号字段才有效。ACK=0 时,确认号无效。

  同步 SYN标志位,连接建立时用于同步序号。当 SYN=1,ACK=0 时表示:这是一个连接请求报文段。

        若同意连接,则在响应报文段中使得 SYN=1,ACK=1。因此,SYN=1 表示这是一个连接请求,或 连接接受报文

        SYN 这个标志位只有在 TCP 建立连接时才会被置1,握手完成后 SYN 标志位被置0。

  终止 FIN标志位,用来释放一个连接。FIN=1 表示:此报文段的发送方的数据已经发送完毕,并要求释放运输连接。

三次握手(TCP 建立连接过程)

  第一次:客户端发送一段TCP报文(syn包),其中 SYN = 1seq = x(x一般为1),客户端进入 SYN - SENT 状态(等待服务端确认)。

  第二次:服务端收到客户端的TCP报文后,结束 LISTEN 状态,并返回一段TCP报文,其中

      SYN = 1,ACK = 1, 表示“确认客户端的报文 seq 序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接”(即告诉客户端,服务器收到了你的数据)

      seq = y,确认号 ack = x+1,表示 收到客户端的序号 seq 并将其值加1作为自己确认号 ack 的值。

      服务端进入 SYN - RCVD 状态(RCVD 表示 received,即已接收)

  第三次:客户端接收到来自服务器端的确认TCP报文后,明确了从客户端到服务器的数据传输是正常的,结束 SYN-SENT 状态,并返回最后一段TCP报文,其中

      ACK = 1,表示 “确认收到服务器端同意连接的信号”(即告诉服务器,我知道你收到我发的数据了)

      seq = x+1,表示 收到服务器端的确认号 ack,并将其值作为自己的序号值

      ack = y+1,表示 收到服务器端序号 seq,并将其值加1作为自己的确认号 ack的值;

      客户端进入 ESTABLISHED 状态。

      服务端收到来自客户端的“确认收到服务器数据”的TCP报文之后,明确了从服务器到客户端的数据传输是正常的。结束 SYN-SENT 状态,进入 ESTABLISHED 状态。

 

 

  注意:

    1. 握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。

        理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。

    2. 在客户端与服务器端传输的TCP报文中,双方的确认号 ack 和序号 seq 的值,都是在彼此 ack 和 seq 值的基础上进行计算的,这样做保证了TCP报文传输的连贯性

        一旦出现某一方发出的TCP报文丢失,便无法继续 "握手",以此确保了"三次握手"的顺利完成。

四次挥手(TCP断开连接)

  所谓的四次挥手即TCP连接的释放(解除)。连接的释放必须是一方主动释放,另一方被动释放。以下为客户端主动发起释放连接的图解:

 

  第一次:客户端想要释放连接,向服务器端发送一段TCP报文,其中,

      FIN = 1,表示 “请求释放连接“。

      seq = u,u 等于前面已经传送过来的数据的最后一个字节的序号加1。

      客户端进入FIN - WAIT - 1状态,即半关闭状态。

      此时相当于主动关闭方(客户端)告诉被动关闭方:我已经不会再给你发数据(非确认报文),但是,此时主动关闭方还可以接受数据;

      在 fin 包之前发送出去的数据,如果没有收到对应的 ack 确认报文,主动关闭方依然会重发这些数据。

  第二次:服务器端接收到从客户端发出的TCP报文之后,结束 ESTABLISHED 阶段,进入 CLOSE-WAIT 阶段(半关闭状态)并返回一段TCP报文,其中,

      ACK = 1, 表示“接收到客户端发送的释放连接的请求”;

      seq = vack = u+1,表示 在收到客户端报文的基础上,将其序号 seq 值加1作为本段报文确认号 ack 的值。

      服务器端开始准备释放服务器端到客户端方向上的连接。

      客户端收到从服务器端发出的TCP报文之后,确认了服务器收到了客户端发出的释放连接请求。

      随后客户端结束 FIN-WAIT-1 阶段,进入 FIN-WAIT-2 阶段

      前"两次挥手"既让服务器端知道了客户端想要释放连接,也让客户端知道了服务器端了解自己想要释放连接的请求。

      于是,可以确认关闭客户端到服务器端方向上的连接了。

  第三次:服务器端自从发出ACK确认报文之后,经过 CLOSED-WAIT 阶段,做好了释放服务器端到客户端方向上的连接准备,再次向客户端发出一段TCP报文,其中,

      FIN = 1ACK = 1,表示“已经准备好释放连接了”。

      seq = wack = u+1,表示是在收到客户端报文的基础上,将其序号 seq 值加1作为本段报文确认号 ack 的值。

      服务器端结束 CLOSE-WAIT 阶段,进入 LAST-ACK 阶段。停止在服务器端到客户端的方向上发送数据,但是服务器端仍然能够接收从客户端传输过来的数据。

 

  第四次:客户端收到从服务器端发出的TCP报文,确认了服务器端已做好释放连接的准备,结束 FIN-WAIT-2 阶段,进入 TIME-WAIT 阶段,并向服务器端发送一段报文,其中,

      ACK = 1,表示“接收到服务器准备好释放连接的信号”

      seq = u+1,表示是在收到了服务器端报文的基础上,将其确认号 ack 值作为本段报文序号的值。

      ack = w+1,表示是在收到了服务器端报文的基础上,将其序号 seq 值作为本段报文确认号的值。

      客户端开始在TIME-WAIT阶段等待2MSL(Maximum Segment Lifetime,指一个片段在网络中最大的存活时间

      后“两次挥手”既让客户端知道了服务器端准备好释放连接了,也让服务器端知道了客户端了解了自己准备好释放连接了。

      于是,可以确认关闭服务器端到客户端方向上的连接了,由此完成“四次挥手”。

  与“三次挥手”一样,在客户端与服务器端传输的TCP报文中,双方的确认号 ack 和序号 seq 的值,都是在彼此 ack 和 seq 值的基础上进行计算的,这样做保证了TCP报文传输的连贯性。

  一旦出现某一方发出的TCP报文丢失,便无法继续"挥手",以此确保了"四次挥手"的顺利完成。

 

常见面试题:

  Q:为什么连接的时候是三次握手,关闭的时候却是四次挥手?

  A:TCP建立连接时之所以只需要"三次握手",是因为在第二次"握手"过程中,服务器端发送给客户端的TCP报文是以 SYN 与 ACK 作为标志位的。

     SYN 是请求连接标志,表示服务器端同意建立连接;ACK 是确认报文,表示告诉客户端,服务器端收到了它的请求报文。

     即 SYN 建立连接报文与 ACK 确认接收报文是在同一次"握手"当中传输的,所以"三次握手"正好让双方明确彼此信息互通。

     TCP释放连接时之所以需要“四次挥手”,是因为 FIN 释放连接报文与 ACK 确认接收报文是分别由第二次和第三次"握手"传输的。

  那么为何建立连接时一起传输,释放连接时却要分开传输?

     建立连接时,被动方服务器端结束 CLOSED 阶段进入“握手”阶段并不需要任何准备,可以直接返回 SYN 和 ACK 报文,开始建立连接。

     释放连接时,被动方服务器,突然收到主动方客户端释放连接的请求时并不能立即释放连接,因为还有必要的数据需要处理。

     所以服务器先返回 ACK 确认收到报文经过 CLOSE-WAIT 阶段准备好释放连接之后,才能返回 FIN 释放连接报文

     因此是“三次握手”和“四次挥手”。

 

  Q:两次握手为什么不可以?

  A: 三次握手完成了两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。

         现在把三次握手改成仅需要两次握手,死锁是可能发生的。

    作为例子,考虑计算机A和B之间的通信,假定B给A发送一个连接请求分组,A收到了这个分组,并发送了确认应答分组。按照两次握手的协定,A认为连接已经成功地建立了,可以开始发送数据分组。

    可是,B在A的应答分组在传输中被丢失的情况下,将不知道A是否已准备好,不知道A建立什么样的序列号,B甚至怀疑A是否收到自己的连接请求分组。

    在这种情况下,B认为连接还未建立成功,将忽略A发来的任何数据分组,只等待连接确认应答分组。而A在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。

  Q:为什么 TIME_WAIT 状态(第四次挥手)需要经过 2MSL (最大报文段生存时间) 才能返回到 CLOSE 状态?

  A:目的是确认服务器端是否收到客户端发出的 ACK 确认报文,确保由于网络堵塞等原因迟到且客户端重发的消息在本次连接中作废掉,而不会进入下一次连接中。   

     当客户端发出最后的 ACK 确认报文时,并不能确定服务器端能够收到该段报文。所以客户端在发送完 ACK 确认报文之后,会设置一个时长为 2MSL 的计时器。

     2MSL 即是服务器端发出为FIN报文和客户端发出的 ACK 确认报文所能保持有效的最大时长。

     当服务器端在 1MSL 内没有收到客户端发出的 ACK 确认报文时,就会再次向客户端发出 FIN 报文;

     如果客户端在 2MSL 内,再次收到了来自服务器端的 FIN 报文,说明服务器端由于各种原因没有接收到客户端发出的 ACK 确认报文。

     客户端再次向服务器端发出ACK确认报文,计时器重置,重新开始 2MSL 的计时;

     否则客户端在 2MSL 内没有再次收到来自服务器端的 FIN 报文,说明服务器端正常接收了 ACK 确认报文,客户端可以进入 CLOSED 阶段,完成“四次挥手”。

 

  Q:如果已经建立了连接,但是客户端突然出现故障了怎么办?

  A:TCP设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。

    服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。

    若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

    

posted @ 2021-03-17 14:59  zjcfrancis  阅读(184)  评论(0编辑  收藏  举报