上一篇文章中我们对TCP有了一个简单的了解,同时我们也知道了TCP是如何建立和关闭连接的,在这篇文章中我们将学习到TCP是如何进行数据传输的。

MSS

Maximum Transmission Unit (缩写MTU) 是指最大传输单元,对于以太网来说这个值是1500字节,也就是说以太网一次传输的报文不得大于1500,超过这个大小设备可能就会丢弃报文。IP可以对数据进行拆分,但是为了减小路由器的压力,TCP中会设置合理的数据报文大小以避免最终的报文大小超过MTU,这个大小叫做Maximum Segment Size (缩写MSS),如果剔掉IP报文和TCP报文的头部,则一个TCP报文的MSS为 1500 - 20 - 20 = 1460。

使用ACK进行确认

在连接创建和关闭的时候我们已经知道了,TCP为了保证数据不丢失,会在收到报文后发送ACK来告知发送方自己已经收到了该数据。接收方根据发送方发送的seq来发送确认ACK,ACK代表了接收方接收到的数据内容,有时接收方会把多个seq的信息进行合并,也就是说有时候多次传输只会对应一次的ACK。

1
2
3
4
5
6
7
8
9
10
11
15:18:45.635203 IP 172.19.3.44.59480 > lin-21-3-92.5800: Flags [S], seq 586172531, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 1351103125 ecr 0,sackOK,eol], length 0
15:18:45.635253 IP lin-21-3-92.5800 > 172.19.3.44.59480: Flags [S.], seq 2988380519, ack 586172532, win 14480, options [mss 1460,sackOK,TS val 433480428 ecr 1351103125,nop,wscale 7], length 0
15:18:45.640712 IP 172.19.3.44.59480 > lin-21-3-92.5800: Flags [.], ack 2988380520, win 2069, options [nop,nop,TS val 1351103130 ecr 433480428], length 0
15:18:45.640806 IP 172.19.3.44.59480 > lin-21-3-92.5800: Flags [P.], seq 586172532:586172612, ack 2988380520, win 2069, options [nop,nop,TS val 1351103130 ecr 433480428], length 80
15:18:45.640818 IP lin-21-3-92.5800 > 172.19.3.44.59480: Flags [.], ack 586172612, win 114, options [nop,nop,TS val 433480433 ecr 1351103130], length 0
15:18:45.649141 IP lin-21-3-92.5800 > 172.19.3.44.59480: Flags [P.], seq 2988380520:2988380537, ack 586172612, win 114, options [nop,nop,TS val 433480442 ecr 1351103130], length 17
15:18:45.649584 IP lin-21-3-92.5800 > 172.19.3.44.59480: Flags [FP.], seq 2988380537:2988381641, ack 586172612, win 114, options [nop,nop,TS val 433480442 ecr 1351103130], length 1104
15:18:45.653609 IP 172.19.3.44.59480 > lin-21-3-92.5800: Flags [.], ack 2988380537, win 2068, options [nop,nop,TS val 1351103143 ecr 433480442], length 0
15:18:45.653781 IP 172.19.3.44.59480 > lin-21-3-92.5800: Flags [.], ack 2988381642, win 2051, options [nop,nop,TS val 1351103143 ecr 433480442], length 0
15:18:45.654360 IP 172.19.3.44.59480 > lin-21-3-92.5800: Flags [F.], seq 586172612, ack 2988381642, win 2051, options [nop,nop,TS val 1351103144 ecr 433480442], length 0
15:18:45.654376 IP lin-21-3-92.5800 > 172.19.3.44.59480: Flags [.], ack 586172613, win 114, options [nop,nop,TS val 433480447 ecr 1351103144], length 0

我们把上一篇文章的报文再拿过来观察一下,第4行为客户端向服务端发送HTTP请求报文的TCP segment,该报文的seq为586172532:586172612,代表了80个字节的数据。服务端在收到数据后返回ACK的seq为586172612,表示服务端已经收到了客户端seq至586172612的数据。

第6行第7行服务端连续发送了两个数据给客户端,第7行还附带了FIN标志,这在上一篇文章中我们已经说过了。第8行第9行分别是针对这两个数据报文的ACK。

滑动窗口

滑动窗口是接收方与发送方用来协商数据发送速度的一种方式,它也是实现TCP流量控制的手段,关于滑动窗口可以看我之前的文章TCP协议的流量控制与Linux内核的Scoket缓冲区,这里就不再赘述了。

超时重传

超时重传是TCP中的最重要的内容之一,这也是TCP对于数据可靠性保证的来源。关于超时重传我在文章一次网络超时的排查中也有所介绍,具体超时情况的分析可以参考之前的文章。

这里我们重点了解一下TCP中的超时时间是如何计算出来的。TCP中引入了Round Trip Time (简写RTT)来记录数据传输的往返时间,它的值为一个指定seq的报文被发送、到接收这个seq所对应的ACK所需要花费的时间。而Retransmission Timeout (简写RTO)则代表了TCP发送报文的超时时间,如果过了RTO时间还没能接收到指定报文的ACK,就触发超时对该报文进行重发。

不难发现RTO是根据RTT动态计算出来的,RTO的计算方式对于TCP的性能有重大影响。RTO过短会导致不应该被重传的数据被重传,增加整个网络中的数据传输压力,最终降低系统吞吐量;RTO过大会降低TCP数据的传输速度,影响传输性能。

在TCP的发展过程中,各种根据RTT计算RTO的公式被不断地发明出来,例如Jacobaon/Karels公式

第一次RTO计算:

SRTT = R
RTTVAR = R/2
RTO = SRTT + max (G, K*RTTVAR)

之后:

RTTVAR = (1 - beta) * RTTVAR + beta * |SRTT - R'|
SRTT = (1 - alpha) * SRTT + alpha * R'
RTO = SRTT + max (G, K*RTTVAR)

其中

  • SRTT(smoothed round-trip time):平滑RTT时间
  • RTTVAR(round-trip time variation):RTT变量,其实就是rtt平均偏差
  • G表示系统时钟的粒度,一般很小,us级别。
  • beta = 1/4, alpha = 1/8,R为RTT的值,K的值为4

需要注意的是上面我们介绍的是初次重传时的RTO,如果重传后还没能收到另一端的响应,下一次重传RTO会指数增加。例如第一次重传RTO是1,之后分别2,4,8,16,…。这种行为叫做指数回避策略,所以对于tcp来说,当丢包率高时,有可能一个包要很久才能送达。

拥塞控制

上文中提到的流量控制(flow control)是一种针对接收端的极限而实现的速度限制机制,拥塞控制(congestion control)则是针对网络链路中的路由器的极限而提出的速度限制机制。

链路中的丢包一般都是因为路由器的负载较高而产生的,如果路由器因为负载较高丢掉了一些报文,此时发送端会对数据进行重传,这进一步加重了路由器的压力,最终产生一个恶性循环。拥塞控制用于在面临网络拥塞时遏制发送方,拥塞控制对于提升整个互联网的吞吐量有着巨大的影响。

和流量控制类似,拥塞控制使用拥塞窗口(congestion window,简写cwnd)来限制发送速度,拥塞控制的核心在如何确定一个合理的cwnd。TCP的拥塞算法分为三步:慢启动,拥塞避免,快速恢复

慢启动

TCP在刚刚发送数据时会把MSS设置为一个较小的值,每当一次数据的成功ACK,cwnd的值就会增加一个MSS(切记MSS代表了一次发送报文的大小,而cwnd代表了报文的发送速率)。

慢启动的过程如下,它分为三种情况

  1. 数据发送发生超时,TCP将cwnd重置为1并且将cwnd的最大阈值ssthresh设置为发生超时时的cwnd的1/2,之后重新开始慢启动的过程
  2. 如果cwnd成功增加到了ssthresh的大小并且没有发生超时,则进入拥塞避免模式
  3. 如果检测到3个冗余ACK,这时TCP执行一种快速重传并进入快速恢复状态

拥塞避免

每次RTT只会给cwnd增加 MSS x (MSS / cwnd),整个系统进入一个临时的稳定状态。一旦出现超时,则进入慢启动模式

快速恢复

  1. 对收到的每个冗余ACK,cwnd值增加一个MSS
  2. 如果丢失报文的ACK又被收到,降低cwnd进入拥塞避免状态
  3. 发生超时,进入慢启动

BBR拥塞控制算法

BBR是Google在2016年提出的一种拥塞控制算法,在Linux kernel4.9及以后的版本中已添加该算法。BBR比经典的TCP拥塞算法更加激进,关于BBR俺也不是特别了解,等以后有机会一定学习一下。

参考

TCP/IP详解 卷1:协议:第17章 ~ 第24章
TCP RTO计算方法以及go实现验证
TCP流量控制与拥塞控制