首页 > 代码库 > 《TCP/IP详解卷2:实现》笔记--UDP:用户数据报协议
《TCP/IP详解卷2:实现》笔记--UDP:用户数据报协议
用户数据报协议,即UDP,是一个面向数据报的简单运输层协议:进程的每次输出操作只产生一个UDP数据报,从而发送
一个IP数据报。
进程通过创建一个Internet域内的SOCK_DGRAM类型的插口,来访问UDP。该类型插口默认地称为无连接的。每次进程发送
数据时,必须指定目的IP地址和端口号。每次从插口上接收数据报时,进程可以从数据报中收到源IP地址和端口号。
UDP插口也可以被连接到一个特殊的IP地址和端口,这样,所有写到该插口的数据报都被发往该目的地,而且只有来自该IP
地址和端口号的数据才被传给该进程。
1.UDP的protosw结构
下图显示了UDP的协议交换入口
2.UDP的首部
UDP首部定义成一个udphdr结构,下图是UDP首部的数据结构和图。
在代码中,通常把udp首部作为一个紧跟着UDP首部的IP首部来引用。这就是udp_input如何处理收到的ip数据报,以及
udp_output如何构造外出的ip数据报,这种联合的ip/udp首部是一个udpiphdr结构,如下图所示:
20个字节的ip首部定义成一个ipovly结构,如下图所示。不幸的是,这个结构并不是一个真正的ip首部,大小虽然相同,但是
字段不同。
3.udp_init函数
domaininit函数在系统初始化时调用udp的初始化函数(udp_init),这个函数所做的唯一的工作是把头部PCB的向前和向
后指针指向它自己。这是一个双向链表。
udb PCB的其他部分都被初始化为0,尽管在这个头部PCB中唯一使用的字段是inp_lport,它是要分配的下一个UDP临时
端口号。
4.udp_output函数
当应用程序调用一下五个写函数中的任意一个时,发生UDP输出。这五个函数是:send,sendto或sendmsg、write或
writev。如果插口已经连接上,则可任意调用调用这五个函数,尽管用sendto或sendmsg不能指定目的地址。如果插口
还没有连接上,则只能调用sendto和sendmsg,并且必须指定一个目的地址。下图总结了这五个函数,它们在终止时,
都调用udp_output,该函数再调用ip_output。
五个函数终止调用sosend,并把一个指向msghdr结构的指针作为参数传给该函数。要输出的数据被分装在一个mbuf链
上,sosend把一个可选的目标地址和可选的控制信息放在mbuf中,并发布PRU_SEND请求。
udp_output函数的处理流程如下:
1.丢掉可选控制信息。udp输出不适用任何控制信息。
2.临时连接一个未连接上的接口。如果调用方为UDP数据报指定了目的地址,则插口是由in_pcbconnect临时连接到该地址
的,并在该函数的最后被断连。
3.在前面加上ip/udp首部。M_PREPEND在数据的前面为IP和UDP首部分配空间。
4.1.UDP检验和计算和伪首部
在讨论udp_output的后一部分之前,我们描述一下udp如何填充ip/udp首部的某些字段,如何计算udp检验和,如何传递
ip/udp首部及数据给ip输出的,这些工作很巧妙地使用了ipovly结构。
下图显示了udp_output在由m指向的mbuf链的第一个存储器上构造的28字节ip/udp首部。没有阴影的字段是udp_output
填充的,有阴影的字段是由ip_output填充的。
在计算UDP检验和使用以下三个事实:1.在伪首部中(如下图)的第三个32bit字看起来与ip首部中的第三个32bit字类似:
2个8bit值和一个16bit值。2.伪首部中的三个32bit值的顺序是无关的。事实上,internet检验和的计算不依赖于所使用的
16bit值的顺序。3.在检验和计算中另外再加上一个全0的32bit字没有任何影响。
udp_output利用这三个事实,填充udpiphdr结构的字段,如下图所示。
在20字节的ip首部中,最后三个32bit字被用作检验和计算的为首部,ip首部的前两个32bit字也用作检验和计算中,但他们
被初始化为0,不影响最后的检验和。
下图总结了我们描述的操作:
1.上图中最上面的图是为首部的协议定义。
2.中间的图是源代码中使用的udpiphdr结构,被用于计算udp检验和。
3.下面的图是出现在线路上的ip/udp首部。上面有箭头的7个字段是udp_output在检验和之前填充的,上面有星号的3个字段
是udp_output在检验和计算之后填充的,其他6个有阴影的字段是ip_output填充的。
下面是udp_output函数的后半部分。
1.为检验和计算准备伪首部。
2.计算检验和
3.填充UDP长度、TTL和TOS。
4.发送数据报。调用ip_output发送数据报。
5.断连临时连接的插口。
5.udp_input函数
5.1.对收到的udp数据报的一般确认
1.丢弃IP选项。
2.验证UDP长度。与UDP数据报相关的两个长度是:IP首部的长度字段(ip_len)和UDP首部的长度字段(uh_ulen)。
比较这两个长度,可能有三种可能性:
1)ip_len等于uh_ulen。这是通常情况。
2)ip_len大于uh_ulen。ip首部太大,如下图所示。代码相信两个长度中小的那个,并从数据报的最后移走多余的数据字节,
从mbuf链的最后截断数据。
3)ip_len小于uh_ulen。当udp首部的长度给定时,ip数据报比可能的小,如下图所示。这说明数据报有错误,必须丢弃,
没有其他的选择。
3.保存ip首部的备份,验证udp的检验和。
5.2.分用单播数据报
假定数据报的目的地址是一个单播地址。
1.检查“向后一个”高速缓存。udp维护一个指针,该指针指向最后在其上接收数据报的internet pcb,在查看pcb之前,可能必须
搜索udp表上的pcb,把最近一次接收pcb的外部和本地地址以及端口号和收到的数据报进行比较。这称为“向后一个”高速缓存。
它是根据这样一个假设,即收到的数据报极有可能要发往上一个数据报发往的同一端口。
2.搜索所有的UDP的PCB。
3.生成ICMP端口不可达差错。如果没有找到匹配的PCB,UDP通常产生一个ICMP端口不可达差错。
4.返回源站IP地址和源站端口。
5.处理IP_RECVDSTADDR插口选项。该选项把收到的UDP数据报中的目的IP地址作为控制信息返回。
6.把数据加到插口的接收队列中。
5.3.分用多播和广播数据报
这些数据报被提交给匹配的所有插口,而不仅仅是一个插口。
6.udp_saveopt函数
如果进程指定了IP_RECVDSTADDR插口选项,则udp_input调用udp_saveopt,从收到的数据报中接收目的IP地址。
7.udp_ctlinput函数
当icmp_input收到一个ICMP差错(目的主机不可达、参数问题、重定向、源站抑制和超时)时,调用相应协议的pr_ctlinput
函数。对于UDP,调用udp_cltinput。我们来看下对收到的ICMP所做的处理:
1.icmp_input把icmp类型和码转换成PRC_xxxx差错码。
2.把PRC_xxxx差错码传给协议的控制输入函数。
3.internet pcb协议(TCP和UDP)把PRC_xxx差错码映射到一个unix的errno值,这个值被返回给进程。
8.udp_usrreq函数
许多操作都要调用协议的用户请求函数,在某个UDP插口上调用五个写函数中的任意一个,都以请求PRU_SEND调用UDP
的用户请求函数结束。下面讨论该函数中各个请求。
1.PRU_ATTACH请求,来自socket系统调用,分配一个新的pcb,把它加到udp pcb表的前面,把插口结构和pcb连接在一起。
2.close系统调用发布PRU_DETACH请求。从udp表中移走pcb,并释放该pcb。
3.PRU_BIND请求完成绑定操作。
4.如果有PRU_LISTEN请求,则是无效的,只有面向连接的协议才使用它。
5.在一个udp应用程序中,客户或服务器,可以调用connect,它发送CONNECT请求修改插口发送或接收的外部ip地址和端口号。
6.socketpair系统调用发布PRU_CONNECT2请求。
8.对于UDP插口,有两种情况会产生PRU_DISCONNECT请求:
a.当关闭了一个连接上的UDP插口时,在PRU_DETACH之前调用PRU_DISCONNECT。
10.调用无法写函数,发布PRU_SEND请求,最终调用udp_output发送该数据报。
12.PRU_SOCKADDR和PRU_PEERADDR请求分配来自系统调用getsockname和getpeername。
9.udp_sysctl函数
udp的sysctl函数只支持一个选项,udp检验和标志位。系统管理员可以禁止用sysctl程序使能或静止udp检验和。
《TCP/IP详解卷2:实现》笔记--UDP:用户数据报协议