TCP/IP详解 卷1:协议 学习笔记 第十一章 UDP:用户数据报协议
UDP是面向数据报的运输层协议,进程的每个输出操作都正好产生一个UDP数据报,并组装成一份待发送的IP数据报。
RFC 768是UDP的正式规范。
UDP不提供可靠性。
如果IP数据报长度超过网络的MTU,就要对IP数据报进行分片,如果需要,源端到目标端之间的每个网络都能进行分片,并不只是发送端才能这样做。
端口号表示发送和接收进程。
如果TCP和UDP同时提供某种知名服务,两个协议通常选择相同的端口号,这是为了使用方便。
UDP长度字段指UDP首部和UDP数据的字节数总和,该字段的最小值为8(可以发送数据字段长为0的UDP数据报),此长度值等于IP数据报长度字段表示的长度减去IP数据报首部长字段表示的长度。
UDP检验和覆盖UDP首部和UDP数据。在计算时,由于UDP不像IP首部一样补0,UDP长度可以是奇数,但检验和算法以16bit为单位计算,因此最后长度不足时补0,这只是为了检验和的计算,而不会将补的0发送出去。
UDP数据报和TCP段都包含一个12字节长的伪首部,它是为了计算检验和而设置的,伪首部包含IP首部一些字段,目的是让UDP两次检查数据是否正确到达目的地。
上图中的填充字节是计算检验和时使用的。UDP数据报的长度在检验和计算过程中出现两次。
如果发送的UDP检验和字段值为全0,表示发送端没有计算检验和(如果计算的检验和结果为全0,则存入的值全为1(65535))。
如果接收端检测到校验和有差错,则UDP数据报被丢弃,不产生任何差错报文(IP层检测到IP首部校验和有差错时也这样做)。
尽管UDP检验和是可选的,但它们总是应该被使用。80年代,一些计算机产商默认关闭UDP检验和功能,以提高使用UDP的NFS(Network File System)的速度,这在单个局域网中可能是可接受的,因为链路层会对数据帧(如以太网或令牌环数据帧)进行循环冗余检验(比校验和更有效)可以检测到大多数的差错。以前的经验表明,当数据报通过路由器时,什么都可能发生,路由器中存在软件和硬件差错,导致数据报中的数据被修改,如果关闭端到端的UDP检验和功能,则这些差错在UDP数据报中就不能被检测出来,另外,一些数据链路层协议(如SLIP)没有任何形式的数据链路检验和。
Host Requirements RFC声明,UDP检验和选项默认打开,且如果发送端计算了校验和,接收端必须检验接收到的校验和。但许多系统没有遵守这一点,它们只在出口检验和选项被打开时才验证接收到的检验和。
如果你发送的数据很重要,不要完全相信UDP或TCP的检验和,这些都只是简单的检验和,不能检测出所有可能发生的差错。
任何时候IP层接收到一份要发送的IP数据报时,它要判断向本地哪个接口发送数据(选路),并查询该接口获得其MTU,IP层把MTU与数据报长度进行比较,如果需要则进行分片。分片可以发生在原始发送端主机上,也可以发生在中间路由器上。
把一份IP数据报分片后,只有到达目的地才进行重新组装(而其他网络协议有些要求在下一站就进行重新组装,而不是在最终目的地)。已经分片过的数据报有可能会再次进行分片,IP首部中包含的数据为分片和重新组装提供了足够的信息。
对于IP首部,与分片有关的字段如下:发送端发送的每份IP数据报的标识字段都包含一个唯一值,该值在数据报分片时被复制到每个片中。标志字段用其中一个比特表示后面还有片,除了最后一片外,其他每个组成数据报的片都要把该比特置1。片偏移字段指该片偏移原始数据报开始处的位置。当数据报被分片后每个片的总长度要改为该片的长度值。IP首部的标志字段中有一个比特称为不分片位,如果将这一比特置为1,IP将不对数据报进行分片,遇到要分片时,把数据报丢弃并发送一个ICMP差错报文(“需要进行分片但设置了不分片比特”)给起始端。
IP数据报分片后,每一片都成为一个分组,有自己的IP首部,并在选择路由时与其他分组独立,这样,当数据报的这些片到达目的端时有可能会失序,但IP首部中有足够的信息让接收端正确组装这些数据报片。
但IP分片即使只有一片数据丢失也要重传整个数据报(是否重传取决于上层协议,对于UDP来说,它没有超时重传机制(但一些UDP应用程序本身也执行超时和重传)),因为如果分片不是在发送端分的,则发送端不知道中间路由是如何分片的,因此要避免分片。
使用UDP很容易导致IP分片(TCP试图避免分片,对于应用程序来说,几乎不可能强迫TCP发送一个需要进行分片的长报文段)。
在一个以太网上,数据帧最大长度为1500字节(不算帧头和帧尾),其中1472字节留给数据(假定IP首部为20字节,UDP首部为8字节),如果以数据长度为1471、1472、1473、1474字节发送UDP数据报,tcpdump查看UDP数据报的分片(最后两次应发生分片):
IP数据报被分片后,tcpdump打印出了更多信息,第三行、第四行的frag 26304和第五行、第六行的frag 26313指的是IP首部中标识字段的值。
如上图,当UDP数据报长度为1473字节时被分片,如果以太网使用的是IEEE 802封装,则由于以太网首部多了8字节,1465字节成为UDP数据报分片的最小长度。
分片信息中的下一个数字,即冒号和@号之间的数字,是该片除IP首部外的长度。
在分片时,除最后一片外,其他每一片中的数据部分(除IP首部外的部分)必须是8字节的整数倍,原因在于,如下IP首部图,3位标志字段占3位,但目前只有2位有意义,该字段中的最低位记为MF(More Fragment),MF=1即表示后面“还有分片”,MF=0表示这已是若干数据报片中的最后一个;该字段中间的一位记为DF(Don’t Fragment),意思是“不能分片”,只有当DF=0时才允许分片。而此时片偏移只有13位可用了,由于总长度是16位,为了使偏移量能覆盖整个数据部分,13位片偏移字段的单位就是8字节了。
位于@符号后的数字是从数据报开始处计算的片偏移值,此数字后面的加号对应于IP首部中3bit标志字段中的更多片比特,目的是让接收端知道后面还有片。
上图中第4行和第6行省略了协议名、源端口号、目的端口号,其中协议名是可以打印出来的,因为协议名在IP首部中,但端口号在UDP首部中,只能在第一片中发现(任何运输层首部只出现在第一片数据中)。
分组是IP层和链路层之间传送的数据单元,一个分组可以是一个完整的IP数据报,也可以是IP数据报的一个分片。
发生ICMP不可达差错的另一种情况是,当路由器收到一份需要分片的数据报,而在IP首部又设置了不分片(DF)的标志比特。如果某程序需要判断到达目的端的路途中最小MTU是多少(称为路径MTU发现机制),那么这个差错就能被该程序使用。
下一站网络的MTU字段提供下一站的MTU(为了让发送端知道如果不分片,在此处路由的最大数据报长度)。如果路由器没有提供这种新的ICMP差错报文格式,那么下一站网络的MTU字段就设为0。
ICMP不可达差错的产生:
当SLIP被安装到主机sun时,在配置SLIP过程中我们配置了sun到netb的链路MTU,现在我们想从netb到sun方向判断链路MTU(点对点链路中,不要求两个方向的MTU为相同值)。采用的方法是在主机solaris上运行ping发送ICMP回显请求到主机bsdi,增加数据分组长度,直到看到分组被分片为止,此时在主机sun上运行tcpdump:
每行中的DF说明在IP首部中设置了不分片比特,这是实现路径MTU发现机制的一部分。
第一行显示的是回显请求通过路由器netb到达sun主机,没有进行分片,因此还没有达到netb的SLIP MTU。
第二行中DF标志被复制到回显应答报文中,虽然回显应答和回显请求报文长度相同(超过600)字节,但sun外出的SLIP接口MTU为552,因此回显应答需要进行分片,但设置了DF标志比特,因此sun产生了一个ICMP不可达差错报文给bsdi,此时差错报文在bsdi处被丢弃。这就是为什么在solaris上看不到任何回显应答的原因,这些应答永远不能通过sun。
第三行和第六行中,mtu=0表示主机sun没有在ICMP不可达报文中返回出口的MTU值。
有些系统不支持路径MTU发现功能,但可以修改traceroute程序,用它来确定路径MTU,要做的是发送分组,并设置不分片标志比特,发送的第一个分组长度正好与出口MTU相等,每次收到ICMP不能分片差错时就减小分组长度。如果发送ICMP不可达报文的路由器发送的报文是新格式,包含出口MTU,则用该MTU值来发送,否则就用下一个更小的MTU值来发送,根据RFC的声明,MTU值的个数是有限的,因此取下一个更小MTU值来发送。
如上图,首先尝试从主机sun到主机slip的路径MTU,我们提前知道SLIP链路的MTU为296:
上例中,路由器bsdi没有在ICMP差错报文中返回出口MTU,因此我们选择下一个较小的MTU。当修改中间路由bsdi,令其返回出口MTU值,此时再运行修改过的traceroute程序:
此时就不用逐个尝试多个MTU值了。
现在许多但不是所有的广域网都可以处理大于512字节的分组,利用路径MTU发现机制,应用可以充分利用更大的MTU发送报文。
如上图,从solaris上使用UDP向slip发送一份650字节数据到slip,由于slip主机位于MTU为296的SLIP链路后,因此,任何长于268(296-20-8)字节且不分片比特置1的UDP数据都会使bsdi路由器产生ICMP不能分片差错报文。
solaris上以五秒为间隔产生一个650字节数据部分的UDP数据报,以下是在sun上运行tcpdump的结果:
以上例子中,路由器bsdi不返回下一跳MTU信息。
第一行发送的第一个数据报中将DF比特置1,结果应该是bsdi路由器发回我们可以猜测的结果(第二行中发回),但令人不解的是,第三行在发送下一个数据报时,应该将DF置0,但还是发送的DF为1的数据报,其结果是同样的ICMP差错(第四行)。
第五行显示IP已经知道了发往该目的地址的数据报不能将DF比特置1,因此IP将数据报在源站上进行分片,分成两片发出(第五、六行)。
但第七行中,发送的数据报仍设置了DF位,因此bsdi将其丢弃并返回ICMP差错,这是发生了IP定时器超时,作用是通知IP查看现在路径MTU是否增大了。从第7行和第19行可以看出,IP每过30秒就将DF置1,30秒太短了,RFC 1191建议其取10分钟,可通过修改solaris系统的ip_ire_pathmtu_interval参数改变该值。
上图中分片的大小是不正确的,实际MTU值是296字节,这意味着经solaris分片的数据报还将被bsdi分片,下图是目的主机slip上收到的第一份数据报(上图中第五、六行)内容,依据是IP标识字段都是47942:
上例中,solaris不应对外出数据报分片,它应该置DF比特为0,让具有最小MTU的路由器完成分片工作。
下例重复上例,但将路由器bsdi设置为在ICMP不能分片差错报文中返回下一跳MTU:
与上例相同,前两个数据报都是将DF置1后发送出去的。目的端这次只收到了三片数据而非四片。272+272+114是658字节,比发送的UDP数据部分多8字节,原因是272和114是不算IP首部的IP数据报片长,包括8字节的UDP首部长。
产生一个数据部分8192字节的UDP数据报,预测这会在以太网上产生6个数据报片,同时在发送此数据报前,ARP缓存是空的,这样发送第一个数据报片前必须交换ARP请求和应答,以下是tcpdump结果:
可见每个数据片都产生了一个ARP请求。在接收到第一个ARP应答时,只发送最后一个数据报片,似乎前五个数据报片都被丢弃了,实际上,这是ARP实现的正常操作,等待一个ARP应答时,只将最后一个报文发送给特定主机。Host Requirements RFC要求实现中必须防止这种类型的ARP洪泛(即以高速率重复发送到同一个IP地址的ARP请求报文),建议最高速率是每秒一次,它还规定ARP应保留至少一个报文,这个报文必须是最后一个报文,这正是我们看到的结果。
上例中发送端在发送若干ARP请求后,最先发送的数据报是最后一个偏移的数据报片,说明ARP的输入是后进先出的。
上例中有一个无法解释的不正常现象,svr4发回7个而非6个ARP应答。
在第一个数据报片到达时,IP层启动一个定时器,定时值为30或60秒,如果定时器超时而所有数据报片未能全部到达,那么将这些数据报片丢弃,如果不这么做,那些永远无法到达的数据报片迟早将接收端缓存填满。
在最后一个ARP应答返回后,tcpdump继续监测了5分钟,发现svr4并没有返回ICMP组装超时差错。没看到的原因有两个:一是大多从Berkeley派生的实现从不生成该差错,这些实现会设置定时器,也会在定时器溢出时将数据报片丢弃;二是并未收到包含UDP首部的偏移量为0的第一个数据报片,不要求实现对这种片产生ICMP差错,原因是因为没有运输层首部,ICMP差错的接收者不知道是哪个进程发送的数据报被丢弃。
理论上,IP数据报最大长度为65535字节,这是由IP首部16比特总长度字段限制的。除去20字节IP首部和8字节UDP首部,UDP数据报中用户数据最长长度为65507字节,但大多实现提供的最大值不到此值,有两个限制因素:一是应用可能受到其程序接口限制,socket API提供了一个可供应用程序调用的函数,以设置接收和发送缓存的长度,对于UDP socket,这个长度与应用程序可以读写的最大UDP数据报的长度直接相关,现在大部分系统都默认提供可读写大于8192字节的UDP数据报(使用这个默认值是因为它是NFS读写用户数据的长度的默认值);二是来自于TCP/IP的内核实现,可能存在一些特性或差错。
主机被要求最小必须能接收576字节的IP数据报,因此在许多UDP应用程序的设计中,其应用数据被限制成512字节或更小。
IP能发送或接收特定长度的数据报并不意味着应用程序可以读取该长度的数据,因此,UDP编程接口允许应用指定每次返回的最大字节数,如果接收到的数据报长度大于应用程序能处理的长度,则会发生的情况取决于具体实现:Berkeley版socket API对数据报截断,丢弃多余数据,而应用程序在4.3BSD Reno及其后的版本可以收到数据报被截断的消息;SVR 4下的socket API(包括Solaris 2.x)不截断数据报,超出部分在后面的读取中返回,但它不通知应用要对单个UDP数据报进行多次读取操作;TLI API不丢弃数据,它返回一个标志表明可以获得更多数据,而应用后面的读操作将返回数据报的其余部分。
UDP也可以产生ICMP源站抑制差错,当一个主机或路由器接收数据报的速度比其处理速度快时,可能产生这个差错(即使该主机或路由器已经没有缓存并丢弃了数据报,也不要求一定发送源站抑制报文)。
早期RFC要求路由器在没有缓存时产生源站抑制差错,但较新的RFC提出路由器不应产生源站抑制差错报文,由于源站抑制要消耗网络带宽,且对于拥塞来说是一种无效而不公平的调整,因此人们现在对于路由器源站抑制差错的态度是不支持的。
如果采用UDP,那么BSD实现通常忽略其接收到的源站抑制报文,部分原因在于,在接收到源站抑制差错报文时,导致源站抑制的进程可能已经终止了。
当客户端使用UDP数据报时,其IP首部包含源端和目的端IP地址,UDP首部包含了源端和目的端UDP端口号,当服务器接收到UDP数据报时,操作系统会告诉它发送者的源IP地址和端口号,这个特性允许一个交互UDP服务器对多个客户进行处理,给每个发送请求的客户发回应答。
一些应用需要知道数据报的目的IP地址,如RFC规定TFTP服务器必须忽略接收到的发往广播地址的数据报。这要求操作系统将接收到的UDP数据报中的目的IP地址发送给应用,并非所有实现都提供此功能。
大多UDP服务器是交互服务器,这意味着单个服务器进程对单个UDP端口上的所有客户请求进行处理。每个端口都与一个有限大小的输入队列相联系,这会出现排队溢出,造成数据报丢失。排队溢出时应用程序并不知道发生了溢出,发送端也不会收到数据报被丢弃的通知。UDP输出队列是先进先出的。
大多UDP服务器在创建UDP端口时使其本地IP地址具有通配符的特点,此时如果到达的UDP数据报目的地为服务器端口,那么在任何本地接口均可接收到它。
有可能在相同的端口上启动不同服务器,每个服务器具有不同的本地IP地址,但一般需要告诉系统重用相同端口号没有问题,如使用sockets API时,必须指定SO_REUSEADDR socket选项。
如果服务器A设置socket为任意本地IP地址+某端口号,此时如果服务器B设置socket为特定本地IP地址+与服务器A相同的端口号,此时到达服务器B的数据报,理论上就能被两个服务器接收,但存在隐含的优先级关系,即先选择更具体的socket,即服务器B。
大多数系统允许UDP对远端地址进行限制,可只接收特定IP和端口号的UDP数据报。如果这么设置时没有选择本地地址,伯克利派生的系统将会自动选择一个本地IP地址,此IP成为到达远端的源端接口。
上图中从上到下的顺序是UDP判断用哪个应用程序接收数据报时采用的顺序。
大多数系统在某一时刻只允许一个进程与某个本地IP地址和端口号相关联,如果此时启动另一个具有相同本地地址和端口号的服务器,那么它将不运行,尽管我们使用了SO_REUSEADDR socket选项。
在支持多播的系统上,多个进程可以使用同一个IP地址和端口号,应用通常必须告诉API这是可行的(如使用SO_REUSEADDR socket选项)。当UDP数据报到达的目的IP为广播地址或多播地址,且目的IP地址和端口号处有多个进程时,就向每个端点传送一份数据报;但如果UDP数据报到达的是一个单播地址,那么只向其中一个端点传送一份数据报的复制,选择哪个端点取决于各个不同的系统实现。
假定有一份8192字节数据部分的UDP数据报需要发送,则对于IP来说,加上UDP 8字节的首部,有8200字节数据报需要发送(除IP首部外的数据部分)。
如果一个UDP数据报被分为4片,接收端只接收到前两片,而应用在10s后超时重发此UDP数据报,并且被分为相同的4片,假定这次接收端只接收到后两片,且接收端重新组装的时间为60秒,则这4片不会重新组装成一份IP数据报,因为重传的IP数据报有一个新的标识字段,而重新装配只针对具有相同标识字段的分段。
某主机上的netstat程序显示TCP、IP的检验和差错远远多于UDP的检验和差错,原因可能是给该主机传的UDP数据报没有计算校验和(发送端校验和全填0时)或UDP数据报主要是本地通信,发生差错几率小。
当发生分片时,是否将IP首部中的选项也复制到每片的IP首部中取决于选项类型,不严格的和严格的源站选路选项将IP首部复制到每个数据报片中;时间戳选项和记录路由选项的IP首部只出现在第一个数据报片中。
有很多方式过滤一个送往给定UDP端口号的输入数据报,可根据目的IP地址、源IP地址、源端口号过滤。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!