HTTP(0x02)-TCP三次握手与四次挥手详解&面试常见问题

鸣谢!

TCP协议中的三次握手和四次挥手 图解、原因、状态码总结

TCP 三次握手 和 四次挥手

TCP三次握手详解及面试题

一、状态码

  • SYN(synchronous)

    • 请求建立连接的标志位,与ACK标志位搭配使用
    • SYN有两个值:1或0,SYN=1表示请求建立连接,SYN=0表示没有请求建立连接
    • 在实际报文中存在SYN表示:SYN=1,不存在SYN表示:SYN=0
      • [SYN] seq=x : 请求建立连接
      • [SYN, ACK] seq = y, ack = x+1 : 请求建立连接, 确认建立连接
      • [ACK] seq = x + 1 , ack = y + 1 : 确认建立连接
  • ACK(acknowledgement)

    • 确认的标志位,可以与SYN、FIN标志位搭配使用
    • ACK有两个值:1或0,ACK=1表示确认,ACK=0表示未确认
    • 在实际报文中存在ACK表示:ACK=1,不存在ACK表示:ACK=0
  • PSH(push)

    • 这个标志位表示Push操作
    • 所谓Push操作就是指在数据包到达接收端以后,立即传送给应用程序,而不是在缓冲区中排队
  • FIN(finish)

    • 表示发送端已经达到数据末尾,用于请求断开连接
  • RST(reset重置)

    • 这个标志表示连接复位请求。用来复位那些产生错误的连接,也被用来拒绝错误和非法的数据包
  • URG(urgent紧急)

    • 此标志表示TCP包的紧急指针域有效,用来保证TCP连接不被中断,并且督促中间层设备要尽快处理这些数据;
  • seq(Sequence Number顺序号码)

    • 用来标识从TCP发送端向TCP接收端发送的数据字节流,它表示在这个报文段中的的第一个数据字节在数据流中的序号,主要用来解决网络报乱序的问题
    • 实际报文中形式为: seq = x 或 seq = y
      • x、y表示客户端和服务端数据字节序号是相互独立的, 不是互相累加,而是各自的序列
    • 发送方发送seq=x后,期待接收方返回的ack确认号码一定是:ack=x+1
  • ack(Acknowledge Number确认号码)

    • 标识需要获取确认一方期望收到的下一个序号,因此,确认序号应当是上次已成功收到数据字节序号加1。
      • 即收到的seq=x, 则返回的ack一定为ack=x+1
    • 只有当标志位中的ACK标志为1时该确认序列号的字段才有效。主要用来解决不丢包的问题

二、TCP三次握手

img

握手流程:

  • 第一次握手:客户端向服务端发送请求建立连接的报文。报文内容包括:状态码SYN(请求建立连接)、顺序号码seq=x(客户端的数据字节序号,此时是随机生成)。从发送报文到接收返回的确认报文时间段内客户端处于SYN-SENT状态
    • [SYN] seq=x : 请求建立连接
  • 第二次握手:服务端收到请求,向客户端发送同意并请求与客户端建立连接。报文内容包括:状态码SYN(请求建立连接)、状态码ACK(对客户端发来的建立连接表示同意和确认),顺序号码seq=y(服务端的数据字节序号,此时是随机生成),确认号码ack=x+1(在客户端发来的seq=x的基础上+1)。在接收到客户端请求之前,服务端处于LISTEN状态,从接收到客户端请求到再次接收到客户端同意建立连接的报文时间段服务端处于SYN-RCVD状态。
    • [SYN, ACK] seq = y, ack = x+1 : 服务端确认与客户端建立连接,服务端请求与客户端建立连接
  • 第三次握手:客户端收到请求,发送同意与服务端建立连接。报文内容包括:状态码ACK(对服务端发来的建立连接表示同意和确认),顺序号码seq=x+1(客户端的数据字节序号,即ack=x+1的值),确认号码ack=y+1(在服务端发来的seq=y的基础上+1)。
    • [ACK] seq = x+1, ack = y+1 : 客户端确认与服务端建立连接

TCP三次握手实际报文数据:

image-20200916205353301

关键点:

  • 连接的建立需要双方都发送请求连接建立(SYN)和确认建立连接(ACK),所以一共是4次报文,其中第二次握手同时包括了一个请求和一个确认,所以只需要3次挥手即可完成连接的建立

三、TCP四次挥手

img

挥手流程:

  • 第一次挥手:客户端向服务端发送断开连接的报文。
    • 当客户端需要发送给服务端的数据已经全部发送完后,会发送这个断开连接的请求。
    • 发送这个请求后客户端从ESTAB-LISHED状态进入FIN-WAIT-1状态。
    • 发送的报文包括:断开连接的状态码FIN, 顺序号码seq=u(注意此时的u不是随机产生的,而是之前客户端传送的数据的最后一个字节的序号加1)
      • [FIN] seq = u
  • 第二次挥手: 服务端向客户端发送收到FIN的确认报文
    • 当服务端收到客户端发来的断开连接的请求后,会发送确认收到的报文。
    • 发送确认报文后,服务端从ESTAB-LISHED状态进入CLOSE-WAIT状态,在该状态服务端可以继续向客户端发送还未发完的数据;客户端接收到确认报文后进入FIN-WAIT-2状态,继续接收服务端发来的数据。
    • 发送的确认报文包括:确认状态码ACK, 确认号码ack=u+1, 顺序号码seq=v(注意此时的v不是随机产生的,而是之前服务端传送的数据的最后一个字节的序号加1)
      • [ACK] seq = v , ack = u+1
  • 第三次挥手: 服务端向客户端发送断开连接的报文
    • 当服务端需要发送给客户端的数据已经全部发送完后,会发送这个断开连接的请求。
    • 发送完这个请求后,服务端进入LAST-ACK状态;客户端接收到这个请求后,进入TIME-WAIT状态。
    • 发送的报文包括:断开连接的状态码FIN, 顺序号码seq=w(注意此时的w不是随机产生的,而是之前服务端传送的数据的最后一个字节的序号加1)
      • [FIN] seq=w
  • 第四次挥手: 客户端向服务端发送收到FIN的确认报文
    • 在客户端收到服务端断开连接的请求后,向服务端发送确认报文
    • 发送完这个确认报文后,客户端进入TIME-WAIT状态;服务端收到这个确认报文后进入CLOSED状态,资源释放。若在2MSL时间内客户端没有收到服务端重复发送断开连接的请求,则表明服务端已经收到确认报文,则客户端也进入CLOSED状态,释放资源
    • 发送的确认报文包括:确认状态码ACK, 确认号码ack=w+1
      • [ACK] ack = w+1

关键点:

  • 断开连接也需要客户端和服务端都发送断开连接请求(FIN)和确认收到断开连接请求(ACK),这就需要4次报文,而不同于建立连接(在第二次握手中可以同时发送ACK和SYN两个状态码),在断开连接过程中,服务端收到客户端断开连接请求后,只表明客户端再没有数据发送给服务端了,但是服务端任然还有数据需要发送给客户端,所以服务端先给客户端发送一个确认ACK报文,继续向客户端发送数据,数据发送完后再发送断开连接的请求,所以,4次报文只能单独4次来发送,所以必须是4次挥手。

注意

中断连接端可以是客户端,也可以是服务端。

四、常见问题

img

【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?

  • 因为当服务端端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。
  • 但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,继续发送还没发送完的数据。只有等到服务端所有的数据都发送完了,才能发送FIN报文,因此不能一起发送。故需要四步握手。

【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?

  • 虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可能最后一个ACK丢失,TIME_WAIT状态就是用来重发可能丢失的ACK报文。所以2MSL时间的等待的本质是解决ACK丢失的问题
  • TIME_WAIT在四次挥手中有着不可替代的位置,如果没有TIME-WAIT,主动方就会直接进入CLOSED状态,(假设主动方是客户端,被动方是服务端)这时候如果立即重启客户端使用相同的端口,如果因为网络中种种原因最后一次ACK丢失了,服务端就会重复FIN请求,这时这个FIN就会被重新启动的客户端接收到,或者新启动的客户端向服务端发起请求的时候,因为服务端正在等待最后一次ACK,因此新连接请求发送的SYN就会被服务端认为是请求码错误,服务端就会回复RET重置连接。所以就需要主动方发送最后一次ACK之后进入TIME_WAIT状态,等待2MSL(两个报文最大生命周期),等待这段时间就是为了如果接收到了重发的FIN请求能够进行最后一次ACK回复,让在网络中延迟的FIN/ACK数据都消失在网络中,不会对后续连接造成影响
  • 2MSL就是一个发送和一个回复所需的最大时间,正好对应发送过去ACK和等待下一个FIN发送过来的时间(如果服务端在一个MSL时间里没收收到ACK,则会发送下一个FIN)。如果直到2MSL,客户端都没有再次收到FIN,那么客户端推断ACK已经被成功接收,则结束TCP连接。
  • 等待的另一个作用是防止“已失效的连接请求报文段”出现在本连接中。客户机在发送完最后一个ACK报文段,再经过时间2MSL,就可以使本连接持续的时间内所产生的报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。

MSL是Maximum Segment Lifetime英文的缩写,中文可以译为“报文最大生存时间”,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为tcp报文(segment)是ip数据报(datagram)的数据部分,具体称谓请参见《数据在网络各层中的称呼》一文,而ip头中有一个TTL域,TTL是time to live的缩写,中文可以译为“生存时间”,这个生存时间是由源主机设置初始值但不是存的具体时间,而是存储了一个ip数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减1,当此值为0则数据报将被丢弃,同时发送ICMP报文通知源主机。RFC 793中规定MSL为2分钟,实际应用中常用的是30秒,1分钟和2分钟等。

【问题3】2次握手就可以建立连接,为什么需要3次握手?

《计算机网络》中是这样说的:

为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。
例子如下:
client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”

【问题4】为什么是4次挥手?

  • 断开连接也需要客户端和服务端都发送断开连接请求(FIN)和确认收到断开连接请求(ACK),这就需要4次报文,而不同于建立连接(在第二次握手中可以同时发送ACK和SYN两个状态码),在断开连接过程中,服务端收到客户端断开连接请求后,只表明客户端再没有数据发送给服务端了,但是服务端任然还有数据需要发送给客户端,所以服务端先给客户端发送一个确认ACK报文,继续向客户端发送数据,数据发送完后再发送断开连接的请求,所以,4次报文只能单独4次来发送,所以必须是4次挥手。

【问题5】 为什么不能一次握手建立连接?

  • TCP是面向连接的,一定是确保双方可以收到消息,所以至少需要一问一答2次挥手。若只1次握手,客户端是无法知道服务端端是否收到连接请求,也就无法建立稳定、可靠的连接。

【问题6】 只2次握手会出现的问题?

《计算机网络》中列举的例子:

  • 如果客户端想建立连接,给服务端发了一个连接请求(SYN),但是由于网络中种种情况,导致没有及时到达服务端,这就导致客户端在很长一段时间中没有收到回复消息(ACK),这时客户端又给服务端发送一个SYN,这次的发送和接收的很顺利,很快就收到了ACK,但是这时之前的SYN终于到了服务端,服务端规规矩矩的为这个SYN申请资源,然后返回ACK。由于之前的SYN已经失效了,所以客户端也不会去理会这个ACK,但是服务端并不知道这个SYN已经失效了,一直为客户端开着资源,这就造成了资源的浪费。

img

【问题7】每次握手失败如何应对?

第一次握手失败:

  如果第一次的SYN传输失败,两端都不会申请资源。如果一段时间后之前的SYN发送成功了,这时客户端只会接收他最后发送的SYN的SYN+ACK回应,其他的一概忽略,服务端也是如此,会将之前多申请的资源释放了。

第二次握手失败:

  如果服务端发送的SYN+ACK传输失败,客户端由于没有收到这条响应,不会申请资源,虽然服务端申请了资源,但是迟迟收不到来自客户端的ACK,也会将该资源释放。

第三次握手失败:

详细解释

  如果第三次握手的ACK传输失败,导致服务端迟迟没有收到ACK,服务端就会释放资源,而这时候客户端认为自己已经连接好了,就会给服务端发送数据,服务端由于没有收到第三次握手,就会以RST包对客户端响应。但是实际上服务端会因为没有收到客户端的ACK多次发送SYN+ACK,次数是可以设置的,如果最后还是没有收到客户端的ACK,则释放资源。

在这里插入图片描述

【问题8】如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。