前言
TCP协议是传输层的重要协议,TCP协议保证将数据包完整、可靠地送达对端,是网络中最重要的协议之一,本文我们结合TCP的首部格式来了解TCP。
TCP的首部格式
TCP首部包括20bytes的固定字段和64bytes的可选字段(如下图),16位的源端口号和目的端口号是传输层的基本要求,下面我们通过这些字段来理解TCP的机制。
应答
TCP为每个数据包添加自增的32位序列号,在接收到数据包时,TCP也需要根据包的序列号向对端发送应答,使用client-server模型,可以通过下图来表达这个过程,其中序列号使用seq来表示,应答序号使用ack来表示。
当然,在实际的网络通信过程中,有很多包在收发,ack应答通常和数据包一起发送,提高发送效率,因此这个通信过程应该是这样:
从上图可以发现规律:每个ack的值就是收到的上一个数据包的seq+1,而seq的值就是上一个数据包的ack值,不妨这样理解:数据包ack的值就是希望收到的下一个包的序列号,因此当确定包的seq值时,就看上一个包希望收到的包是哪个序号的包;当确定包的ack值时,就看现在收到了那些连续的序列号,将缺失的最小的序列号作为ack值。
超时重传
由于IP层协议是不保证可靠的,因此TCP接收到的数据包经常会遇到丢包的情况,此时将不会收到对端发送的对应的ack包,当超过一定时间,TCP认为这个包在网络中丢失了,将重传这个包。
超时重传存在的问题是如何计算定时器计时的时间,超时重传时间被称为RTO,RTO是基于RTT来计算的,而RTT指的是数据包在从发送到接收到该数据包对应的确认包所需的时间。
RTO的计算过程如下:
- 通过下面的公式计算SRTT:
- 计算SRTT金额RTT的差值:
- 计算RTO:
目前在linux下,α=0.125, β=0.25, u=1,a=4。
在网络通信过程中,一个数据包传送完后,等待该数据包的应答到达以后再传输下一个包需要RTT时间,这时间内通常会继续发送数据包以提高发送效率,而对端在发送应答时,如果已经接收到了多个连续的数据包,则可以多个数据包一起应答,如下图:
在上图中,如果seq=x+2的包丢失了,那么将发生下面的情况:
无论后续接收到的包序号是多少,只要缺少了seq为x+2的数据,server每收到一个seq>x+2的包,就发送一次ack=x+2,至于后续收到的seq=x+3和x+4的包,可能被直接丢弃,也可能放在缓存中,等待接收seq=x+2的数据包后发送ack=x+5的包。
拥塞控制
慢启动和拥塞避免
TCP在三次握手时,会确定慢启动门限的值ssthresh和拥塞窗口的值cwnd,在刚启动时cwnd的值为1,发送方每收到一个新报文的确认,就将cwnd的值翻倍,当cwnd的值不小于ssthresh时,为了避免网络的压力太大,将进入拥塞避免状态,即每收到一个新报文的确认,就将cwnd的值线性增加,直到遇到网络拥塞。
快速重传和快速恢复
在讨论超时重传时,我们看到当丢失一个包时,对端将一直重传相同的ack,当在一个RTO时间内接收到了三个相同的冗余ack包,TCP将启动快速重传,直接重传这个ack请求的数据包,而不等待超时重传,这样做的有点也就是避免超时重传等待时间的消耗。注意这里是接收到三个冗余ack,也就是一共接收到四个相同的ack。触发超时重传后,TCP连接将进入快速恢复状态。
TCP目前使用的快速恢复算法是reno算法,reno算法使进入快速恢复状态将ssthread变为之前的一半,然后线性继续线性增加。
假设ssthresh被初始化为20,那么cwnd可能的变化情况如下:
勘误:根据《计算机网络-自顶向下方法》的描述,遇到网络拥塞时,reno算法将ssthread变为当前拥塞窗口(cwnd)的一半,而cwnd变为ssthread+3,上图画的有些错误。
滑动窗口
TCP首部中包含16bit的滑动窗口,这指示了当前缓冲区可以接纳多大容量的数据,对端将根据这个窗口大小发送相应大小的数据包。
应用程序调用send()发送tcp报文时,报文并不会立马被发到对端,这是因为tcp是一种流式协议,当程序调用send(),报文会被加入到内核中的缓冲区中,内核会将缓冲区中的数据按照滑动窗口的大小发送到对端,对端接收到数据以后,先存入内核缓冲区中,再提交到应用程序。
随着数据的发送与接收,缓冲区的大小在不停的变化,因此通过窗口大小字段控制两端传输数据量,这种发送次数与接收次数不相等的传输协议称为流式协议。
滑动窗口和拥塞窗口都是控制流量的方法,但滑动窗口考虑的是传输两端的窗口大小,而拥塞控制考虑的是网络传输路径上的拥塞情况,实际TCP通常取滑动窗口和拥塞窗口中较小的作为发送窗口的大小。
校验值和紧急数据
TCP首部字段还包括16位校验值,校验值由发送端根据TCP首部和数据计算,接收端接收后,可以根据校验值计算数据包与发送时是否一致,如果不一致则直接丢弃数据包。
16位紧急指针用于指示紧急数据在字节流中的位置,在数据传输的过程中,由于缓冲区的存在,数据不会马上被发送,接收端也不会马上传递给应用层,当有数据希望被立刻传输时,可以使用TCP发送带外数据。
当发送带外数据时,需要标识出数据在字节流中的位置,这通过紧急指针来计算:
带外数据字节号 = TCP报文序号 + 紧急指针 - 1
标志位
TCP首部字段中还包括6个标志位,标志位的作用是标识对应的字段有效。
- URG标志位为1时,表明紧急指针有效,此时根据紧急指针可以找到带外数据;
- ACK标志位为1时,表明确认号有效,此时32位确认号标识下一个希望收到包的序号;
- PSH标志位标识接收到当前报文后PUSH到应用进程,例如当发送的包大小为4096bytes时,假设被拆分成四个包发送,最后一个包带上PSH标识,则对端接收到最后一个包时上应用层递交缓冲区数据;
- RST标志位为复位标识,当TCP连接出现异常,如一端关闭时,关闭的一端会向对端发送一个RST复位报文;
- SYN标志位标识这个报文是握手报文;
- FIN标志为标识这个报文是挥手报文。
总结
本文介绍了TCP固定首部字段,并通过这些字段介绍了TCP的特性,TCP正是通过这些特性来保证将数据可靠地送达对端,下篇文章将介绍TCP经典的三次握手和四次挥手相关的知识。