本节书摘来自异步社区《UNIX网络编程 卷1:套接字联网API(第3版)》一书中的第8章,第8.9节,作者:【美】W. Richard Stevens , Bill Fenner , Andrew M. Rudoff著,更多章节内容可以访问云栖社区“异步社区”公众号查看
8.9 服务器进程未运行
我们下一个要检查的情形是在不启动服务器的前提下启动客户。如果我们这么做后在客户上键入一行文本,那么什么也不发生。客户永远阻塞于它的recvfrom调用,等待一个永不出现的服务器应答。然而这是一个很好的例子,它要求我们更多地了解底层协议以理解网络应用进程将发生什么。
首先,我们在主机macosx上启动tcpdump,然后在同一个主机上启动客户,指定主机freebsd4为服务器主机。接着,我们键入一行文本,不过这行文本没有被回射。
macosx % udpcli01 172.24.37.94
hello, world 我们键入这一行
但没有任何内容回射回来
图8-10给出了tcpdump的输出。
首先我们注意到,在客户主机能够往服务器主机发送那个UDP数据报之前,需要一次ARP请求和应答的交换。(我们把这个交换保留在tcpdump的输出中,是为了强调在IP数据报可发往本地网络上另一个主机或路由器之前,还是有可能出现ARP请求-应答的。)
我们从第3行看到客户数据报发出,然而从第4行看到,服务器主机响应的是一个"port unreach-able"(端口不可达)ICMP消息。(长度13是12个字符加换行符。)不过这个ICMP错误不返回给客户进程,其原因我们稍后讲述。客户永远阻塞于图8-8中的recvfrom调用。我们还指出ICMPv6也有端口不可达错误类型,类似于ICMPv4(见图A-15和图A-16),因此这里讨论的结果对于IPv6也类似。
我们称这个ICMP错误为异步错误(asynchronous error)。该错误由sendto引起,但是sendto本身却成功返回。回顾2.11节,我们知道从UDP输出操作成功返回仅仅表示在接口输出队列中具有存放所形成IP数据报的空间。该ICMP错误直到后来才返回(图8-10所示为4ms之后),这就是称其为异步的原因。
一个基本规则是:对于一个UDP套接字,由它引发的异步错误却并不返回给它,除非它已连接。我们将在8.11节讨论如何给UDP套接字调用connect。很少有人明白套接字最初实现时为什么做此设计决策。(实现内涵在TCPv2第748~749页讨论。)
考虑在单个UDP套接字上接连发送3个数据报给3个不同的服务器(即3个不同的IP地址)的一个UDP客户。该客户随后进入一个调用recvfrom读取应答的循环。其中有2个数据报被正确递送(也就是说,3个主机中有2个在运行服务器),但是第三个主机没有运行服务器。第三个主机于是以一个ICMP端口不可达错误响应。这个ICMP出错消息包含引起错误的数据报的IP首部和UDP首部。(ICMPv4和ICMPv6出错消息总是包含IP首部和所有的UDP首部或部分TCP首部,以便其接收者确定由哪个套接字引发该错误,如图28-21和图28-22所示。)发送这3个数据报的客户需要知道引发该错误的数据报的目的地址以区分究竟是哪一个数据报引发了错误。但是内核如何把该信息返回给客户进程呢?recvfrom可以返回的信息仅有errno值,它没有办法返回出错数据报的目的IP地址和目的UDP端口号。因此做出决定:仅在进程已将其UDP套接字连接到恰恰一个对端后,这些异步错误才返回给进程。
只要SO_BSDCOMPAT套接字选项没有开启,Linux甚至对未连接的套接字也返回大多数ICMP“destination unreachable”(目的地不可达)错误。图A-15中除代码为0、1、4、5、11和12之外的所有ICMP目的地不可达错误均被返回。
我们将在28.7节再次讨论UDP套接字上异步错误的这个问题,并给出一个使用我们自己的守护进程获取未连接套接字上这些错误的简便方法。