浅析TCP可靠连接的构建

版权声明:原创文章,转载请声名。 https://blog.csdn.net/qq_40707451/article/details/86635518

TCP是处于传输层的协议,它和UDP在网络层使用的都是相同的网络层协议(IP),但是它向网络层提供的确是完全不同的服务。

TCP是有连接,可靠的,面向字节流的服务。

有连接

TCP的连接是通过三次握手建立的,这个连接并不具有物理的实体,而是一种虚拟的连接,只是在连接的两端主机内存上各保留了一份状态信息。

SYN:同步序列编号(Synchronize Sequence Numbers

ACK: (Acknowledgement)即“是”确认字符

  1.  客户端向服务器发送一个同步数据包请求建立连接,该数据包中,初始序列号(ISN)是客户端随机产生的一个值,确认号是0;    
  2.  服务器收到这个同步请求数据包后,会对客户端进行一个同步确认。这个数据包中,序列号(ISN)是服务器随机产生的一个值,确认号是客户端的初始序列号+1;
  3.  客户端收到这个同步确认数据包后,再对服务器进行一个确认。该数据包中,序列号是上一个同步请求数据包中的确认号值,确认号是服务器的初始序列号+1。
     

解释一下三次握手,为何三次?

三次握手就是检测两个主机的接收和发送功能,第一次1号主机向2号主机发送数据,第二次2号主机回复数据,如果这两个次过程正常进行,已经说明了两个主机的接收和发送功能都没问题了,就没有必要进行第三次,但这是我们从上帝视角去看的。现在的情况实际是,1号主机知道自己和2号主机的发送、接收功能都没问题,但2号主机只知道自己的接收和1号主机的发送功能没问题,2号主机不清楚自己的发送功能和1号主机的接收功能有没有问题,所以就有了第三次的发送,也就是1号主机回复2号主机。

此时需要建立连接的双方主机都已经确定了对方是否能正常收发信息了。

四次挥手

TCP是全双工通信的,断开连接需要在两个方向上同时进行。


可靠

  • 确认应答机制

TCP连接的双方互相通信过程中,发送方发送的数据包被接收方收到到时,接收方要向发送方发送一个确认信号,确认已收到发送方的数据包和数据包的序号。这样做可以让发送方确定接收方是否收到正确的数据,那么接收方知道这个信息又有什么用途呢?因此就有了下面的机制。

  • 超时重传与快速重传机制

确认应答机制可以让发送方知晓接收方是否收到数据,但存在这样一种情况,如果接收方发送的确认信号在发送过程中丢失,那么这种情况如何处理?超时重传就是说如果在规定时间内没有收到这个确认信号,那么发送方同意认为接收方没有收到自己的数据报,就将会进行重新发送。

在多个数据包传递时,由于网络原因,引起数据包的失序,我们不能确认少量重复的确认序号是因为数据包的丢失还是数据包的失序而导致的重新排序。重新排序的报文段被处理并产生一个新的ACK之前,只可能产生1~2个重复的ACK。所以当连续收到三个或三个以上重复的ACK时,我们认为数据包已经丢失。这是我们不需要等待重传定时器超时,而是直接重传当前确认序列的数据包。

重传定时器在发送方发送数据时开始计时,在收到确认信号后刷新计时。

TCP的可靠传输步骤如下:

  • 发送方向接收方发送数据包,为了保证数据包的可靠传递,将当前发送的数据包缓存;
  • 为已发送的数据包启动重传定时器;
  • 在定时器超时之前收到数据包的确认信息(可能是当前包的也可能是后续包的),从而释放对应数据包的缓存并刷新重传定时器;
  • 定时器超时或者收到连续三次重复的ACK,则重传数据包,直到收到应答或者达到重传的最大次数;
  • 接收方将收到的数据进行CRC(循环冗余校验码:Cyclic Redundancy Check)校验,将正确的数据包交给应用层程序,给发送方发送一个ACK来确认数据包已经收到,在发送ACK时,如果接收方有数据要发送给发送方,ACK应达包也可放在数据包中捎带过去。(捎带应答机制)
  • 滑动窗口机制

有了上面的机制,TCP已经能够建立一个可靠的数据通信方式了。但也正因为上面的机制使得TCP的效率较低,为了提高效率,我们引入了滑动窗口机制,我们一次不只是发一条数据包,然后等待确认应答包的到来。而是一次发多条数据,数据包的个数被称为窗口的大小。

在滑动窗口机制下,为了提高数据传输效率和可靠性,我们引入了慢启动和拥塞避免。

拥塞避免算法和慢启动算法需要对每个连接维持两个变量:一个拥塞窗口cwnd和一个慢启动门限ssthresh

cwnd初始值为1个报文段,在达到慢启动门限前以指数形式增长(1,2,4...),当发生拥塞(超时重传或者重复确认),慢启动门限ssthresh变为当前窗口值的一半,cwnd和接收方通告窗口大小的最小值,但最少为两个报文段。但如果是超时引起的重传,则cwnd重新被设置为1个报文段,也就是慢启动。

  • 保活定时器

目的在于看看对方有没有发生异常,如果有异常就及时关闭连接。当传输双方不主动关闭连接时,就算双方没有交换任何数据,连接也是一直有效的。如果这个时候对端、中间网络出现异常而导致连接不可用,本端如何得知这一信息呢?答案就是保活定时器。它每隔一段时间会超时,超时后会检查连接是否空闲太久了,如果空闲的时间超过了设置时间,就会发送探测报文。然后通过对端是否响应、响应是否符合预期,来判断对端是否正常,如果不正常,就主动关闭连接。

当服务器发送探测报文时,客户端可能处于4种不同的情况:仍然正常运行、已经崩溃、已经崩溃并重启了、由于中间链路问题不可达。在不同的情况下,服务器会得到不一样的反馈。

面向字节流

我们知道UDP是面向数据报的服务,那么字节流与数据报的区别在哪呢?

面向数据报:面向数据报的方式就是应用层交给UDP多长的报文,UDP就会发送多长,即一次发送一个报文。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。但是,当报文太长,在UDP层虽然不会对数据进行分片,但在IP层将会进行报文分片,IP分片会使得丢包概率增大,从而降低传输效率。在一个以太网上,最大MTU(Maximum Transmission Unit)为1500,UDP首部为8个字节,IP首部(不加选项)为20个字节,所以数据段最大为1472个字节。

面向字节流:在IP层对数据的划分叫做分片,在TCP层对数据的划分叫做分段。TCP将应用层交下来的数据看成是一串字节流,TCP有一个缓冲,当应用程序交下来的数据块太长,TCP将对数据段进行拆分,拆分大小根据MTU大小来计算,它会向链路层询问MTU的大小,从而确定MSS(Maximum Segment Size)大小,在一个以太网上,一般情况下,MSS=MTU-20(TCP首部)-20(IP首部)。

如果两台主机之间通信要经过多个网络,那么每个网络链路层MTU是不同的,为了不使数据分包并且不使得其在传输路径上某个路由器缓存数据,我们需要确定一个最小的MTU值来确保传输数据的效率。

在历经多个网络的传输路径上每个路由器MTU的最小值被称为路径MTU。

TCP的路径MTU发现按如下方式进行:在连接建立时,TCP使用输出接口或对端声明的MSS中的最小MTU作为起始的报文段大小。路径MTU发现不允许TCP超过对端声明的MSS。如果对端没有指定一个MSS,则默认为536。

一旦选定了起始的报文段大小,在该连接上的所有被TCP发送的IP数据报都将被设置DF比特。如果某个中间路由器需要对一个设置了DF标志的数据报进行分片,它就丢弃这个数据报,并产生一个ICMP的“不能分片”差错。

如果收到这个ICMP差错,TCP就减少段大小并进行重传。如果路由器产生的是一个较新的该类ICMP差错,则报文段大小被设置为下一跳的MTU减去IP和TCP的首部长度。如果是一个较旧的该类ICMP差错,则必须尝试下一个可能的最小MTU。当由这个ICMP差错引起的重传发生时,拥塞窗口不需要变化,但要启动慢启动。

由于路由可以动态变化,因此在最后一次减少路径MTU的一段时间以后,可以尝试使用一个较大的值(直到等于对端声明的MSS或输出接口MTU的最小值)。

在对非本地目的地,默认的MSS通常为536字节,路径MTU发现可以避免在通过MTU小于576(这非常罕见)的中间链路时进行分片。对于本地目的主机,也可以避免在中间链路(如以太网)的MTU小于端点网络(如令牌环网)的情况下进行分片。但为了能使路径MTU更加有用和充分利用MTU大于576的广域网,一个实现必须停止使用为非本地目的制定的536的MTU默认值。MSS的一个较好的选择是输出接口的MTU(当然要减去IP和TCP的首部大小)

总结就是将源地址将数据报的DF(Don't Fragment,不要分片)位置位,再逐渐增大发送的数据报的大小,路径上任何需要将分组进行分片的路由器都会将这种数据报丢弃并返回一个“数据报过大”的ICMP响应到源地址.这样,源主机就获得了能通过这条路径的MTU了

结论:TCP通过一系列的机制来保证可靠和高效,但本质上TCP也是一种不可靠的通讯方式,我们只是在不可靠的通讯方式上建立了可靠的通讯方法。因此,通过这些机制的加持,我们可以让不可靠的连接变得可靠。

猜你喜欢

转载自blog.csdn.net/qq_40707451/article/details/86635518