We read the world wrong but say that it deceives us.
"我们看错了世界,却说世界欺骗了我们"
参考资料:TCP/IP入门经典 (第五版)
一、简介
UDP(用户数据报协议),是一个简单的面向数据报的传输层协议,进程的每个输出操作都正好产生一个UDP数据报,并组装成一份待发送的IP数据报。它在协议栈中的位置如下
特点:UDP是一个面向数据报的协议,所以它不提供可靠性:它把应用程序传给IP层的数据发送出去,但并不保证它们能到达目的地
二、UDP首部
由于不提供可靠性,所以UDP的首部比较简单
各字段含义如下:
● 端口号:就是传输层识别应用程序的一个正整数
● 16位UDP长度:UDP长度指的是UDP首部和UDP数据的总长度。注意,UDP长度的最小值为8字节,也就是说可以发送一个没有数据的UDP数据报。记得在 TCP/IP协议-IP协议 中说到IP首部中有一个首部长度,那么用IP数据报的总长减去IP首部的长度就可以得到UDP数据报的长度了
● 16位UDP检验和:关于检验和,要特别留意几点。
首先,UDP的检验和覆盖了UDP首部和UDP数据,而IP首部的检验和只覆盖了IP首部;其次,UDP的检验和是可选的,而TCP的检验和是必须的;UDP数据报的长度可以是奇数字节,所以在计算时要在末尾填充0,但是这些0可能不被传送;UDP检验和的计算方法与IP首部的检验和一样(16个bit的二进制反码求和)
最后,UDP在计算检验和的时候包含了一个12字节长的伪首部,它仅仅是为了计算检验和而设置。UDP检验和计算过程中使用的各个字段如下
如果检验和的计算结果为0,则存入的值全为1。如果传送的检验和为0,说明发送端没有计算检验和。这样,如果接收端计算的检验和不为全0,但是检验和字段为全0的话,说明传送过程中出错了,UDP数据报将会被丢弃,并且不产生任何差错报文
虽然UDP检验和是可选的,但是它们应该总是在用,毕竟还是要确报数据传输的正确性
三、IP分片和重组
为什么要在这里讨论IP分片呢?前面讲到UDP是不提供传输的可靠性的,但是至少要保证数据能完整的传输,所以只能把这个责任留给IP。下面先来了解几个概念
最大UDP数据报长度
IP数据报的最大长度为65535个字节,去掉IP首部的20个字节和UDP首部的8个字节,UDP数据报的最大长度理论上应该为65507字节。但事实上,大多数实现都达不到这个长度,原因有下:
● 应用程序接口限制:socket API提供了一个可供应用程序调用的函数,用来设置接收和发送缓存的长度,大部分系统都默认提供了可读写大于8192字节的UDP数据报。
● 内核实现:可能存在一些实现特性(或差错),使IP数据报长度小于65535
虽然IP可以发送那么长的数据报,但应用程序并不保证能够读取该长度的数据。因此,UDP编程接口允许应用程序设置能处理的最大长度。当数据报长度超出应用程序能处理的最大长度时,不同的API有着不同的处理策略,这就要求发送端控制数据报的大小
路径MTU
MTU(最大传输单元),指的是数据链路层对传输的数据帧长度的一个上限值,不同的网络类型有着不同的值。而路径MTU指的是两台通信主机路径中最小的MTU。两台主机之间的路径MTU不一定是一个常数,因为路由选择并不一定是对称的,而且网络随时都在发生变化
ICMP
ICMP(Internet控制报文协议),是一个位于网络层的协议。它经常被认为是IP层的一个组成部分,因为ICMP的主要功能是传递差错报文及其他需要注意的信息,并且ICMP报文是封装在IP数据报内部的
常见的几种差错报文:
● 端口不可达:如果接收端收到一份UDP数据报而目的端口与某个正在使用的进程不相符,那么UDP返回一个ICMP不可达报文
● 分片冲突不可达:当路由器收到一份需要分片的数据报,而在IP首部又设置了不分片的标志位,那么路由器返回一个ICMP不可达报文。并且,此时可以把这条路径的路径MTU返回给源主机或路由器,这样就可以帮助这条路径上的其他路由器确定每个子路径上的MTU,这被称作路径MTU发现机制
● 源站抑制:当一个系统(路由器或主机)接收数据报的速度比其处理速度快时,它有可能会返回一个源站抑制差错报文。由于源站抑制需要消耗带宽,并且实际上没有什么作用,所以人们并不支持产生源站抑制报文
IP分片
前面已经讲过,数据链路层一般要限制每次发送数据帧的最大长度(MTU)。任何时候IP层接收到一份要发送的IP数据报时,它要判断向本地哪个接口发送数据(选路),并查询接口获得MTU,如果数据报长度大于MTU,那就需要进行分片。分片可以发生在原始发送端主机上,也可以发生在中间路由器上。
在介绍IP首部字段的时候,谈到IP首部的“第二行”有三个字段:16位标识、3位标志和13位片偏移,它们就是被用在IP分片过程中。对于发送端发送的每份IP数据报来说,其标识字段都包含一个唯一值,在数据报分片时被复制到每个片中,表明它们是来自同一个数据报。标志字段用其中一个bit来表示“更多的片”,把该位置1表示这个还有其他分片,所以最后一片要把该位置0。片偏移字段指的是该片偏移原始数据报开始处的位置。另外,分片以后,每个片的总长度值要改为该片的长度值。标志字段中有一个bit称作“不分片”位,如果将这个置1,IP将不对数据报进行分片。如果此时导致数据报不能发送,那么将会返回一个ICMP不可达差错报文(分片冲突)
分片之后,每一片都成为一个分组,这里的分组指的是网络层到数据链路层之间的传输单元,而IP数据报是指IP层端到端的传输单元。每个分组都有自己的IP首部
需要注意是:
● 在分片时,除最后一个片外,其他每一个片中的数据部分(除IP首部外的其余部分)必须是8字节的整数倍(暂时没搞清楚为什么,个人猜测是为了方便计算片偏移,还有接收端的重组)
● 任何传输层首部只出现在第一片数据中
● 分片可能发生在源主机或者路径上的路由器,但重组只可能发生在目的主机上。因为路径上的路由器只负责转发,并不关心IP数据报的数据内容
● 即使只丢失了一片数据也需要重新传整个数据报,因为上一点,中间路由器可能再次分片,而目的端并不知道丢失的片是被如何分片的
● 各个片到达目的端时可能是乱序的,但是IP首部利用前面讲的三个字段,可以将分片正确地拼接起来
下面来完整描述一个IP数据报的传输过程作为总结,假设待发送的传输层数据报需要进行分片,并且发送端和接收端不是直接相连:
①源主机的传输层将数据报发送到网络层;
分片:②
②IP发现数据报太大,需要分片,就将数据报分片:第一片包含了数据报的开头部分数据,数据大小为8字节的整数倍,包含了传输层首部,并将标志字段中表示“更多的片”字段置1,片偏移字段置0,然后把总长度值改为分片以后的片长度值;第二片到倒数第二片包含了某个长度的数据,并且数据大小为8字节的整数倍,但是它们都没有包含传输层首部,它们的“更多的片”字段置为1,片偏移字段根据片序号和前一片的数据长度设置,总长度设置同第一片;最后一片包含了剩余的数据部分,数据长度不一定是8字节的整数倍,没有包含传输层首部,“更多的片”字段被置0,片偏移字段根据前面的数据长度设置,总长度值也根据前面的片进行设置;所有片的标识字段都相同
③网络层把每一个片作为一个分组传送给数据链路层;
传输:④~⑥
④数据链路层将分组转化为数据帧发送给下一个系统(路由器或者主机);
⑤接收到数据帧的路由器向上传送至网络层;
⑥IP检查数据报长度和MTU,如果需要分片,就跳到第②步;如果不需要分片,就跳到第④步,直到发送到目的主机;
重组:⑦
⑦目的主机陆续收到各个片,如果目的主机发现缺失了某个片,那么将会返回一个ICMP错误报文,源主机将会重新发送数据报,回到第①步;如果没有片缺失,目的主机将会根据标识字段、标志字段和片偏移将所有片重组为传输层数据报,传输层发送给应用程序,数据传输完成
关于IP分片重组的实现,可以参考 Linux TCP/IP协议栈关于IP分片重组的实现
四、UDP服务器设计
最后来讨论一下UDP那些影响使用该协议的服务器的设计和实现方面的协议特性
客户IP地址及端口号
UDP服务器必须保存来自客户数据报的源IP地址和源端口号,这个特性允许一个交互UDP服务器对多个客户进行处理
目的IP地址
某些应用程序需要知道数据报是发送给谁的,即目的IP地址
UDP输入队列
大多数UDP服务器是重复型服务器,单个服务器进程对单个UDP端口上的所有客户请求进行处理。所以必须维护一个输入队列来对差不多同时到达的请求进行处理
限制本地IP地址
大多数UDP服务器在创建UDP端点是都使其IP地址具有通配符的特点,这样可以从任何的本地接口来接收用户请求
限制远端IP地址
限制远端IP地址和端口号可以只接收特定地址的UDP数据报
每个端口有多个接收者
当UDP数据报到达目的IP地址为广播或多播地址,而且在目的IP地址和端口号处有多个端点时,就向每个端点传送一份数据报的复制。如果UDP数据报到达的是一个单播地址,那么只向其中一个端点传送一份数据报的复制
总结:UDP是一个简单的协议,本文大部分篇幅都用在介绍IP分片的内容,等到介绍TCP的时候,再讨论UDP与TCP之间的区别和联系