TCP三次握手及其四次挥手详解:原理篇

tcp/ip分层结构

下图括号中的数字代表的是当前域所占的空间大小,单位是bit位。最外层的黄色,表示数据链路层的头部,一共14字节;浅蓝色的部分是IP头部,一般是20字节;紫色部分是TCP头部,一般是20字节;最内部的是数据包内容。

TCP传给IP的数据单元称作TCP报文段或简称为TCP段(TCP segment)。IP传给网络接口层的数据单元称作IP数据报(IP datagram)。通过以太网传输的比特流称作帧(Frame)

img

由图中可以看到,tcp/ip协议是分层的,一共有四层,每一层负责不同的功能:

  1. 链路层,有时也称作数据链路层或网络接口层,通常包括操作系统中的设备驱动程序和计算机中对应的网络接口卡。它们一起处理与电缆(或其他任何传输媒介)的物理接口细节。
  2. 网络层,有时也称作互联网层,处理分组在网络中的活动,例如分组的选路。在TCP/IP协议族中,网络层协议包括IP协议(网际协议),ICMP协议(Internet互联网控制报文协议),以及IGMP协议(Internet组管理协议)。
  3. 运输层主要为两台主机上的应用程序提供端到端的通信。在TCP/IP协议族中,有两个互不相同的传输协议:TCP(传输控制协议)和UDP(用户数据报协议)。
  4. 应用层负责处理特定的应用程序细节。

这四层分层协议对应在wireshark上结构如下:

1559615628589

TCP层头部

  1. 端口号:用来标识同一台计算机的不同的应用进程。TCP报头中的源端口号和目的端口号同IP数据报中的源IP与目的IP唯一确定一条TCP连接。源端口:源端口和IP地址的作用是标识报文的返回地址。目的端口:端口指明接收方计算机上的应用程序接口。

  2. 序列号,占4个字节,TCP连接中传送的字节流中的每个字节都按顺序编号。例如,一段报文的序号字段值是 301 ,而携带的数据共有100字段,显然下一个报文段(如果还有的话)的数据序号应该从401开始;用来解决网络包乱序(reordering)问题。

  3. 确认号,占4个字节,是期望收到对方下一个报文的第一个数据字节的序号。用于确认收到,用来解决不丢包的问题
  4. 数据偏移/首部长度:4bits。由于首部可能含有可选项内容,因此TCP报头的长度是不确定的,报头不包含任何任选字段则长度为20字节,4位首部长度字段所能表示的最大值为1111,转化为10进制为15,15*32/8 = 60,故报头最大长度为60字节。首部长度也叫数据偏移,是因为首部长度实际上指示了数据区在报文段中的起始偏移值。
  5. 保留:为将来定义新的用途保留,现在一般置0。
  6. 控制位:常见为URG ACK PSH RST SYN FIN,共6个,每一个标志位表示一个控制功能。
  • URG:紧急指针标志,为1时表示紧急指针有效,为0则忽略紧急指针。
  • ACK:(确认编号,Acknowledgement Number),为1时表示确认号有效,为0表示报文中不含确认信息,忽略确认号字段。
  • PSH:push标志,为1表示是带有push标志的数据,指示接收方在接收到该报文段以后,应尽快将这个报文段交给应用程序,而不是在缓冲区排队。
  • RST:重置连接标志,用于重置由于主机崩溃或其他原因而出现错误的连接。或者用于拒绝非法的报文段和拒绝连接请求。
  • SYN:(同步序列编号,Synchronize Sequence Numbers),用于建立连接过程,在连接请求中,SYN=1和ACK=0表示该数据段没有使用捎带的确认域,而连接应答捎带一个确认,即SYN=1和ACK=1。
  • FIN:finish标志,用于释放连接,为1时表示发送方已经没有数据发送了,即关闭本方数据流。

  1. 窗口:滑动窗口大小,用来告知发送端接受端的缓存大小,以此控制发送端发送数据的速率,从而达到流量控制。窗口大小时一个16bit字段,因而窗口大小最大为65535。用于解决流控的
  2. 校验和:奇偶校验,此校验和是对整个的 TCP 报文段,包括 TCP 头部和 TCP 数据,以 16 位字进行计算所得。由发送端计算和存储,并由接收端进行验证。
  3. 紧急指针:只有当 URG 标志置 1 时紧急指针才有效。紧急指针是一个正的偏移量,和顺序号字段中的值相加表示紧急数据最后一个字节的序号。 TCP 的紧急方式是发送端向另一端发送紧急数据的一种方式。
  4. 选项和填充:最常见的可选字段是最长报文大小,又称为MSS(Maximum Segment Size),每个连接方通常都在通信的第一个报文段(为建立连接而设置SYN标志为1的那个段)中指明这个选项,它表示本端所能接受的最大报文段的长度。选项长度不一定是32位的整数倍,所以要加填充位,即在这个字段中加入额外的零,以保证TCP头是32的整数倍。
  5. 数据部分: TCP 报文段中的数据部分是可选的。在一个连接建立和一个连接终止时,双方交换的报文段仅有 TCP 首部。如果一方没有数据要发送,也使用没有任何数据的首部来确认收到的数据。在处理超时的许多情况中,也会发送不带任何数据的报文段。

TCP三次握手详解

所谓三次握手(Three-Way Handshake)即建立TCP连接,是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发,整个流程如下图所示:

  1. TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态;
  2. TCP客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位SYN=1,同时选择一个初始序列号 seq=x ,此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。
  3. TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号 seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。
  4. TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。
  5. 当服务器收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。

为什么服务端还需要一个seq呢?因为TCP的连接是双向的。server也是需要发送数据,所以需要维护这个序列号。

TCP四次挥手

数据传输完毕后,双方都可释放连接,这个过程就是tcp四次挥手(Four-Way Wavehand)。最开始的时候,客户端和服务器都是处于ESTABLISHED状态,然后客户端主动关闭,服务器被动关闭。

  1. 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
  2. 服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
  3. 客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
  4. 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
  5. 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2 * MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态
  6. 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

初始化序列号(ISN)

在前面学习tcp连接三次握手的时候,客户端和服务端在建立tcp连接时,双方都会发送SYN报文并初始化序号(英文为:Initial Sequence Number,简称ISN)。所以说在三次握手时,seq并不是固定为1的,是通过ISN算来的。为什么要这么不固定一个值呢?

https://blog.csdn.net/qq_35733751/article/details/80552037所示,如果seq固定为1,那么在A跟B第一次交互时由于各种问题出现异常了,然后A跟B发起第二次请求时成功了,B也正常收到了240B的数据,后来第一次tcp连接时传的包,竟然B收到了。那B会怎么办呢?由于A每次发送报文都使用了相同的序号(SEQ = 1),这可能会让B误认为这200字节是属于新建立的tcp连接的,因此B会对这200字节数据照收不误。这就导致B在收到新tcp连接的数据后,又收到旧tcp连接的数据,从而出现数据乱序的问题。

所在三次握手时,seq都是按一定的算法生成的,即客户端和服务端双方在建立tcp连接并初始化序列号ISN,那么上面所说的这些情况从一开始就可以避免。ISN会和一个假的时钟绑在一起,这个时钟会在每4微秒对ISN做加一操作,直到超过2^32,又从0开始。这样,一个ISN的周期大约是4.55个小时。因为,我们假设我们的TCP Segment在网络上的存活时间不会超过Maximum Segment Lifetime(缩写为MSL),所以,只要MSL的值小于4.55小时,那么,我们就不会重用到ISN

问题

1. 为什么TCP客户端最后还要发送一次确认呢?

主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。有种情况,在A发出的第一个连接请求报文,但由于网络问题在某些网络节点长时间滞留了,这时A会重发一个请求报文,第二次发送就立马到达B了,相互通信完成之后,第一次的SYN很神奇地到达了B, 本来这是一个早已失效的报文段,但B收到此失效的连接请求报文段后,就误认为A又发出了一次新的连接请求。 于是又向A发出确认报文段,同意建立连接。假定不采用三次握手,那么只要B发出确认,新的连接就建立了。 由于现在A并没有发出建立连接的请求,因此不会理会B的确认,也不会向B发送数据。 但B却以为新的运输连接已经建立了,并一直等待A发来数据。 B的许多资源就这样浪费了。

2. 为什么客户端最后由TIME-WAIT变成CLOSE状态时还要等待2MSL(最大报文段生存时间)?
MSL(Maximum Segment Lifetime),TCP允许不同的实现可以设置不同的MSL值。

第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。

第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。

3. 为什么建立连接是三次握手,关闭连接确是四次挥手呢?

建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。
而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。

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

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

5. 如果server端接到了clien发的SYN后回了SYN-ACK后client掉线了怎么办?
如果server端接到了clien发的SYN后回了SYN-ACK后client掉线了,server端没有收到client回来的ACK,那么,这个连接处于一个中间状态,即没成功,也没失败。于是,server端如果在一定时间内没有收到的TCP会重发SYN-ACK。在Linux下,默认重试次数为5次,重试的间隔时间从1s开始每次都翻售,5次的重试时间间隔为1s, 2s, 4s, 8s, 16s,总共31s,第5次发出后还要等32s都知道第5次也超时了,所以,总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 2^6 -1 = 63s,TCP才会把断开这个连接。

总体图

参考资料

0%