UDP是如何工作的
原文:https://www.slashroot.in/how-does-udp-work
作者:Sarath Pillai
原文发表日期:2017年6月1日
翻译:tommwq
译文:http://tommwq.tech/blog/?p=370
IETF(互联网工程任务组)是一个由工程师和计算机科学家组成的社区,他们致力于带来新的互联网技术、标准和规范。RFC文档就是由IETF发布的。
RFC通常是为了进行同行评审而编写的正式文档。RFC主要讨论了不同协议的方法及其完整的细节和特性。它主要是作为标准文件,以便工程师实现、提供反馈、提交与新协议相关的信息及其概念(RFC类似提案,因此以“Request for Comments(征求意见)”命名)。为什么要此处要讨论IETF和RFC呢?
那是因为一个编号为768的RFC文档。这可能是最短的RFC文档(只有几段文字和协议的少量细节)。RFC通常非常详细,有数百页。但是这个RFC非常短小紧凑。
这份RFC所描述的协议称为UDP(用户数据报协议)。UDP协议的复杂性较低,并且非常直观(和相对的TCP协议不同。TCP协议稍微复杂一些,因为一些可靠性方面的机制)。
TCP/IP参考模型包含5层。每层执行不同的工作来实现网络通信。最顶端的是应用层。这一层为软件和应用程序在网络中使用协议(如HTTP、FTP、SMTP)通讯提供方法。下一层是传输层,TCP和UDP(我们讨论的主题)在这里进入视野。TCP提供可靠的通信,而UDP不提供任何可靠传输机制。UDP也不提供任何顺序传输机制。再下一层是IP层,它提供了将消息传送到目标IP的方法。接下来是数据链路层,在这里物理地址(MAC)被添加到数据包头部,以便通过物理层将消息传递到网关。
层 | 功能 |
---|---|
应用层 | 这诸如入HTTP、FTP、SMTP等应用层协议工作的地方。应用程序使用这些协议进行通信。 |
传输层 | 这一层为正确传输消息添加了大量特性。基本上它通过TCP来增加可靠性。 |
网络层 | 这是IP地址进入视野的地方。这一层不提供任何可靠性保证。 |
数据链路层 | 添加源和目标(网关)的MAC地址。 |
物理层 | 这是网络硬件工作的地方。 |
不管你使用的是TCP还是UDP,IP协议是让上层协议可以在网络中工作(这是因为TCP和UDP位于传输层,而IP位于网络层)。看看上面的表格。通信从应用层开始,然后向下通过不同的层。每个层将在其上一层数据上添加自己的字段和头部。在源头,消息每进入下一层,都被添加相关的字节和信息。在目的地,消息每进入上一层,下层的无用信息都被剥离。
因此无论你根据需求选择了TCP还是UDP,都会使用IP协议,让网络通信变成可能。
相关阅读:理解TCP三次握手
在本文中我们不会讨论TCP。这是因为它有点复杂,需要单独的文章来阐述。UDP是传输层协议中使用最少的。这是因为我们日常使用的大多数应用程序都需要可靠性。UDP基本上是一种面向消息的不可靠协议。
在某种程度上UDP和IP非常相似。IP也不提供任何形式的传输或可靠性保证机制。
让我们使用tcpdump看看当建立UDP连接时会发生什么。tcpdump是可以捕获网络数据包的工具。几乎所有的Linux发行版都支持tcpdump。
相关阅读:使用tcpdump分析网络包
tcpdump可以帮助我们查看网络流量的详细信息。为了理解这一点,我们需要首先发送一个UDP请求,同时捕获网络数据包。
让我们向远程服务器发送一个DNS请求。然后在另一个终端上捕获数据包并查看。
相关阅读:DNS服务器是如何工作的
在第一个终端上执行以下命令:
ubuntu@testing:~$ sudo tcpdump -n -vvv host 8.8.8.8 and port 53
在第二个终端上执行以下命令:
ubuntu@testing:~$ dig @8.8.8.8 www.google.com
在第二个终端上执行命令时,你会在第一个终端中看到一系列消息,看起来类似:
18:40:39.758842 IP (tos 0x0, ttl 64, id 4636, offset 0, flags [none], proto UDP (17), length 56) 192.168.40.27.55625 > 8.8.8.8.53: [udp sum ok] 63851+ A? google.com. (28) 18:40:39.812844 IP (tos 0x0, ttl 59, id 53901, offset 0, flags [none], proto UDP (17), length 104) 8.8.8.8.53 > 192.168.40.27.55625: [udp sum ok] 63851 q: A? google.com. 3/0/0 google.com. [32s] A 172.217.26.174, google.com. [32s] A 172.217.26.174, google.com. [32s] A 172.217.26.174 (76)
第一行表示IP包的内容。它和UDP协议没有关系。字符串“proto UDP (17)”表示用于标识上一层协议的一个8位字段。不同的协议用不同的数字表示。如果是TCP协议,那么你在输出中看到的数字将是6,而非17。
如果IP报头中没有protocol字段,接收方将无法知道IP分组所传递的数据属于哪种协议,是ICMP还是GRE等等。我们例子中的协议是UDP,因此显示数字17。第一行输出不包含任何与UDP相关的内容,它只是告诉IP包传递的是UDP数据。
请记住,UDP包的信息,以及程序数据都封装在IP包中(正如我们前面讨论的,接收方将剥离包中与每层协议相关联的特定数据,然后提交到上一层协议,向上传递直到应用层)。
“id 4636” 是IP标识字段的一部分,在处理分片时很有用。
如果IP包过大,中间网络设备无法直接发送,这是需要对IP包进行分片。再将各个分片发送到目标地址。接收方需要使用某种标识来重新组装收到的分片。所有分片拥有相同的标识数字。接收方据此把分片视为同一个包的一部分。如果没有发生分片(比如我们的例子),大多数IP头将具有唯一的标识。
“tos 0x0”表示服务类型。
TOS(Type Of Service,服务类型)表明应该如何处理数据包。有些数据包可能需要特殊处理(例如语音电话)。
“ttl 64”表示生存时间。
生存时间是在到达最终目的地之前,一个IP数据包可能经过的最大网络设备数。如果在源和目标之间有68个设备,例子中的IP包将在第64个设备上被丢弃(因为ttl是64),并不会到达目的地。不同系统默认的生存周期是不同的。
“offset 0”也和IP分片有关。默认情况下,它始终设置为0。如果IP分片,各分片将具有相同的标识字段(如前所述),同时还有一个偏移量字段,表明重组时分片数据的位置。
让我们考虑分片的例子。假设第1个分片的标识是100,偏移量是0。第2个分片的标识是100,偏移量是170。这表示在重组时,第2个分片的数据位于原始IP包的第1360(170*8=1360)字节处。
现在我们回到主题:UDP。下面是tcpdump输出中的第二行:
192.168.40.27.55625 > 8.8.8.8.53: [udp sum ok] 63851+ A? google.com. (28)
这一行有5个主要部分。这些是UDP的全部内容。这里看到的IP地址实际上是IP包的一部分,IP地址属于UDP。IP地址存在于IP层。源IP地址是192.168.40.27,这是我电脑的IP地址。8.8.8.8是google的公共DNS地址,我们正式向这个地址发送的DNS请求。
下面是UDP的头字段。
- 源端口。这是发送UDP请求时随机选择的端口号(在例子中是55625,从第二行可以看出)。
- 目标端口:接收我们请求的应用程序的目标端口。DNS默认使用端口号53,和例子中的相同。
- 长度:请求发送的实际用户数据大小。在例子中(通过dig发送的DNS请求),请求长度是UDP请求数据长度加上UDP头长度。第二行的最后一个字段是数字28,表示用户数据占28字节。UDP包有8字节的头部数据。即UDP包的长度是28+8=36字节。
- 校验和:UDP校验和的计算有点复杂。我将写一篇文章介绍UDP和TCP校验和的计算(对于TCP和UDP,校验和计算方式是相同的)。虽然tcpdump输出中没有显示校验和值,但它看起来像0xaab或0x8921之类的值。
相关阅读:UDP和TCP校验和是如何计算的?
- 有效载荷:这是客户端实际发送的数据。在例子中是由dig生成的DNS请求
63851+ A? google.com
。A代表请求google.com的A记录,63851+是DNS事务标识,它帮助DNS客户端识别应答。
关于UDP校验和需要记住的一点是,它是可选的。UDP协议没有强制要求计算校验和。校验和用于判断数据在传输过程中是否遭到篡改。它提供了一种“错误检测”机制。
为什么UDP使用校验和进行错误检测?
这是个好问题,是个实际的问题。因为UDP要求轻量级,并未提供任何可靠性或纠错机制。既然UDP是无连接的、不可靠的,为什么还要校验和呢?
UDP不关心被丢弃的数据包,也不关心乱序传递。但是UDP希望收到的数据包是完整的(尽管是可选的,有完整性验证的规定)。但是,如果不能纠错,用校验和检查完整性又有什么用呢?。
它的确不能纠正完整性错误。但是可以丢弃校验和错误的数据包。接收方不会收取校验和错误的数据包。没有什么机制可以反馈给发送方,错误的数据包会被默默地丢弃。
如果UDP是无连接的,它如何识别应答?
8.8.8.8.53 > 192.168.40.27.55625: [udp sum ok] 63851 q: A? google.com. 3/0/0 google.com. [32s] A 172.217.26.174, google.com. [32s] A 172.217.26.174, google.com. [32s] A 172.217.26.174 (76)
上面显示的是对DNS请求应答(tcpdump输出中的最后一行)。这里的源IP和源端口是8.8.8.8和53。目标IP和目标端口为192.168.40.27和55625。
应答中的目标端口与原始请求中的源端口相同(tcpdump输出中的第二行)。
也就是说,应答直接发送给发出原始请求的端口。这是客户端程序识别正确应答的方法。
在理想情况下,客户端程序打开端口等待响应。只有在收到应答时,源端口(套接字)才会关闭。
将UDP与TCP进行比较的最佳用例
想象一下,你希望将一个视频直播给数百万用户(可能是一场板球比赛)。TCP需要大量的开销来处理这类请求。就直播流而言,如果TCP收到太多请求,操作系统必须等待所有未确认的数据。这意味着,如果有数百万个请求,操作系统将把所有未确认数据保存在缓冲区中。在这种情况下,TCP不是个好主意。
如果你需要快速且简单的响应,那么UDP是最好的选择。比如DNS、NTP等。
想想游戏之类的场景。游戏中新的状态正在不断地替换旧状态。这意味着旧状态对于客户端来说是没有用的(忘了重发缺失包的面向连接的TCP吧)。在这里UDP是一个不错的选择。