【图解】TCP的三次握手与四次挥手

写在前面:
最近在看《图解TCP/IP》一书,这本书将整个TCP/IP的协议分层模型讲的很平民化,非常易于看懂,但是在讲到TCP的连接管理时,仅仅简单地描述了三次握手的SYN与ACK交互过程,以及四次挥手的FIN与ACK交互过程。为了加深对于这个重要概念的理解,我参考了网上的一些文章与博客,再根据自己对于三握四挥的理解,做一个描述过程。如有错误,欢迎指正。

1.TCP协议连接过程概述

1.1 TCP的连接过程概述

  TCP提供面向有连接的通信传输。面向有连接是指在数据通信开始之前先做好通信两端之间的准备工作[1]
  UDP是一种面向无连接的通信协议,因此不检查对端是否可以通信,直接将UDP包发送出去。TCP则与此相反,它会在数据通信之前,通过TCP首部发送一个SYN包作为建立连接的请求等待确认应答:如果对端发来确认应答,则认为可以进行数据通信;如果对端的确认应答未能到达,就不会进行数据通信[1]。这段过程称之为“三次握手”。
  此外,在通信结束时会进行断开连接的处理(FIN包)[1]。这段过程称之为“四次挥手”。
  其中值得一提的是,可以使用TCP首部用于控制的字段来管理TCP连接。一个连接的建立与断开,正常过程中至少需要来回发送7个包才能完成[1](可见,三次握手与四次挥手为连接过程的最低限度,在实际情况中,可能由于各种各样的原因,导致握手数与挥手数都远大于7次)。

1.2 三次握手与四次挥手概述

TCP连接的建立与断开
  三次握手与四次挥手的具体流程如上图所示[1]。(想要理解三次握手与四次挥手的基本理念,以及一些基本的逻辑,查看其概述即可。倘若想要理解到TCP包的首部信息等程度时,可以继续查看本文的第2章 三次握手与四次挥手的详解)
  其中三次握手的包为:

  1. 第一次握手: 客户端向服务端发送的【客户端请求建立连接SYN包】;
  2. 第二次握手:服务端向客户端发送的【服务端请求建立连接SYN 与 针对【客户端请求建立连接SYN包】的确认应答ACK包】;
  3. 第三次握手:客户端向服务端发送的【针对【服务端请求建立连接SYN 】的确认应答ACK包】。

  而对应的四次挥手为:

  1. 第一次挥手:客户端向服务端发送的【客户端请求切断连接FIN包】;
  2. 第二次挥手:服务端向客户端发送的【针对【客户端请求切断连接FIN包】的确认应答ACK包】;
  3. 第三次挥手:服务端向客户端发送的【服务端请求切断连接FIN包】;
  4. 第四次挥手:客户端向服务端发送的【针对【服务端请求切断连接FIN包】的确认应答ACK包】。

  通过以上的七种典型TCP包来看,客户端与服务端的连接其实就是一个简单的“你问我,我问你”的问题,包的类型无非就是【SYN/FIN型请求包】与【针对请求包的确认应答ACK包】。每当发送端发送一个请求包后,接收端必然会在之后的某段时间,回应一个确认应答包(即所谓的有问必答)。

2.三次握手与四次挥手详解

2.1 与三握四挥有关的TCP的首部格式

TCP首部格式
  如上图所示为TCP的首部格式图[1]。除了源端口号和目标端口号随着客户端与服务端的发送方不同而有所不同外,对于通信过程有直接关联的有“序列号Seq”、“确认应答号Ack”以及控制位中的三个标志位。

  1. 序列号(Sequence Number):
      字段长32位。序列号(有时也叫序号)是指发送数据的位置。每发送一次数据,就累加一次该数据字节数的大小。序列号不会从0或1开始,而是在建立连接时由计算机生成的随机数作为其初始值,通过SYN包传给接收端主机。然后再将每转发过去的字节数累加到初始值上表示数据的位置。此外,在建立连接和断开连接时发送的SYN包和FIN包虽然并不携带数据,但是也会作为一个字节增加对应的序列号[1]
  2. 确认应答号(Acknowledgement Number):
      确认应答号字段长度32位。是指下一次应该收到的数据的序列号。实际上,它是指已收到确认应答号减一为止的数据。发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收[1]
  3. 控制位(Control Flag):
      字段长为8位,每一位从左至右分别为CWR、ECE、URG、ACK、PSH、RST、SYN和FIN,这些控制标志也叫做控制位,如下图所示[1]
    控制位格式
      其中与三握四挥相关联的控制位为ACK、SYN和FIN,当它们对应位上的值为1时,具体含义如下:
    3.1 ACK (Acknowledgement Flag):
      该位为1时,确认应答的字段变为有效。TCP规定除了最初建立连接时的SYN包之外该位必须设置为1[1]
    3.2 SYN (Synchronize Flag):
      用于建立连接。SYN为1表示希望建立连接,并在其序列号的字段进行序列号初始值的设定[1]
    3.3 FIN (Fin Flag):
      该位为1时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换FIN位置为1的TCP段。每个主机又对对方的FIN包进行确认应答以后就可以断开连接。不过,主机收到FIN设置为1的TCP段以后不必马上回复一个FIN包,而是可以等到缓冲区中的所有数据都因已成功发送而被自动删除之后再发[1]

  为统一名称起见,本博客中对于序列号(Sequence Number)简称为Seq,确认应答号(Acknowledgement Number)简称为Ack,控制位中的ACK、SYN与FIN均使用原名。

2.2 针对连接状态图的三握四挥详解

  从第一节的概述图来看,三次握手与四次握手的主要区别在于,对于第一个请求包的应答时间不同。即第二次握手的【服务端请求建立连接SYN 与 针对【客户端请求建立连接SYN包】的确认应答ACK包】,在挥手过程中变成了第二次挥手的【针对【客户端请求切断连接FIN包】的确认应答ACK包】与第三次挥手的【服务端请求切断连接FIN包】。
  这里就涉及到对“连接状态”这个概念的理解,无论对于客户端还是服务端,两者在连接过程中的状态有不同也有相同。本人结合书籍资料与网上博客的论述[1][2][3],对于七个包的发送过程与两端各自的连接状态统一绘图,其中三次握手过程如下所示:
三次握手过程

2.2.1 握手过程详解:
  1. 第一次握手过程:
    1.1 客户端状态:
      初始为【Closed关闭状态】,当其发送【客户端请求建立连接SYN包】到服务端后,客户端变为【SYN-SENT同步已发送状态】;
    1.2 客户端请求建立连接SYN包:
      此包中为客户端发送的请求建立连接信息,故其SYN = 1,ACK = 0(不画出),同时此数据属于客户端第一次发送的信息,故设其序列号的初始值为x,即Seq = x;
    1.3 服务端状态:
      服务端由于未与对应客户端连接,所以其一直处于【Listen等待连接状态】。当客户端的【客户端请求建立连接SYN包】到达的同时,其立即变为【SYN-RCVD同步已收到状态】。
  2. 第二次握手过程:
    2.1 服务端状态:
      服务端状态变为【SYN-RCVD同步已收到状态】后,立即发送【服务端请求建立连接SYN 与 针对【客户端请求建立连接SYN包】的确认应答ACK包】;
    2.2 服务端请求建立连接SYN 与 针对【客户端请求建立连接SYN包】的确认应答ACK包:
      此包中包含了服务端的请求建立连接信息,以及对于客户端的请求连接的应答,故其SYN = 1,ACK = 1。同时由于此包是服务端发送的第一条信息,故设其序列号为y,则Seq = y,再根据SYN/ACK/FIN包的字节按1计算,得到确认应答号为1.2中SYN的序列号加1,即Ack = x+1;
    2.3 客户端状态:
      其在收到确认应答信息前,一直处于【SYN-SENT同步已发送状态】。当收到服务端的【服务端请求建立连接SYN 与 针对【客户端请求建立连接SYN包】的确认应答ACK包】后,其变为【ESTBLISHED已连接状态】。
  3. 第三次握手过程:
    3.1 客户端状态:
      客户端状态变为【ESTBLISHED已连接状态】后,立即发送【针对【服务端请求建立连接SYN 】的确认应答ACK包】,在此消息之后,客户端将开始向服务端发送实际的通讯信息;
    3.2 针对【服务端请求建立连接SYN 】的确认应答ACK包:
      此包中为客户端的确认应答信息,故其ACK = 1。此消息为客户端发送的第二条消息,所以其序列号应该加上之前发送的SYN消息的字节,即Seq = x+1。同时其确认应答号来源于2.2中的序列号加1,故其Ack = y+1;
    3.3 服务端状态:
      服务端在收到【针对【服务端请求建立连接SYN 】的确认应答ACK包】后,立即由【SYN-RCVD状态】变为【ESTBLISHED已连接状态】,在此之后,服务端将接收到客户端发来的实际通讯信息。
      至此三次握手过程则全部完成。

  通过以上过程不难看出,在三次握手过程中,客户端的状态有三种:【Closed关闭状态】、【SYN-SENT同步已发送状态】与【ESTBLISHED已连接状态】;而服务端的状态也有三种:【Listen等待连接状态】、【SYN-RCVD同步已收到状态】与【ESTBLISHED已连接状态】。不同的状态之间由于发送/接收到了相关消息而发生变化,而这种情况在四次挥手过程中却并不一定。
挥手过程图

2.2.2 挥手过程详解:
  1. 第一次挥手过程:
    1.1 客户端状态:
      客户端处于【ESTBLISHED已连接状态】,当其认为通信过程已结束时,发送【客户端请求切断连接FIN包】给服务端,同时自身进入【FIN-WAIT-1终止等待1状态】,停止发送所有的通讯信息(可能是通讯信息发完了自动停止,也有可能是通信未发完的强制停止);
    1.2 客户端请求切断连接FIN包:
      此包为客户端的请求切断连接包,故其FIN = 1,ACK = 1。由于此时不知客户端相对握手阶段发送了多少字节的信息,所以设此时的序列号为m,故其Seq = m;
    1.3 服务端状态:
      服务端此前一直处于【ESTBLISHED已连接状态】,当其接受到【客户端请求切断连接FIN包】后,其状态转换为【CLOSE-WAIT关闭等待状态】。
  2. 第二次挥手过程:
    2.1 服务端状态:
      服务端在切换成【CLOSE-WAIT关闭等待状态】后,会立马反馈一个【针对【客户端请求切断连接FIN包】的确认应答ACK包】,但是不会像第二次握手阶段一样立即发送客户端的切断连接请求。这是因为可能有某些堵塞的客户端通讯信息还未发到服务端,服务端在反馈ACK包后不会立即请求断开连接,而是自身等待一段时间的信息接收后,再进行关闭过程;
    2.2 针对【客户端请求切断连接FIN包】的确认应答ACK包:
      此包为服务端向客户端发送的确认应答包,故其ACK = 1。由于此时不知服务端相对握手阶段发送了多少字节的信息,所以设此时的序列号为n,故其Seq = nn;
    2.3 客户端状态:
      客户端原状态为【FIN-WAIT-1终止等待1状态】,当其接收到【针对【客户端请求切断连接FIN包】的确认应答ACK包】后,将会进入到【FIN-WAIT-2终止等待2状态】。此时不能算断开了连接,因为客户端收到的仅仅是服务端的ACK反馈消息,还没有得到服务端的中断连接请求;换句话说,就是这段切断连接的请求仍属于客户端的一厢情愿,还没有得到服务端的准确回应。
  3. 第三次挥手过程:
    3.1 服务端状态:
      服务端处于【CLOSE-WAIT关闭等待状态】后,经过一段时间的等待后,没有继续接收到客户端的消息了,则进入【LAST-ACK最后确认状态】,并发送【服务端请求切断连接FIN包】给客户端,表示其认为连接可以切断了;
     (这里值得一提的是,之所以进入的是【LAST-ACK最后确认状态】,是因为可能在发送过程中【服务端请求切断连接FIN包】发送失败,这段过程即为等待客户端的反馈,倘若一定时间内未等到则继续重发此包)
    3.2 服务端请求切断连接FIN包:
      此包为服务端发送给客户端的切断连接请求包,故其FIN = 1,ACK = 1。但此时不确定此消息为服务端在挥手过程中的第二条消息(可能在发送第一条消息之后,又收到了客户端堵塞的消息,而返回了ACK消息导致原n值递增),故假设此时的序列号为nn,故其Seq = nn。而由于Ack号表示下次应该收到的数据号,故其Ack仍与挥手过程中的第一条消息的Ack相同,即Ack = m+1;
    3.3 客户端状态:
      当客户端收到【服务端请求切断连接FIN包】后,其从【FIN-WAIT-2终止等待2状态】进入到【TIME-WAIT时间等待状态】;
  4. 第四次挥手过程:
    4.1 客户端状态:
      当客户端变为【TIME-WAIT时间等待状态】后,立即发送【针对【服务端请求切断连接FIN包】的确认应答ACK包】,这段状态持续2MSL(2个报文段最大生存时间)后,客户端将变为【Closed未连接状态】;
      (这里值得一提是这个【TIME-WAIT时间等待状态】的存在意义:因为客户端在发送完最后的ACK包后与服务端两者之间则再无联系,倘若这个ACK包发送失败了,那么服务端会在一定时间后重发【服务端请求切断连接FIN包】。假如客户端在发送完ACK包后就关闭了,那么服务端则无法得到确切的ACK确认应答消息,导致客户端以为服务端关闭了,但是服务端还未关闭的情况。这个状态就是用于处理重发的FIN包而存在的,至于为什么是2MSL,可以参考此参考文献[4]
    4.2 针对【服务端请求切断连接FIN包】的确认应答ACK包:
      此包为挥手过程中理论的最后一个包,其为客户端发送给服务端的确认应答包,故其ACK = 1,且此消息为客户端在挥手过程中发送的第二条消息(这里值得一提的是,堵塞的消息属于挥手过程前所发的),所以其Seq = m+1。至于Ack,仍参考所接收到的上条服务端消息的Seq(为nn),所以其Ack = nn+1;
    4.3 服务端状态:
      当服务端成功接收到【针对【服务端请求切断连接FIN包】的确认应答ACK包】后,表示客户端以及成功接收到了其FIN包并成功反馈,则可以直接重新进入【Listen等待连接状态】,等待下一次/个的连接过程。
      至此四次挥手过程全部完成。
      通过以上过程可见,挥手过程相对于握手过程更复杂一些,客户端经历了【ESTBLISHED已连接状态】、【FIN-WAIT-1终止等待1状态】、【FIN-WAIT-2终止等待2状态】、【TIME-WAIT时间等待状态】以及【Closed未连接状态】;而服务端经历了【ESTBLISHED已连接状态】、【CLOSE-WAIT关闭等待状态】、【LAST-ACK最后确认状态】以及【Listen等待连接状态】。
2.2.3 问题与雷区总结:

  通过个人撰写自己的这段理解的过程中,我个人认为其中的雷区在于两点:握手/挥手的某些状态为什么存在,以及某些Seq和Ack的值为什么是那个值而不是这个值。其中具体如下:

  1. 为什么是三次握手而不是两次握手和四次握手??(为什么会有【SYN-SENT同步已发送状态】与【SYN-RCVD同步已收到状态】?)
      答:"已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。这就很明白了,防止了服务器端的一直等待而浪费资源[3]
      而为什么有人会问为什么不是四次握手,是因为挥手过程中有一个等待的过程,而这个过程在握手过程中是不需要的,因为建立连接和关闭连接的速度和流程截然不同。当然你愿意加也没关系,只不过是浪费了一些网络资源在里面而已。换句话说,为什么不是四次握手五次握手?很简单,可以握,但没必要。
  2. 为什么是四次挥手?而不是三次或者五次?
      答:这个问题,虽然比握手的问题麻烦,但是实际上却比握手的好回答的多,其主要的核心就是在于挥手过程中的客户端的五个过程,以及服务端的四个过程的变换关系,而这些过程中最重要的就是【TIME-WAIT时间等待状态】、【CLOSE-WAIT关闭等待状态】与【LAST-ACK最后确认状态】的存在原因,具体的可以参考我的2.2.2节中的具体描述,在此不做赘述。这其中可能会涉及到一些TCP中的计时器的概念,可以参考此篇博文[5]
  3. 挥手过程中的第三次挥手的Seq和Ack为什么是nn和m+1?
      答:具体的原因我在过程中以及详细说明:第三次挥手的过程中,不确定此消息为服务端在挥手过程中的第二条消息(可能在发送第一条消息之后,又收到了客户端堵塞的消息,而返回了ACK消息导致原n值递增),故假设此时的序列号为nn,故其Seq = nn。而由于Ack号表示下次应该收到的数据号,故其Ack仍与挥手过程中的第一条消息的Ack相同,即Ack = m+1;
  4. 总结
      除去为啥握手是三次,挥手是四次的问题,其实所有的问题,都是在好好分析理解三握四挥的流程后,都可以融会贯通的。这段TCP的通信连接过程很经典,网上的各类分析、详解数不胜数,我在写这篇博文的过程中也受益良多,但是最后我感觉在详细的描述了其中的所有过程后,我才觉得自己对于这段通信流程才有了切实的理解,也许这就是所谓的实践出真知吧。

在撰写本博客过程中所参考的书籍与博文如下:
[1] 竹下隆史. 图解TCP/IP[M]. 人民邮电出版社, 2013.
[2] https://blog.csdn.net/qq_38950316/article/details/81087809;
[3] https://www.cnblogs.com/shihaochangeworld/p/5770294.html;
[4] https://blog.csdn.net/unix21/article/details/16918307;
[5] https://www.cnblogs.com/metoy/p/5479720.html;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方寸间沧海桑田

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值