写在前面:
在整个网络体系中,运输层总共有两大协议:TCP和UDP。博主个人走的方向是Linux/C++开发方向,平时接触最多的也就是TCP协议了。但是随着对网络协议的认识逐渐深入,渐渐意识到也许UDP才是未来的主流运输层协议。至于为什么这么说,可以关注博主的整个计算机网络体系文章。
UDP协议
UDP是User Datagram Protocol(用户数据报协议)的简称,根据在【RFC 768】中对UDP的描述来看。UDP只是做了运输协议能做的最少工作,也就是多路复用/多路分解以及一些简单的差错检验。它除了这些之外几乎就没有对网络层的IP协议增加别的东西。UDP协议从应用进程得到数据,附加上多路复用/多路分解服务所需的源端口号和目的端口号字段,以及其他的两个字段(稍后会详细介绍)。
什么是多路分解/多路复用:
当一个网络报文到达主机的时候,其必须知道这个报文要交付给哪个进程。而在运输层协议中都会在报文的头部增加端口号信息。根据端口号,内核就知道这个报文应该交给哪个进程。这就是多路分解;多路复用就是每个进程在发送报文的时候,添加上要监听的端口号。
之后这个报文段就会交付给网络层。网络层将该运输层报文封装到IP数据报中,然后尽力的经过网络交付给目的主机。
UDP的报文结构
下图是一个UDP报文段的结构,在【RFC 768】中被定义。UDP首部只有4个字段,每个字段由两个字节组成。
- 源/目的端口:用于标识多路分解和多路复用
- 检验和:接受主机使用检验和来检查报文段中是否有差错
- 报文长度:标识整个UDP报文的长度,也就是UDP报头的8字节+应用层数据
UDP的检验和
刚刚说了,UDP只用提供最少的运输层服务。其实按照博主的理解,只用加上源端口和目的端口完成多路复用/多路分解功能其实就可以了。而且在链路层其实就已经提供了差错检验的功能,为什么要加上报文长度和检验和字段呢?
UDP的检验和提供了差错检验功能,即检验和用于确定当UDP报文段从源到达目的主机的时候,其中的 bit 是否被改变了。发送端的UDP对报文段中的所有16比特字的和进行反码运算,求和时遇到任何溢出都会被回卷。得到的结构放在UDP报文段的检验和字段 。下面【RFC 1071】中实现的一个例子:
//假定我们有下面三个16 bit字
①:0110 0110 0110 0000
②:0101 0101 0101 0101
③:1000 1111 0000 1100
对于前两个bit字①和②的比特字之和是:
④:1011 1011 1011 0101
得到的结果④再和前面的③相加得到:
⑤:1 0100 1010 1100 0010
对于最后一个得到的结果⑤来说,其最终结果其实是溢出了一个bit,这就要被回卷(也就是舍弃这一个)。所以最终的结果应该如下:
⑥:0100 1010 1100 0010
对于最终结果⑥来说,进行反码运算(01互换),得到如下结果:
⑦:1011 0101 0011 1101
⑦就是我们要的检验和,其最终被填写在UDP报头的检验和字段。
在接受方,全部的4个16 bit字一起相加,如果分组中无差错,则显然在接收方这个和将是1 1111 1111 1111 1111
。如果有一个 bit 是0,那么我们 就知道分组中出现了差错。
至于为什么还要在运输层再次提供差错校验,是因为不能保证源主机和目的主机之间的所有链路都能够提供差错检测,也许其中有一条链路没有使用差错检验协议呢。此外,即使报文段经过链路正确的被传输,其在某个路由器的内存中时,也可能引入bit 差错。
UDP协议的特点
一般我们都说UDP是无连接、不可靠的数据报协议,为什么这么说呢?
- UDP在发送报文段之前,发送方和接收方的运输层实体没有进行握手(信息交换),此为无连接
- UDP并没有提供如TCP那样的确认应答和超时重传机制,报文是否到达都是未知数。不过,可以通过应用层来实现UDP的可靠性,此为不可靠
- 对于UDP来说,其不缓存从应用层来的数据,每次都是直接发送出去了。此为数据报
UDP协议的优势
相比于TCP的可靠字节流服务来说,UDP不保证可靠性,容易发生报文丢失的事件。那么对于开发人员来说,TCP是不是总是优于UDP的呢?
其实不然,UDP其创建出来自有其优势:
- 应用层能够更好的控制要发送的数据和发送时间:采用UDP的时候,只要应用进程将数据传输给UDP,UDP就会将此数据打包成UDP报文并立即传递给网络层。但是对于TCP来说,根据其拥塞控制手段,其会遏制上层的应用层数据的发送。
- 无需链接的建立:之前其实也介绍过,对于使用TCP协议的HTTP协议,特别是采用非持久连接。其每发送一个HTTP请求报文,就需要两个RTT时间,其中一个半的RTT时间都被用来TCP三次握手连接的建立。而UDR无需任何数据传输的准备,所以不会引入建立连接的时延。
- 无连接状态:对于TCP来说,在三次握手之后,其在两个端系统都需要维护连接状态,包括但不限于:接受/发送缓冲区、拥塞窗口、最大可发送报文窗口等。其会占用大量的系统资源,使得基于TCP的服务器其所能服务的客户机数量大大减少了,这一方面,UDP就能支持更多的活动客户机
- 分组首部开销较小:每个TCP报文都会有20字节的首部开销,而UDP只有8字节。对于telnet这种每次只传输一到两个字节的应用来说,其使用TCP的有效网络带宽利用率只有5%~10%之间
下图所示是因特网应用及其所使用的运输层协议:
UDP的使用场景
UDP在博主看来,其具有少年的特性:
- 沟通简单:不需要繁杂的握手协议,永远相信网络时通畅的,永远热泪盈眶
- 轻信他人:任何主机都可以给它发数据,它也可以给任何主机发数据,可以多播
- 不会权变:不管实际网络是否堵塞,其都照常发数据,不会降低速率
而这种少年心性,使得我们可以在以下的场景中使用它:
- 需要资源少、网络情况比较好的内网,或者对于丢包不敏感的应用
- 不需要一对一沟通、建立连接、适于广播
- 需要处理速度快、时延低、可以容忍少数丢包,但是要求即便网络堵塞,也毫不退缩,一往无前
为什么说UDP协议是未来新星
另外,随着时代的发展,UDP有了更大的舞台,也就是Google在2016推出的基于UDP的QUIC协议!
细数TCP的四宗罪
- TCP队头阻塞,停等问题:这是TCP的可靠性机制的特性。HTTP2是在一个TCP连接上并行发送多个资源,TCP队头阻塞将会导致所有资源的传输需要停等,对网络质量影响较大。
- 握手延迟:这个刚刚介绍过了,不再赘述
- 网络中间设备僵化:网络中间设备在传输TCP协议时设置了很多潜规则,例如部分防火墙只允许通过80和443端口;部分NAT网关在转换网络地址时会重写传输层头部,可能导致双方无法使用新的传输格式;部分中间代理有时候出于安全需要,删除一些它不认识的选项字段。因此升级基于TCP的网络协议时,就必须要考虑和兼容这些影响
- 协议僵化:TCP是在操作系统内核和中间设备固件中实现的。要对TCP进行大更改,就必须要通信双方升级操作系统,中间设备更新固件。这基本难以大规模实现
以上罪状使得我们对TCP再次进行升级变得困难重重,所以需要在UDP上进行改进。
QUIC
QUIC:
QUIC(Quick UDP Internet Connection)是谷歌制定的一种基于UDP的低时延的互联网传输层协议。在2016年11月国际互联网工程任务组(IETF)召开了第一次QUIC工作组会议,受到了业界的广泛关注。这也意味着QUIC开始了它的标准化过程,成为新一代传输层协议。
但是,其实这么说也是不对的。在博主看来,QUIC是应用层协议,是UDP的应用层实现。
简单来说,QUIC协议就是基于UDP重新实现了一遍HTTP2的特性。
用一个等式来描述就是 QUIC = UDP + TLS + HTTP2
QUIC与HTTP2的网络层次对比如下图所示:
QUIC特性
-
快速连接:对于QUIC来说,其建立连接只需要一次RTT即可。而TCP三次握手最快也要一次,TSL要一次,可以省下一半时间。
-
连接迁移:TCP下一个连接是以四元组标识的,即
<源IP,目的IP,源端口,目的端口>
。而QUIC连接是以客户端产生的一个64位随机数作为连接标识。当网络、端口发生改变或中断时,只要连接标识不改变,连接就不会中断。 -
改进拥塞控制:
- QUIC在应用层即可实现不同的拥塞控制算法,不需要改操作系统和内核。
- 单个程序的不同连接也能支持配置不同的拥塞控制。这样我们就可以给不同的用户提供更好的拥塞控制。
- 应用程序变更拥塞控制,甚至不需要停机和升级。
- QUIC还有带宽预测,RTT监控,发送速率调整等高级算法支持。
-
双级别流控:TCP通过滑动窗口机制来保证可靠性以及进行流量控制。QUIC更新了其滑动窗口机制,并在Connection和Stream两个级别分别进行流控。
用公式表示就是:
connection可用窗口 = stream1可用窗口 + stream2可用窗口 + streamN可用窗口 -
实现与升级更灵活:TCP协议栈是写在操作系统内核以及中间设备固件上的,对其更新升级,耗费的时间是以年为周期。基于UDP协议栈的QUIC协议在应用层实现。应用软件的更新较为轻量,因此协议新特性的升级迭代周期是以月或周来计算。
参考文献
[1] 计算机网络自顶向下方法.第三章
[2] 趣谈网络协议.刘超.极客时间
[3] QUIC.百度百科
[4] QUIC简介.哲学之喵.腾讯云