UDP 协议
引言
网络通信中,UDP与TCP一起构成了四层模型的传输层。UDP/TCP是基于网络层IP协议之上的更上层协议,前面的笔记中我已详细记叙了网络层的几个核心协议,本篇文章正式进入传输层的UDP协议。
什么是 UDP?
要回答这个问题首先得来看另一个问题:为什么需要传输层?前面我们已经讲过,TCP/IP协议,其中一个核心就是位于网络层的IP协议。IP协议固然解决了网络数据包的路由问题,但是缺陷就是IP协议的“粒度”是比较粗的,只负责将网络数据包从源主机运到目标主机。
而在网络环境中,一个主机可能跑着多个需要网络通信的进程/亦或是一台嵌入式设备跑着多个网络线程。如果仅仅依靠IP协议,那么数据包到达后该给哪个进程/线程?我们必须定义一个上层协议来正确描述这个行为,而这个协议就是UDP协议。
UDP全称叫User Datagram Protocol,也即用户数据报文协议。负责主机上更细粒度的进程/线程 ---> 进程/线程之间的通信。当然,传输层的TCP协议也是用来负责进程到进程之间通信的,TCP与UDP二者功能是重合的,但是二者使用场景截然不同,各自有自身的舒适区,此处不展开讨论。
UDP 特点
UDP 是最简单的传输层协议:无连接、不可靠、面向报文、快速。这几个特点将在下文进行讨论。
这里先简单讲讲协议栈是怎么识别进程/线程的,这也是UDP协议工作的原理。我们都知道一个IP地址对应有一个底层物理网卡接口,因此IP作为逻辑地址可用于寻找设备。但是光找到设备没用,还需要知道该与这个目标设备的哪一个进程/线程通信,这里便引入了端口号(Port)这一概念。端口号就类似于该程序在本机上的一个唯一身份证,外部主机能够通过指定端口号来与本机某一个进程进行通信。
例如:192.168.43.12:4800 代表的就是访问本地网段IP为192.168.43.12这台设备的监听端口为4800的这个程序。有了端口号就好办了,UDP将通信双方的端口号塞到报文里面,然后依附在IP数据段中发出去。这下对端收到包之后按照格式能够正确解析出源端口、目的端口,这样两台主机之间的两个唯一的进程的通信就能够正确建立了。
Ps:这里要提醒一下——端口号唯一这种情况针对的是对于同一类型协议而言。传输层不同协议的端口空间是独立的,例如对于4800端口,可以同时使用一个UDP连接和TCP连接监听该端口。
对于服务器要绑定固定端口,而对于客户端尽量使用临时端口,原因在后续文章中再进行详述。
UDP 报文格式
一个协议最重要的自然是报文格式,这里给一下UDP的报文控制头:
| 字段 | 长度 | 含义 |
|---|---|---|
| 源端口 | 16 位 | 发送方进程的端口号。这是可选项,如果不需要对方回复,可以直接填0 |
| 目标端口 | 16 位 | 接收方进程的端口号。 |
| 长度 | 16 位 | UDP 头部 + 数据的总长度。最小 8(只有头),最大 65535 |
| 校验和 | 16 位 | 覆盖 伪首部 + UDP 头 + 数据。IPv4 中可选,IPv6 中强制 |
可以看到,UDP的头部真的十分精简:源、目的端口唯一指定通信双方程序,长度用于表示整个UDP报文多大,校验和用于传输的报文数据可靠性保证。
这里要单独讲一下伪首部的问题。当你在互联网上搜UDP报文结构时,可能会看到以下这种结构(图片源于网络):
前面多了个12字节的伪首部,然而你在实际抓包时会发现根本不存在这一段。这是对的,实际抓包只会看到8字节的标准UDP头部,因为这12个字节压根没被送往物理链路上发送出去。这里讲讲伪首部究竟是怎么运作的:
- 在进行UDP头部的组装时,协议栈内部还会将目标IP、源IP、协议号、UDP长度这几个字段收集起来组成伪头部。并将这个伪头部与标准8字节UDP头部以及剩下的数据段部分进行整体算校验和。之所以要取这些字段,是为了防止IP等字段被篡改导致的潜在风险。
- 整体算出来的这个校验和被填入UDP头部的校验和字段后,这个伪头部就算完成任务了,将被扔掉。后续在物理链路上发送时是不存在这12字节的。
对于接收方而言:
- 收到UDP包后,同样从IP包及UDP头部里面取出上述四个字段,临时拼成伪头部。将该伪头部与接收的UDP标准头部以及数据部分进行校验计算。
- 若校验通过则表示数据完整未被篡改,递交给上层;若校验不通过则直接丢弃掉报文。
至始至终伪首部其实都只工作在传输层那一段时间,不会出现在应用层和其它下层层次,所以这也是为什么使用工具抓不到该部分的原因。
这里最后串讲一下ICMP:我们在之前文章提到过,ICMP差错报文在返回时会在自己的ICMP数据部分携带出错IP报文数据段的至少前8字节数据。
为什么是8字节?因为对于UDP刚好就可以拿到整个标准头部,对于TCP也可以拿到源端口、目的端口、序列号等信息,这些信息对于错误诊断是具备价值的。
UDP 的特点详述
无连接、不可靠
UDP与TCP最大的差别之一就是:UDP无需维护连接状态机,也就是我们常说的无连接。想发数据或接收数据,不需要提前和对方建立连接,直接将对方IP和对应的服务监听端口号丢给协议栈,底层就直接封装发送。不会维护任何连接记录,也不需要断开连接,整个过程算是“雷厉风行”了,干净。而且由于不需要维持连接,对于服务器的开销更小、服务器能够同时处理海量的UDP交互数据。
但是这也是最大的问题:发过去对方真的收到了吗?对方目前的状态真的适合接收数据吗?TCP可是维护有流控机制的,但是UDP并没有。而且UDP不保证顺序,几乎同一时刻按顺序发出去的报文,由于中间路由的选择问题,可能后发的包会先送到。
而且静默失败是一个很大的问题:相比起TCP起码在出错后有超时机制兜底以及对方会发回RST表示通信出现问题。UDP在任何路径上出现的任何问题,都只有倚靠网络层的ICMP来定位。对于成熟的Linux系统内部已经集成处理机制了,能够处理ICMP后变成可读形式报告给上层。而在非Linux的嵌入式设备上只有靠自己维护一套处理机制来捕获错误以及根据实际工况制定对应的重传策略等。
你比如需要与远端某设备进行通信:使用TCP发送SYN请求建立连接时,若SYN包丢了,TCP会尝试重传直至超时;而使用UDP直接对对方端口进行访问。假如UDP在中间被防火墙拦截了且没有发出ICMP,那么本机真就完全不知情,只有靠上层维护一个健壮的超时等机制解决。
快速、面向报文
UDP速度是非常快的,效率也高。UDP与TCP不同:TCP是字节流协议,而UDP是面向报文的协议。在网络环境正常,且能容忍部分丢包的情况下,UDP效率远高于TCP。
先来看速度:前面我们提到过UDP的头部只有8字节,这是优势之一。因为比起TCP的标准20字节头部,是”货真价实“、实打实的减少了底层数据链路传输的比特流数目;同时,UDP无需建立连接,省去了握手和挥手的时间;UDP也没有TCP的超时重传、拥塞控制、流量控制等机制,更不需要维护连接以及等对面的ACK,因此速率比起TCP能拉得很快。
平时玩得网络电子游戏以及音视频本质都是使用UDP进行流式传输的,丢一帧没关系,下一帧很快就送过来补上,所以在宏观视觉效果上不会有太大问题;而假如是TCP网络质量不佳的情况下,频繁触发重传,那得要老命了——频繁重传会触发拥塞控制,TCP 主动降低发送速率,加上延迟确认,延迟越积越大,最终表现为卡顿。
再来看面向报文:和TCP字节流不同,UDP是报文直接发送。你可以将UDP理解为一个独立包裹,不管你向包裹里面放入什么数据,哪怕只有1个字节。只要交到协议栈手上,协议栈将立马封包发送,不会攒数据。看下图:
t1、t2、t3三个时刻发出数据包,不管这三个时刻在时间轴上有多接近,在协议栈层面永远都是三个独立的UDP包,分别携带数据A、B、C发往对端。再来和下面的TCP行为进行比较:
可以看到,TCP内部会有算法机制,有时候会将数据混成一个段发送(上述示例中,不同时刻发送的B和C被打包成一个段BC一起发送了,这也是粘包的源头)。至于TCP这个机制是如何运作的,涉及的内容很多,此处不展开。
所以要记住了,UDP不用担心粘包问题,每次send()调用都是独立封装成包的。
UDP 应用场景
UDP应用场景那可太多了,这里我捡两个常见的来说。
NTP 授时
打开我们的Linux,什么也不用做,直接抓网卡的udp流量。等上一会你会发现抓到以下这几个出入站流量(由于收包是乱序的,因此我此处简单对每次探测按Client-Server进行了排版):
1 | |
NTP网络时钟协议用于校准本机设备的时间,使本机时钟和互联网上的时间服务器保持同步。Linux在启动之后,会在后台自动跑一个NTP校时客户端。这就是为什么我们什么不做,也能够抓到流量的原因。
NTP校时用的就是UDP协议,端口开在123上面。之所以使用UDP,是因为UDP速度快、延迟小,而且NTP本身也能容忍丢包(大不了就是丢本次数据,下次重新来就行了)。而且NTP报文也不大,用TCP建立反而多此一举。
DHCP 发现服务
DHCP也是UDP的典型应用示例。我们作为客户端的网络设备一般情况下IP地址是不会写死在程序中的(除非回退静态IP),而是通过本网段的DHCP服务器来租借IP地址。
问题来了,怎么租借呢?通过TCP吗?那肯定不行,你想:我们向DHCP请求服务目的就是为了拿到一个可用IP,而TCP建立连接一个大前提就是需要IP地址,你IP都还没有建立什么连接呢?这不就陷入了鸡生蛋还是蛋生鸡问题(bushi)。所以我们只能使用UDP服务来进行。
通过UDP发广播报文,找到DHCP服务器后,服务器选个可用IP广播/单播回复给我们。我们确定使用后再回复给DHCP服务器表示确定租用。这就是一次DHCP服务的简略流程。
END
简单总结一下:UDP 是传输层最轻量的协议。无连接、不可靠、面向报文,头部只有 8 字节。通过端口实现进程到进程的通信,通过伪首部校验和保证数据完整性。
UDP是面向报文的协议,不用担心也不存在粘包问题。
UDP 适合实时性优先、能容忍丢包的场景:DNS、DHCP、音视频、游戏、多播。