UDP是一个简单的面向数据报的运输层协议:进程的每个输出操作都正好产生一个UDP数据报,并组装称一个待发送的IP数据报。这与面向流字符协议不同,如TCP,应用程序产生的全体数据与真正发送的单个IP数据报可能没什么联系。
UDP封装成一份IP数据报的格式如下:
UDP不提供可靠性,它把应用程序传给IP层的数据发送出去,但是不能保证它们到达目的地。
UDP首部:
UDP长度指的是UDP首部和UDP数据的字节长度。该字段最小为8字节。这个UDP长度是有冗余的。IP数据报长度指的是数据报全长,因此UDP数据报长度等于IP数据报长度减去IP首部长度(该值在IP首部中指定)。
UDP检验和覆盖UDP首部和UDP数据部分。IP首部检验和只覆盖IP首部部分。UDP检验和计算和IP首部检验和类似,但UDP数据报长度可以为奇数字节,但是检验和算法是把若干16bit相加。解决办法是必要时在最后增加填充字节0。UDP数据报和TCP数据报都包含一个12字节的伪首部,它是为了计算检验和而设置的。伪首部包含IP首部的一些字段,其目的是让UDP两次检查数据是否正确到达目的地。伪首部格式如下:
UDP数据报的长度在检验和中出现了两次,如果发送端没有计算检验和而接收端监测到检验和计算有差错,就丢弃数据报,不产生任何差错报文。
UDP检验和是一个端到端的检验和,由发送端计算,由接收端验证。其目的是为了发现UDP首部和数据在传送过程中的任何改动。
IP分片
物理网络层一般要限制每次发送数据帧的最大长度。任何时候IP层接收到一份要发送的IP数据报时,它要判断向本地哪儿个接口发送数据(选路),并查询该接口获得MTU。IP把数据报和MTU比较,需要需要则进行分片。分片可能发生在原始端主机上,也可能发生在中间路由器上。
把一份IP数据报分片后,只有到达目的地才进行组装。重新组装由目的IP完成,其目的是使分片和重新组装过程对运输层是透明的。已经分片过的数据可能会再次进行分片。IP首部中的数据为分片和组装提供了足够的信息。
对于发送端发送的每份IP数据报来说,其标识字段包含唯一值。该值在数据报分片时包含到每一个分片中。标识字段用其中1bit表示“更多的片”,除最后一片外,其它每个数据报的片该bit都置为1。片偏移字段是该片偏移原始数据报开始处的位置。另外,当数据报被分片时,每个片的总长度值要改为该片的长度值。
最后标识字段中有一个不分片位,如果该bit置为1,IP将不对该数据报进行分片,相反把数据报丢弃并发送一个ICMP差错报文。
当IP数据报分片后,每一片都成为一个分组,具有自己的IP首部,并在选择路由时与其它分片独立。这样,这些数据报到达目的地时可能会失序,但IP首部中有足够的信息让接收端能正确组装这些数据分片。
尽管IP分片过程看起来是透明的,但有一点让人不想使用它:即使丢失一片数据也要重传整个数据报。这是因为IP本身没有超时重传机制,由更高层进行超时和重传。TCP有超时重传机制,UDP没有,一些UDP应用程序本身也执行超时重传机制。当来自TCP的某一片丢失时,TCP在超时后会重发整个TCP报文段,该报文段对应于一份IP数据报。没有办法只重传IP数据报的一片。事实上,如果对数据报分片的是中间路由器,而不是起始段系统,那么起始段系统就不知道数据报是如何分片的。就这个原因,经常要避免分片。
ICMP不可达差错(需要分片)
发送ICMP不可达差错的另一种情况是,当路由器收到一份需要分片的数据报,而在IP首部中又设置了不分片的标志比特。如果某个程序需要判断到达目的端的路径最小MTU是多少——称做路径MTU发现机制,那么这个差错就可以被该程序使用。这种情况下的ICMP报文格式如下图所示。
如果路由器没有提供这种新的ICMP差错报文格式,那么下一站的MTU设置为0。
最大的UDP数据报长度
理论上,IP数据报的最大长度为65535,这是由IP首部16bit总长度字段所限制的。去除20字节的IP首部和8字节的UDP首部,UDP数据报中用户数据长度最大为65507字节。但是大多数实现提供的最大长度比这个小。
我们将遇到两个方面限制。第一应用程序可能会受到其程序接口的限制。socket API提供了一个可供应用程序调用的函数,以设置接收和发送缓存的长度。对于UDP socket,这个长度和应用程序可以读写的最大UDP数据报的长度有关。现在大部分系统都默认提供可读写大于8192字节的UDP数据报。
第二个限制来自TCP/IP的内核实现,可能存在一些实现特性,使IP数据报长度小于65535字节。
UDP服务器的设计
通常一个客户启动后与服务器相连,就结束了。对于服务器来说,它启动后处于休眠状态,等待客户请求的到来。对于UDP来说,当客户数据报到达时,服务器苏醒过来,数据报中可能包含来自客户的某种形式的请求。
客户IP地址和端口号
IP首部包含源端和目的端地址,UDP首部包含了源端和目的端端口号,当一个应用程序接收到UDP数据报时,操作系统必须告诉它是谁发送了这个消息,即源端地址和端口号。这个特性允许一个交互服务器和多个客户进行处理,给每个发送请求的客户发送应答。
目的IP地址
一些应用程序需要知道数据报是发给谁的,即目的IP地址。如RFC规定,TFTP服务器必须忽略发往广播地址的数据报。这要求操作系统从接受到的UDP数据报中将目的IP交给应用程序。
UDP输入队列
大多数UDP服务器是交互式服务器,这意味着单个服务器进程对单个UDP端口上的所有客户请求进行处理。通常程序所使用的每个UDP端口都与一个有限大小的输入队列相联系。这意味着,来自不同客户的差不多同时到达的请求将由UDP排队,接收到的UDP数据报以其接收顺序交于应用程序。然而排队溢出造成内核中UDP模块丢弃数据报时可能存在的。
限制本地IP地址
大多数UDP服务器在创建UDP端点时都使其本地IP地址具有通配符特性。这表明进入的UDP数据报如果其目的地为服务器端口,那么任何本地接口都可接受到它。用netstat命令观察一个服务器端点状态。
-a选项表示报告所有网络端口的状态,-n选项表示以点数格式打印IP地址而不用DNS把地址转换成名字,打印数字端口号而不是名字。-f inet表示只报告UDP和TCP端点。
本地地址以*.7777格式打印,星号表示任何本地IP地址。
当服务器创建端点时,它可以把一个主机本地IP地址包括广播地址指定为端点的本地IP地址。只有当目的地址与指定IP匹配时,进入的UDP数据报才能被送到这个端点。如果在服务器接口中有一个含星号的IP地址,那么就存在一种优先级,如果为端点指定了特定IP地址,优先匹配该IP地址,匹配不成功才使用含星号的端点。
限制远端IP地址
在前面的netstat命令显示中,远端IP地址和端口号均为星号,其意思是该端点接受任何IP地址和任何端口号的UDP数据报。大多数系统允许UDP端点对远端地址进行限制。这说明端点将只能接受特定IP地址和端口号的UDP数据报。
在伯克利派生系统中指定远端IP地址和端口号带来的副作用:如果指定远端地址时没有选择本地地址,那么将自动选择本地地址。它的值将成为选择到达远端IP地址路由时将选择的接口IP地址。
UDP服务器本身可以创建三类地址绑定:
每个端口有多个接收者
大多数系统在某一时刻只允许一个程序端点与某个本地IP地址及UDP端口号相连。当目的地为该IP地址及端口号的UDP数据报到达主机时,就复制一份传给该端点。端点的IP地址可以含星号。
在支持多播的系统上,多个端点可以使用同一个IP地址和端口号,尽管应用程序通常必须告诉API是可行的(指定SO_REUSEADDR socket选项)。
当UDP数据报到达目的IP地址为广播或多播地址,而且在目的IP地址和端口号处有多个端点时,就像每个端点传送一份数据报的复制。如果UDP数据报到达的是一个单播地址,那么只像其中一个端点传送一份数据报复制。选择哪儿个端点传送数据取决于各个不同的系统实现。