TCP 协议学习笔记 (二)
在 上一篇文章 中我们对 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 | 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 |
我们把上一篇文章的报文再拿过来观察一下,第 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 代表了报文的发送速率)。
慢启动的过程如下,它分为三种情况
- 数据发送发生超时,TCP 将 cwnd 重置为 1 并且将 cwnd 的最大阈值 ssthresh 设置为发生超时时的 cwnd 的 1/2,之后重新开始慢启动的过程
- 如果 cwnd 成功增加到了 ssthresh 的大小并且没有发生超时,则进入拥塞避免模式
- 如果检测到 3 个冗余 ACK,这时 TCP 执行一种快速重传并进入快速恢复状态
拥塞避免
每次 RTT 只会给 cwnd 增加 MSS x (MSS / cwnd),整个系统进入一个临时的稳定状态。一旦出现超时,则进入慢启动模式
快速恢复
- 对收到的每个冗余 ACK,cwnd 值增加一个 MSS
- 如果丢失报文的 ACK 又被收到,降低 cwnd 进入拥塞避免状态
- 发生超时,进入慢启动
BBR 拥塞控制算法
BBR 是 Google 在 2016 年提出的一种拥塞控制算法,在 Linux kernel4.9 及以后的版本中已添加该算法。BBR 比经典的 TCP 拥塞算法更加激进,关于 BBR 俺也不是特别了解,等以后有机会一定学习一下。
参考
TCP/IP 详解 卷 1:协议:第 17 章 ~ 第 24 章
TCP RTO 计算方法以及 go 实现验证
TCP 流量控制与拥塞控制