谈谈在TCP面试中的点(二)-三次握手和四次挥手

237 阅读8分钟

TCP的连接过程详解

在发送HTTP请求以及在接收方和发送方正式发送数据之前首先是要先建立TCP连接,最开始会有三次握手的动作,在确认连接之后才会开始发送数据,同样在数据发送完成后会有四次挥手的动作,这里先不讨论三次握手四次挥手,只是针对于整个连接过程做一次拆解。

image.png

上图就是TCP的一个三次握手的动作,大概步骤如下:

  • 客户端发出一个建立连接的请求,SYN = 1,ACK=0,Length=0
  • 服务器收到请求后对建立连接的请求做出回应,SYN = 1,ACK=1,Length=0
  • 客户端对服务器的回应做出回应,SYN = 0,ACK=1,Length=0

同时注意之前有写到序号是在首部中确认的,这里看到的都是相对值,其实本身的值会是一个很大的随机值,为了简化搞出来个相对值,例如下图中可以看到,同样确认号也是如此

image.png

同样可以看到在三次握手的前两次TCP首部大部分均为32字节,除掉固定的20字节,也就是说前两次都有额外的信息存在于选项中:

  • 最大段的大小(MSS):反映了TCP数据包最大的大小
  • 窗口比例(Window Scale):这个值主要用于对方知道实际窗口大小

比如下图中实际窗口大小为7680,但是首部中窗口字段为60,但是窗口比例的值为128,7680=60*128,因为窗口字段仅占有2个字节,可能不足以表达很大的数字,那么这里就靠窗口比例这个数据来放大它 image.png

所以三次握手中前两次会告知对方起始序号MSS窗口窗口比例这些信息

TCP建立连接到发送数据大概流程如下图所示 image.png

大致流程:

  • 客户端序号从s1开始,因为要建立连接syn标志位为1,没有需要回应的所以ack0,长度为0
  • 服务器序号从s2开始,同意建立连接syn标志位为1acks1+1,长度为0
  • 客户端对服务端进行回应本次序号从s1+1开始,syn标志位为0acks2+1,长度为0
  • 客户端发起HTTP请求,序号从s1+1开始,syn标志位为0acks2+1,长度为k
  • 服务端发出第一个数据包,序号从s2+1开始,syn标志位为0acks1+k+1,长度为b1
  • 服务端发出第二个数据包,序号从s2+b1+1开始,syn标志位为0acks1+k+1,长度为b2
  • 服务端发出第三个数据包,序号从s2+b1+b2+1开始,syn标志位为0acks1+k+1,长度为b3
  • 客户端连续收到了服务器的三个数据包对此进行回应,序号从s1+k+1开始,syn标志位为0acks2+b1+b2+b3+1,长度为0

TCP的三次握手

image.png

TCP的三次握手可以用上图来表示,这里会把客户端和服务端划分出几种状态:

  • CLOSED客户端处于关闭状态
  • LISTEN服务端处于监听状态,等待客户端连接
  • SYN-SENT客户端已发送同步报文,等待服务端的响应
  • SYN-RCVD服务端已接收到客户端发出的请求同步要求,当收到客户端的确认报文后会进入完成建立连接的状态
  • ESTABLISHED表示连接已经建立完成

TCP建立连接前两次的特点:

  • 同步字段SYN均为1
  • 数据部分长度都是0
  • 首部长度大多为32字节:固定头部20字节+选项12字节
  • 双方交换的信息包括MSS、是否支持SACK窗口缩放系数window scale等均存放于首部的选项字段

疑问

为什么一定要是三次握手,交换信息其实在前两步就完成了,为什么一定要有第三次才可以?

一定要是三次握手而不是两次握手的主要目的是为了防止服务端会一直处于等待的状态,会浪费资源

如果建立连接只采用了两次握手的话,可能会出现:

  • 假设客户端发出的第一个请求建立连接的报文由于网络延迟,在连接已经完成后才发送到服务端
  • 这本是一个已经失效的连接请求,但是服务端收到后误以为是客户端最新发起的连接请求
  • 于是服务端向客户端发出了确认连接的报文,连接建立成功
  • 但是客户端此时并没有需求去连接服务器,也就没有数据要发给服务器,这样服务器资源就因为长时间处于等待状态而被浪费

上述情况如果采用三次握手就会避免浪费资源,只要客户端不向服务端发出确认报文,连接便不会确认,这样就避免了上述情况的发生

这里还要注意另一种情况,如果第三次握手失败了,此时服务器处于SYN-RCVD状态,如果等不到客户端的确认报文,服务端会重新发送SYN+ACK包,如果服务端多次重发SYN+ACK都得不到客户端的确认报文,就会发送RST包,强制关闭连接

TCP四次挥手

刚才分析了TCP建立连接时的三次握手,那么在数据传输完成后两端要断开连接会经历四次挥手的过程

image.png

上图对四次挥手进行了区分状态,这些状态意思大致如下:

  • FIN-WAIT-1表示此端想主动断开连接,向对方发出FIN报文后进入FIN-WAIT-1终止等待1状态
  • CLOSE-WAIT表示在等待关闭状态,当对方发送FIN报文自己回应ACK时会进入关闭等待状态,在这个状态的时候如果自己还有数据要发送可以接着发送,如果没有就会发送FIN报文给对方
  • FIN-WAIT-2在对方发送ACK报文后,先要求断开连接的这端会处于此状态FIN-WAIT-2终止等待2
  • LAST-ACK被动关闭连接的一端在发送FIN报文后会进入LAST-ACK最后确认的状态,当收到对方的ACK报文后进入关闭状态
  • TIME-WAIT在收到对方的FIN报文后,发出己方的ACK报文在等待2MSL的时间后就会进入关闭状态。还有一种情况如果是在FIN-WAIT-1状态下,收到对方同时带有FINACK的报文后可以直接进入TIME-WAIT状态,这里是将第二次握手和第三次握手给合并了,相当于只会出现三次握手

除了图上这些状态之外还有一种特殊的状态:CLOSING,因为TCP是全双工的,所以可能会出现一端发出FIN报文的同时另一端也发出FIN报文,也就是两端都没有数据要发送了,都想断开连接,这样就会出现CLOSING状态,但是这种状态很罕见。

这些状态通过netstat命令可以看到

image.png

同样为什么断开连接需要四次挥手?(这里以客户端先断开连接来分析,实际是服务端和客户端都有可能先提出断开连接)

  • 第一次挥手,客户端发出FIN报文,表示客户端已经没有数据要给服务端发送了,但是此时客户端依然可以接收服务端的数据
  • 第二次挥手,服务端发出ACK报文,表示服务端已经知道了客户端没有数据发送给服务端了,但是服务端可能还有数据发给客户端
  • 第三次握手,服务端发出FIN报文,表示服务端也没有数据发送给客户端了
  • 第四次挥手,客户端发出ACK报文,表示客户端也知道服务端没有数据发送了,随后便可以正式断开连接

可以看到这是两次双向关闭,每次都是一端告诉另一端没有数据要发送了,另一端发送报文表示自己知道了,所以会有四次挥手。

另外可以注意到主动关闭这方有个TIME-WAIT状态,那这个状态存在的意义是什么?

客户端发送ACK后,需要有个TIME-WAIT阶段,等待一段时间后,再真正关闭连接,这个时间大概就是2倍的MSL(Maximum Segment Lifetime,最大分段生存期),MSL是TCP报文在Internet上的最长生存时间,一般这个值大概是2分钟,可以防止本次连接中产生的数据包误传到下一次连接中(因为本次连接中的数据包都会在2MSL时间内消失了)

如果客户端发送ACK后马上释放了,然后又因为网络原因,服务端没有收到客户端的ACK,这时服务端就会重发FIN,那么可能出现:

  • 客户端没有任何响应,服务器那边会干等,甚至多次重发FIN,浪费资源
  • 客户端有个新的应用程序刚好分配了同一个端口号,新的应用程序收到FIN后马上开始执行断开连接的操作,本来它可能是想跟服务器建立连接的,相当于导致了误操作