15.5 数据报
在本章中,我们重点介绍了如何编写与客户之间维持连接的应用程序。我们用面向连接的TCP套接字来完成这一工作。但在有些情况下,在程序中花费时间来建立和维持一个套接字连接是不必要的。
早先,我们在程序getdate.c中所使用的daytime服务就是一个很好的例子。我们首先创建一个套接字,然后建立连接,读取一个响应,最后关闭连接。在这一过程中,我们使用了很多操作步骤,仅仅为了获取一个日期。
daytime服务还可以用数据报通过UDP来访问。为了访问它,发送一个数据报给该服务,然后在响应中获取一个包含日期和时间的数据报。这一过程非常简单。
当客户需要发送一个短小的查询请求给服务器,并且期望接收到一个短小的响应时,我们一般就使用由UDP提供的服务。如果服务器处理客户请求的时间足够短,服务器就可以通过一次处理一个客户请求的方式来提供服务,从而允许操作系统将客户进入的请求放入队列。这简化了服务器程序的编写。
因为UDP提供的是不可靠服务,所以你可能发现数据报或响应会丢失。如果数据对于你来说非常重要,就需要小心编写UDP客户程序,以检查错误并在必要时重传。实际上,UDP数据报在局域网中是非常可靠的。
为了访问由UDP提供的服务,你需要像以前一样使用套接字和close系统调用,但你需要用两个数据报专用的系统调用sendto和recvfrom来代替原来使用在套接字上的read和write调用。
下面是一个修改过的getdate.c版本,它通过UDP数据报服务来获取数据。对先前版本的改动将以阴影显示。
如你所见,需要改动的地方非常少。像以前一样,我们用getservbyname来查找daytime服务,但通过请求UDP协议来指定数据报服务。我们使用带有SOCK_DGRAM参数的socket调用来创建一个数据报套接字。我们还是采用与以前一样的方式来构建目标地址,但现在需要发送一个数据报而不是仅仅从套接字上读取数据。
因为我们并没有明确地建立一条到指定UDP服务的连接,所以必须用某些方式让服务器知道你需要接收一个响应。在本例中,给服务器发送一个数据报(在这里,从准备接收响应的缓存区中发送一个字节的数据),它返回包含日期和时间的响应。
sendto系统调用从buffer缓存区中给使用指定套接字地址的目标服务器发送一个数据报。它的原型如下所示:
在正常应用中,flags参数一般被设置为0。
recvfrom系统调用在套接字上等待从特定地址到来的数据报,并将它放入buffer缓存区。它的原型如下所示:
同样,在正常应用中,flags参数一般被设置为0。
为了让示例程序变得简短,我们省略了错误处理。当错误发生时,sendto和recvfrom都将返回-1并设置errno。可能的错误见表15-7。
表 15-7
除非用fcnt1将套接字设置为非阻塞方式(正如在前面的接受TCP连接中看到的那样),否则recvfrom调用将一直阻塞。我们可以用与前面的面向连接服务器一样的方式,通过select调用和超时设置来判断是否有数据到达套接字。此外,还可以用alarm时钟信号来中断一个接收操作(参见第11章)。