UDP服务器的设计[转]

来源:互联网 发布:黑客用什么编程软件 编辑:程序博客网 时间:2024/05/16 19:36

使用UDP的一些蕴含对于设计和实现服务器会产生影响。通常,客户端的设计和实现比服务器端的要容易一些,这就是我们为什么要讨论服务器的设计,而不是讨论客户端的设计的原因。典型的服务器与操作系统进行交互作用,而且大多数需要同时处理多个客户。
    通常一个客户启动后直接与单个服务器通信,然后就结束了。而对于服务器来说,它启动后处于休眠状态,等待客户请求的到来。对于UDP来说,当客户数据报到达时,服务器苏醒过来,数据报中可能包含来自客户的某种形式的请求消息。
    在这里我们所感兴趣的并不是客户和服务器的编程方面([Stevens 1990]对这些方面的细节进行了讨论),而是UDP那些影响使用该协议的服务器的设计和实现方面的协议特性(我们在18.11节中对TCP服务器的设计进行了描述)。尽管我们所描述的一些特性取决于所使用UDP的实现,但对于大多数实现来说,这些特性是公共的。
11.12.1 客户IP地址及端口号
    来自客户的是UDP数据报。IP首部包含源端和目的端IP地址, UDP首部包含了源端和目的端的UDP端口号。当一个应用程序接收到UDP数据报时,操作系统必须告诉它是谁发送了这份消息,即源IP地址和端口号。
    这个特性允许一个交互UDP服务器对多个客户进行处理。给每个发送请求的客户发回应答。
11.12.2 目的IP地址
    一些应用程序需要知道数据报是发送给谁的,即目的IP地址。例如, Host Requirements RFC规定,TFTP服务器必须忽略接收到的发往广播地址的数据报(我们分别在第12章和第15章对广播和TFTP进行描述)。
    这要求操作系统从接收到的UDP数据报中将目的IP地址交给应用程序。不幸的是,并非所有的实现都提供这个功能。
    socket API以IP_RECVDSTADDR socket选项提供了这个功能。对于本文中使用的系统,只有BSD/386、4.4BSD和AIX3.2.2支持该选项。SVR4、SunOS 4.x和Solaris 2.x都不支持该选项。
11.12.3 UDP输入队列
    我们在1.8节中说过,大多数UDP服务器是交互服务器。这意味着,单个服务器进程对单个UDP端口上(服务器上的名知端口)的所有客户请求进行处理。
    通常程序所使用的每个UDP端口都与一个有限大小的输入队列相联系。这意味着,来自不同客户的差不多同时到达的请求将由UDP自动排队。接收到的UDP数据报以其接收顺序交给应用程序(在应用程序要求交送下一个数据报时)。
    然而,排队溢出造成内核中的UDP模块丢弃数据报的可能性是存在的。可以进行以下试验。我们在作为UDP服务器的bsdi主机上运行sock程序:
    bsdi % sock -s -u -v -E -R256 -P30 6666
    from 140.252.13.33, to 140.252.13.63: 1111111111 从sun发送到广播地址
    from 140.252.13.34, to 140.252.13.35: 4444444444444 从s v r 4发送到单播地址
    我们指明以下标志: - s表示作为服务器运行, - u表示UDP,- v表示打印客户的IP地址,- E表示打印目的IP地址(该系统支持这个功能)。另外,我们将这个端口的UDP接收缓存设置为256字节(- R),其每次应用程序读取的大小也是这个数( - r)。标志- P30表示创建UDP端口后,先暂停30秒后再读取第一个数据报。这样,我们就有时间在另两台主机上启动客户程序,发送一些数据报,以查看接收队列是如何工作的。
    服务器一开始工作,处于其3 0秒的暂停时间内,我们就在sun主机上启动一个客户,并发送三个数据报:
    sun % sock -u -v 140.252.13.63 6666 到以太网广播地址
    connected on 140.252.13.33.1252 to 140.252.13.63.6666
    1 1 1 1 1 1 1 1 1 1 1 1字节的数据(新行)
    2 2 2 2 2 2 2 2 2 1 0字节的数据(新行)
    3 3 3 3 3 3 3 3 3 3 3 1 2字节的数据(新行)
    目的地址是广播地址(140.252.13.63)。我们同时也在主机svr4上启动第2个客户,并发送另外三个数据报:
    svr4 % sock -u -v bsdi 6666
    connected on 0.0.0.0.1042 to 140.252.13.35.6666
    444444444444414字节的数据(新行)
    55555555555555516字节的数据(新行)
    666666669字节的数据(新行)
    首先,我们早些时候在bsdi上所看到的结果表明,应用程序只接收到2个数据报:来自sun的第一个全1报文,和来自svr4的第一个全4报文。其他4个数据报看来全被丢弃。
    图11-20给出的tcpdump输出结果表明,所有6个数据报都发送给了目的主机。两个客户的数据报以交替顺序键入:第一个来自sun,然后是来自svr4的,以此类推。同时也可以看出,全部6个数据报大约在1 2秒内发送完毕,也就是在服务器休眠的30秒内完成的。

    我们还可以看到,服务器的- E选项使其可以知道每个数据报的目的IP地址。如果需要,它可以选择如何处理其接收到的第一个数据报,这个数据报的地址是广播地址。
    我们可以从本例中看到以下几个要点。首先,应用程序并不知道其输入队列何时溢出。只是由UDP对超出数据报进行丢弃处理。同时,从tcpdump输出结果,我们看到,没有发回任何信息告诉客户其数据报被丢弃。这里不存在像ICMP源站抑制这样发回发送端的消息。最后,看来UDP输出队列是FIFO(先进先出)的,而我们在11.9节中所看到的ARP输入却是LIFO(后进先出)的。
11.12.4 限制本地IP地址
    大多数UDP服务器在创建UDP端点时都使其本地IP地址具有通配符( wildcard )的特点。这就表明进入的UDP数据报如果其目的地为服务器端口,那么在任何本地接口均可接收到它。例如,我们以端口号777启动一个UDP服务器:
    sun % sock -u -s 7777
    然后,用n e t s t a t命令观察端点的状态:
    sun % netstat -a -n -f inet
    Active Internet connections (including servers)
    Proto Recv-Q Send-Q Local Address Foreign Address (state)
    udp 0 0 *.7777 *.*
    这里,我们删除了许多行,只保留了其中感兴趣的东西。- a选项表示报告所有网络端点的状态。- n选项表示以点数格式打印IP地址而不用DNS把地址转换成名字,打印数字端口号而不是服务名称。-f inet选项表示只报告TCP和UDP端点。
    本地地址以*.7777格式打印,星号表示任何本地IP地址。
    当服务器创建端点时,它可以把其中一个主机本地IP地址包括广播地址指定为端点的本地IP地址。只有当目的IP地址与指定的地址相匹配时,进入的UDP数据报才能被送到这个端点。用我们的sock程序,如果在端口号之前指定一个IP地址,那么该IP地址就成为该端点的本地IP地址。例如:
    sun % sock -u -s 140.252.1.29 7777
    就限制服务器在S L IP接口(140.252.1.29)处接收数据报。netstat输出结果显示如下:
    Proto Recv-Q Send-Q Local Address Foreign Address (state)
    udp 0 0 140.252.1.29.7777 *.*  
    如果我们试图在以太网上的主机bsdi以地址140.252.13.35向该服务器发送一份数据报,那么将返回一个ICMP端口不可达差错。服务器永远看不到这份数据报。这种情形如图11 - 2 1所示。

    有可能在相同的端口上启动不同的服务器,每个服务器具有不同的本地IP地址。但是,一般必须告诉系统应用程序重用相同的端口号没有问题。
    使用sockets API时,必须指定SO_REUSEADDRsocket选项。在sock程序中是通过-A选项来完成的。
    在主机sun上,可以在同一个端口号(8888)上启动5个不同的服务器:

    除了第一个以外,其他的服务器都必须以- A选项启动,告诉系统可以重用同一个端口号。5个服务器的netstat输出结果如下所示:

    在这种情况下,到达服务器的数据报中,只有带星号的本地IP地址,其目的地址为140.252.1.255,因为其他4个服务器占用了其他所有可能的IP地址。
    如果存在一个含星号的IP地址,那么就隐含了一种优先级关系。如果为端点指定了特定IP地址,那么在匹配目的地址时始终优先匹配该IP地址。只有在匹配不成功时才使用含星号的端点。
11.12.5 限制远端IP地址
    在前面所有的netstat输出结果中,远端IP地址和远端端口号都显示为*.*,其意思是该端点将接受来自任何IP地址和任何端口号的UDP数据报。大多数系统允许UDP端点对远端地址进行限制。
    这说明端点将只能接收特定IP地址和端口号的UDP数据报。sock程序用- f选项来指定远端IP地址和端口号:
    sun % sock -u -s -f 140.252.13.35.4444 5555
    这样就设置了远端IP地址140.252.13.35(即主机bsdi)和远端端口号4444 。服务器的有名端口号为5555。如果运行netstat命令,我们发现本地IP地址也被设置了,尽管我们没有指定。
    Proto Recv-Q Send-Q Local Address Foreign Address (state)
    udp 0 0 140.252.13.33.5555 140.252.13.35.4444
    这是在伯克利派生系统中指定远端IP地址和端口号带来的副作用:如果在指定远端地址时没有选择本地地址,那么将自动选择本地地址。它的值就成为选择到达远端IP地址路由时将选择的接口IP地址。事实上,在这个例子中, sun 在以太网上的IP地址与远端地址140.252.13.33相连。
    图11 - 2 2总结了UDP服务器本身可以创建的三类地址绑定。

    在所有情况下,lport指的是服务器有名端口号, localIP必须是本地接口的IP地址。表中这三行的排序是UDP模块在判断用哪个端点接收数据报时所采用的顺序。最为确定的地址(第一行)首先被匹配,最不确定的地址(最后一行IP地址带有两个星号)最后进行匹配。
11.12.6 每个端口有多个接收者
    尽管在RFC中没有指明,但大多数的系统在某一时刻只允许一个程序端点与某个本地IP地址及UDP端口号相关联。当目的地为该IP地址及端口号的UDP数据报到达主机时,就复制一份传给该端点。端点的IP地址可以含星号,正如我们前面讨论的那样。
    例如,在SunOS 4.1.3中,我们启动一个端口号为9999的服务器,本地IP地址含有星号:
    sun % sock -u -s 9999
    接着,如果启动另一个具有相同本地地址和端口号的服务器,那么它将不运行,尽管我们指定了- A选项:
    sun % sock -u -s 9999 我们预计它会失败
    can't bind local address: Address already in use
    sun % sock -u -s -A 9999 因此,这次尝试- A参数
    can't bind local address: Address already in use
    在一个支持多播的系统上(第12章),这种情况将发生变化。多个端点可以使用同一个IP地址和UDP端口号,尽管应用程序通常必须告诉API是可行的(如,用- A 标志来指明SO_REUSEADDRsocket选项)。
    4.4BSD支持多播传送,需要应用程序设置一个不同的socket选项(SO_REUSEPORT)以允许多个端点共享同一个端口。另外,每个端点必须指定这个选项,包括使用该端口的第一个端点。
    当UDP数据报到达的目的IP地址为广播地址或多播地址,而且在目的IP地址和端口号处有多个端点时,就向每个端点传送一份数据报的复制(端点的本地IP地址可以含有星号,它可匹配任何目的IP地址)。但是,如果UDP数据报到达的是一个单播地址,那么只向其中一个端点传送一份数据报的复制。选择哪个端点传送数据取决于各个不同的系统实现。


    UDP是一个简单协议。它的正式规范是RFC 768 [Postel 1980],只包含三页内容。它向用户进程提供的服务位于IP层之上,包括端口号和可选的检验和。我们用UDP来检查检验和,并观察分片是如何进行的。
    接着,我们讨论了ICMP不可达差错,它是新的路径M T U发现功能中的一部分(2.9节)。用Traceroute和UDP来观察路径MTU发现过程。还查看了UDP和ARP之间的接口,大多数的ARP实现在等待ARP应答时只保留最近传送给目的端的数据报。
    当系统接收IP数据报的速率超过这些数据报被处理的速率时,系统可能发送ICMP源站抑制差错报文。使用UDP时很容易产生这样的ICMP差错。

原创粉丝点击