TCP三次握手(含常见面试题)详解

TCP 头格式

在这里插入图片描述

  • 序列号:在建立连接时就由计算机随机生成,每发送一次数据,该序列号+1,用来解决网络中包乱序的问题
  • 确认应答号:表示下一次期望收到的报文序列号,表示以前的数据报都已经收到了,用来解决网络中丢包的问题
  • 控制位:
    ACK:该位为1时,确认应答号是有效的,除了建立连接开始的syn包时,其他包该位必须为1
    RST:该位为1时,表示出现异常,强制连接断开
    SYN: 该位为1时,表示希望建立连接,并且初始化序列号
    FIN: 该位为1时,希望正常断开连接,通信双方互相交换FIN,表示可以断开

TCP连接建立

在这里插入图片描述
一开始,双方都处于close状态,先是接收端监听某个端口,进入listen状态

第一次握手:发送syn包时,希望向接收端建立连接,并初始化序列号。此时发送端进入syn_sent状态
第二次握手:接收端接受到了syn包,发送ack+syn包,并初始化序列号,并将发送的序列号+1放入确认应答号中。此时接收端进入syn_rcvd状态
第三次握手:发送ack包,给确认应答号+1,表示收到了接收端的报文,进入establelisten状态
服务端收到了客户端的报文后,也进入了establelisten状态、
注意:这里的第三次握手是可以携带数据的,因为发送端已经确认了服务端有接受和发送的能力。而其他两次并没有确认对方的接受能力

为什么是三次握手呢?而不是两次,四次?

避免旧的重复连接初始化造成混乱

假设我们发送端前面发送了一个序列号为90的syn报文,此时网络中拥塞了,此时发送端可能会连续发起多次请求。但是此时的序列号为90的报文也到了,如果是两次握手的话,接收端接受到就会进入establelisten状态(进入这个状态后就可以发送数据),然而当发送方判断这是一个历史连接,发送RST报文终止连接,但此时服务端已经进入了establelisten状态,并且可能发送了数据。断开的话,白白造成了服务端的资源浪费。为了避免旧的重复连接,必须在建立连接之前就杀掉这个重复连接,这就必须要三次握手才能实现
三次握手示意图:
在这里插入图片描述
如果是两次握手的话:
在这里插入图片描述

同步初始化序列号

序列号的作用:

  • 使对方有序的接受数据包
  • 避免对方接收重复的数据包
  • 可以知道自己发送出去的数据包,哪些是被接收的

三次握手的话,客户端发送一个syn报文,服务端接受到后发送syn+ack报文,表示客户端的初始化序列号已经收到。客户端在回复ack报文,表示服务端的初始化序列号也已经收到。这样才能确保双方序列号都被同步了。如果是两次握手,只能保证一方的序列号被成功接收

防止资源浪费

客户端发送syn报文时,可能被网络拥塞了,也有可能服务端的ack+syn报文拥塞了,那么客户端就会重发syn报文。如果是两次握手的话,服务端每来一个syn报文,就会建立一个连接,这样就会造成冗余连接,造成不必要的资源浪费
在这里插入图片描述
总结:
为什么不使用两次握手:无法防止历史连接的建立,会造成资源浪费,不能同步双方序列号
为什么不使用四次握手:三次握手已经足够,再多一次握手只会造成更多的连接时延

为什么初始化的序列号一定要不一样呢?

防止历史报文被下一个相同的四元组接受

这里就有一个疑问了?tcp挥手不是有2msl的时间吗,上次报文应该早就消失了呀!!但是我们不能保证它每次都是正常断开连接,每次都是四次挥手断开连接

假设我们发送一个报文时,在网络中出现了拥塞,一直没有到服务端,而此时我们的服务端又死机了,等再次开机时,客户端又与它建立了连接,用的还是相同的序列号。这时被拥塞的数据包到了,又正好在服务端的接受窗口内,这时服务端就会接收这个数据包,从而造成数据混乱。
在这里插入图片描述
随机成不一样也有可能会发生这种情况啊???
能不能接受到历史报文,取决的是这个数据包在不在服务端的接收窗口内,如果在的话,就接受,不在就不接收

  • 随机成不一样的数,历史报文正好在接收窗口内的情况概率就会变得很小。
  • 而随机成一样的数,就会有很大概率在接收窗口内

那会不会正好随机成一样的呢??
不可能,这就要看它随机数的生成算法了。

  • RFC793 提到初始化序列号 ISN 随机生成算法:ISN = M + F(localhost, localport, remotehost, remoteport)。
    M是一个计时器,这个计时器每隔4毫秒加1
    F 是一个 Hash 算法,根据源IP、目的IP、源端口、目的端口生成一个随机数值,要保证 hash 算法不能被外部轻易推算得出。

这样的话基本不可能初始化成一样的值。
这样的话,还是不能保证不会接收到历史报文了!!因为还有可能会出现序列号回绕的问题,就是如下:

序列号随机成不一样的值就一定不会接收到历史报文了????

不是的,因为我们的序列号是32位的无符号数,当他到达最大值后,就会从0开始。

假设我们发送的数据包延迟了,然后后面的数据包又回退从0开始了,这时就有可能前面延迟的数据包正好在接收窗口内。

我们发现,就算它的序列号一样,但是总还是有先后顺序的呀!!!故而TCP有时间戳的概念
每次发包时,将内核的时间记录在TCP头部,就算收到了相同的序列号,但它的时间戳不是递增的,小于最近的有效时间戳,那么就会把它丢弃。从而解决了接受历史报文的问题。

扩展:时间戳的另一个作用:计算RTT往返时延
在这里插入图片描述

第一次握手丢失会发生什么???

当我们的tcp第一次握手丢失后,客户端迟迟接收不到ack+syn报文,就会触发超时重传机制,每次超时重传的时间呈指数倍上涨,直到发送5次之后,就不会发送syn包了.由tcp_syn_tries参数指定

第二次握手syn+ack丢失会发生什么

客户端无法收到syn+ack报文,就会以为自己没有发送出去,也就相当于第一次握手时的重传一样。

服务端无法收到ack应答报文,也一样会重发syn+ack包。如果下一次客户端重发的syn报文到了,服务端再次发送ack+syn包,但计时器不会重置,还在持续重传。因为服务端在没有收到第三次握手时,会持续重传到最大次数,默认为5次。由tcp_synack_tries指定

第三次握手ack丢失会发生什么

此时服务端没有收到应答报文,会重传到syn+ack的最大次数,此时服务端的连接终止了

客户端收到syn+ack的报文后,进入establelisten状态。之后发送数据,但服务端已经处于断开连接的状态了,所以客户端重传数据包到最大次数后(每次重传时间指数递增),也断开了连接。客户端重传数据默认为15次。由tcp_retries2参数指定
那如果客户端也不发送数据,什么时候才能断开连接呢??
TCP有一个保活机制,如果一个时间段内,双方都没有发送数据,那么TCP隔一段时间就会发送探测报文,如果连续几个探测报文都没有得到响应,则认为当前连接已经死亡,会断开连接
在这里插入图片描述

TCP快速连接

客户端向服务端发起http get请求时,一个完整的交互需要2.5个RTT。如果第三次握手携带数据的话,也需要2个RTT
在这里插入图片描述
可以开启tcp快速连接,可以减少握手的时延
在这里插入图片描述
虽然第一次握手还是2个RTT,但是后面在cookie过期之前握手都只需要1个RTT就够了

猜你喜欢

转载自blog.csdn.net/small_engineer/article/details/124139370