TCP与QUIC比较

一个数据包在网络中传输需要经过很多的设备,从发送主机到接收主机中间有各种的路由器光纤,中间任何一个环节出错都可能导致丢包发生,IP协议是一个尽力而为的协议,不保证可靠传输,所以需要依赖上层协议提供可靠传输。上层协议有两种,一个是TCP,一个是UDP,TCP是可靠的,UDP是不可靠的。TCP在1974年就被提出来,是一个相当古老的协议,经过30多年的发展,形成了一个相当复杂的协议,而且TCP协议栈在内核中实现,要对TCP做大的改动几乎不现实,所以很多人在UDP上做文章,在应用层实现TCP的一些特性,寻求更高效的传输。 QUIC是google在UDP上搞的可靠传输协议,能够在应用层保障可靠传输。在了解QUIC之前,先了解一下保障可靠传输的方法是什么。

超时重传

TCP中使用序号标记数据包,发送端发送数据后,接收端需要返回一个确认信息(ack包)表明自己已经接收到某段数据。当超过一定时间没有收到确认,就认为数据丢失需要重传。TCP中使用的是累积确认机制,确认收到的连续数据包中最大的那个。 发送端发送数据后起一个定时器,当超过一定时间没收到ACK包,就认为发送的数据丢失了,重传丢失的数据包。

快速重传

上面的超时重传有个问题,比如接收方收到1,3,4的包后,已经知道2丢了或者慢了,需要发送方重传2,接收方可以不做任何操作,在那边傻等发送方重传2,也可以通过一种方式去告知发送方2丢了,这种主动告知发送端的方式称为快速重传,TCP中通过连续发送3个相同的ack包告知发送端数据包丢了。在基于UDP实现的可靠传输中协议中常使用NACK包来实现快速重传。

选择重传

TCP连接中,发送端在接收到三个ack后有个困惑,它只需要发送2号包还是要把2号之后的包也重传一遍,为了让发送端清楚地知道需要重传哪些包,接收端在快速重传的基础上加入了额外的信息表示丢包的范围,这样发送方就能精确重传那些丢失的包。在TCP中这种选择重传的方式叫做SACK。在可靠UDP中这些丢包信息会被携带在NACK包中。

前向纠错(FEC)

FEC是通过发送冗余数据包,在一定丢包的情况下利用算法恢复数据包的方法,所以FEC能够消耗更多的带宽换取更好的实时性。常用的FEC算法是异或算法,异或算法的冗余包是通过包组异或计算出来的,只能冗余一个数据包,包组中任意一个数据包丢失都能从其他数据包计算出来。举个栗子:

三个数据包ABC
A = 0101
B = 1100
C = 1001
冗余的数据包为D
D = A^B^C = 0010
假如只有B丢失,要计算出B
B = A^C^D = 0101^1001^0010 = 1100

不过FEC对带宽消耗太大,在少量冗余的情况下带来的效果又不是很明显,于是QUIC在2016年去掉了FEC功能。

QUIC vs TCP

在UDP上保证可靠传输的QUIC协议,很多实现基本上是照搬了TCP协议,另外做了很多优化。

避免队头阻塞

TCP协议必须保证每个包按照顺序交付给应用层,这就造成了前一个数据包如果丢失或延迟,会造成后一个数据包阻塞在TCP协议栈中无法交付给应用层,即使这两个数据包中的数据没有什么相关性。在HTTP2.0中把数据划分成消息和帧,这样在一个TCP连接中可能并行存在多个HTTP请求和响应,这些消息在TCP层面没法区分到底属于那个请求和响应,于是TCP的丢包会造成后续无关数据的阻塞。QUIC能够在传输层中区分出不同的数据流,前一个丢包不会阻塞住后面无关的数据包,这样就避免了刚才的队头阻塞问题。

没有重传歧议

QUIC使用单调递增的sequence number,在TCP中重传的数据包和原数据包的sequence number一致,TCP在重传的时候,接收端收到重传包后无法判断这个包是重传的还是延迟过来的包,这种歧议造成接收端很难计算出RTT。那么QUIC是如何标记一个数据包的ID呢,其实是使用另一个字段 —— offset,重传的包和原数据包他们的offset是一致的。

更多ACK范围

TCP的sack由于TCP头大小的限制(60个字节),最大只能支持3个ack范围,而QUIC最大能支持256个ack范围。

自带加密

QUIC相当于TCP+TLS,自带TLS加密,由于没有经过TCP的三次握手和TLS的几次握手,大大缩短了响应耗时。

连接迁移

TCP通过四元组(源端口、源IP、目的端口、目的IP)标记一条连接,当网络发生变化是会导致连接断开,比如从Wifi切换到4G时,需要断连重连。QUIC通过一个64为的随机ID数表示一个连接,当网络环境变化时,不会断开连接。

没有Reneging

TCP允许让接收端丢弃已经接收到的包,主要是为了减轻服务端的压力,但是这个做法对数据传输干扰比较大,TCP也不建议使用。QUIC中明确禁止Reneging。

ACK delay 时间

用TCP的时间戳选项计算RTT会包含接收端接收到数据与发送出响应的这段时间(ack delay),这段时间只是接收端内部处理耗时,并不是链路上的耗时。QUIC会将这部分时间排除掉,得到更精确的链路耗时。

参考

  1. https://mp.weixin.qq.com/s/vpz6bp3PT1IDzZervyOfqw
  2. https://coolshell.cn/articles/11564.html
  3. https://quicwg.org/base-drafts/draft-ietf-quic-recovery.html