23.1介绍
在一个空闲的(idle)TCP连接上,没有任何的数据流,许多TCP/IP的初学者都对此感到惊奇。也就是说,如果TCP连接两端没有任何一个进程在向对方发送数据,那么在这两个TCP模块之间没有任何的数据交换。你可能在其它的网络协议中发现有轮询(polling),但在TCP中它不存在。言外之意就是我们只要启动一个客户端进程,同服务器建立了TCP连接,不管你离开几小时,几天,几星期或是几个月,连接依旧存在。中间的路由器可能崩溃或者重启,电话线可能go down或者back up,只要连接两端的主机没有重启,连接依旧保持建立。
这就可以认为不管是客户端的还是服务器端的应用程序都没有应用程序级(application-level)的定时器来探测连接的不活动状态(inactivity),从而引起任何一个应用程序的终止。回忆在10.7结束,BGP每隔30秒就向对方发送一个应用程序探测。这是一个应用程序定时器(application timer),与TCP存活定时器不同。
然而有的时候,服务器需要知道客户端主机是否已崩溃并且关闭,或者崩溃但重启。许多实现提供了存活定时器来完成这个任务。
存活(keepalive)并不是TCP规范的一部分。在Host Requirements RFC罗列有不使用它的三个理由:(1)在短暂的故障期间,它们可能引起一个良好连接(good connection)被释放(dropped),(2)它们消费了不必要的宽带,(3)在以数据包计费的互联网上它们(额外)花费金钱。然而,在许多的实现中提供了存活定时器。
存活定时器是一个包含争议的特征。许多人认为,即使需要这个特征,这种对对方的轮询也应该由应用程序来完成,而不是由TCP中实现。一些人对这个话题表现了极大的热情,甚至达到宗教般的狂热。
如果两个终端系统之间的某个中间网络上有连接的暂时中断,那么存活选项(option)就能够引起两个进程间一个良好连接的终止。例如,如果正好在某个中间路由器崩溃、重启的时候发送存活探测,TCP就将会认为客户端主机已经崩溃,但事实并非如此。
一些服务器应用程序可能代表客户端占用资源,它们需要知道客户端主机是否崩溃。存活定时器可以为这些应用程序提供探测服务。Telnet服务器和Rlogin服务器的许多版本都默认提供存活选项。
个人计算机用户使用TCP/IP协议通过Telnet登录一台主机,这是能够说明需要使用存活定时器的一个常用例子。如果某个用户在使用结束时只是关掉了电源,而没有注销(log off),那么他就留下了一个半打开(half-open)的连接。在图18.16,我们看到如何在一个半打开连接上通过发送数据,得到一个复位(reset)返回,但那是在客户端,是由客户端发送的数据。如果客户端消失,留给了服务器端半打开的连接,并且服务器又在等待客户端的数据,那么等待将永远持续下去。存活特征的目的就是在服务器端检测这种半打开连接。
23.2 描述
在此描述中,我们称使用存活选项的那一段为服务器,另一端为客户端。也可以在客户端设置该选项,且没有不允许这样做的理由,但通常设置在服务器。如果连接两端都需要探测对方是否消失,那么就可以在两端同时设置(比如NFS)。
若在一个给定连接上,两小时之内无任何活动,服务器便向客户端发送一个探测段。(我们将在下面的例子中看到探测段的样子。)客户端主机必须是下列四种状态之一:
1. 客户端主机依旧活跃(up)运行,并且从服务器可到达。从客户端TCP的正常响应,服务器知道对方仍然活跃。服务器的TCP为接下来的两小时复位存活定时器,如果在这两个小时到期之前,连接上发生应用程序的通信,则定时器重新为往下的两小时复位,并且接着交换数据。
2. 客户端已经崩溃,或者已经关闭(down),或者正在重启过程中。在这两种情况下,它的TCP都不会响应。服务器没有收到对其发出探测的响应,并且在75秒之后超时。服务器将总共发送10个这样的探测,每个探测75秒。如果没有收到一个响应,它就认为客户端主机已经关闭并终止连接。
3. 客户端曾经崩溃,但已经重启。这种情况下,服务器将会收到对其存活探测的响应,但该响应是一个复位,从而引起服务器对连接的终止。
4. 客户端主机活跃运行,但从服务器不可到达。这与状态2类似,因为TCP无法区别它们两个。它所能表明的仅是未收到对其探测的回复。
服务器不必担心客户端主机被关闭然后重启的情况(这里指的是操作员执行的正常关闭,而不是主机的崩溃)。当系统被操作员关闭时,所有的应用程序进程(也就是客户端进程)都将被终止,客户端TCP会在连接上发送一个FIN。收到这个FIN后,服务器TCP向服务器进程报告一个文件结束,以允许服务器检测这种状态。
在第一种状态下,服务器应用程序不知道存活探测是否发生。凡事都是由TCP层处理的,存活探测对应用程序透明,直到后面2,3,4三种状态发生。在这三种状态下,通过服务器的TCP,返回给服务器应用程序错误信息。(通常服务器向网络发出一个读请求,等待客户端的数据。如果存活特征返回一个错误信息,则将该信息作为读操作的返回值返回给服务器。)在状态2,错误信息类似于“连接超时”。状态3则为“连接被对方复位”。第四种状态看起来像连接超时,或者根据是否收到与该连接相关的ICMP错误信息,而可能返回其它的错误信息。
22.3 存活范例
检查上一部分讲述的状态2,3,4,使用存活选项观察包的交换。
对方崩溃
我们来看服务器主机崩溃而没有重启的情况。用下列步骤来模拟:
- 在客户端(在主机bsdi运行sock程序)和主机svr4上的标准echo服务器之间建立连接。客户端用-K选项提供存活功能。
- 观察客户端TCP每隔两小时发送的存活包和服务器TCP对它们的应答
- 从服务器断开以太网线路,并保持断开直到这个例子结束。这使得客户端认为服务器已经崩溃
- 我们期望客户端在断言连接死亡之前,看到它发送的10个存活探测(每个间隔75秒)。
这是客户端的交互式输出:
bsdi % sock -K svr4 echo -K是存活选项
hello, world 开始时输入此行以验证连接活跃
hello, world 并看到echo
4小时后断开以太网线路
read error: Connection timed out 发生在启动后约6小时又11分
图23.1显示了tcpdump的输出(已经删除了连接建立过程和通告的窗口)
图23.1 判断主机是否崩溃的存活包
行1,2,3从客户端发送字符串”hello,world”到服务器,并且得到返回。第一个存活探测发生在2个小时(7200秒)之后(行4)。在行6的TCP段被发送之前,我们首先看到了一个ARP请求和一个ARP回复。行6的存活探测引起了另一端的响应(行7)。两个小时后,相同的顺序包被交换(行8-11)。
如果我们注意观察存活探测(行6和行10)中的所有域,就会发现它的顺序号小于下一个将要发送的顺序号(本例中是当它应该是14时,这个顺序号域出现的是13),但因为在该段中没有数据,所以tcpdump没有打印顺序号域(对于空段,tcpdump只在设置有SYN,FIN或RST标志的时候才打印顺序号。)正是这个不正确的顺序号强迫服务器的TCP对存活探测做出响应。该响应告知客户端服务器期望的下一顺序号(14)。
然后我们断开线路,等待2小时后下一个探测的失败。当下一个探测发生时,注意我们在线路上看不到TCP段,这是由于主机对ARP请求没有响应。在放弃连接之前,我们仍然能够看到客户端每隔75秒发送了10个探测。从交互式脚本,我们看到由TCP返还给客户端进程的错误码被转换成“连接超时”,事实如此。
对方崩溃并重启
在这个例子中,我们将会看到客户端崩溃和重启的情况。初始的情况和上次相同,但在验证连接活跃之后,我们将服务器从以太网断开,重新启动,然后重新连接到以太网。我们期望下一个存活探测能使服务器产生一个复位,因为服务器现在对于这个连接一无所知。这是交互会话:
bsdi % sock -K svr4 echo -K是存活选项
hi, there 验证连接活跃
hi, there 这是对方的echo
以太网断开,服务器重启
read error: Connection reset by peer图23.2
显示了tcpdump
输出(已经删除了连接建立过程和通告的窗口)。 图23.2 当对方主机崩溃且重启时的存活例子
我们建立了一个连接,并且从客户端向服务器发送了9个字节的数据(行1-3)。两个小时后客户端发送了第一个存活探测,得到的响应是服务器的一个复位。客户端应用程序打印了我们看得懂的错误信息“连接被对方复位”。
另一端不可到达
在这个例子中,客户端没有崩溃,但是在存活探测被发送的10分钟内不可到达。中间的路由器可能崩溃了,或者电话线暂时处于无序状态,或者发生了其它类似情况。
为模拟这个例子,我们将建立一个通过拨号SLIP链路从主机slip到vangogh.cs.berkeley.edu的TCP连接,然后断掉链路。首先,交互式输出如下:
bsdi % sock -K vangogh.cs.berkeley.edu echo
testing
testing
在某个时候拨号SLIP链路被断开 read error: No route to host
图23.3显示了在路由器bsdi收集的tcpdump输出(已经删除了连接建立过程和通告的窗口)。
图23.3当对方不可到达时的存活例子
我们如先前一样地开始分析这个例子:行1-3验证了连接是活跃的。两小时后的第一个存活探测正常(行4和行5),但在下一个两小时的存活探测发出之前,我们断开了路由器sun和netb的SLIP连接。(参考相册的拓扑图)
第六行的存活探测引发了来自路由器sun网络不可到达的ICMP。对于路由器slip上的接收TCP来说,这是一个软件错误。它记录收到了一个ICMP错误,但是错误的接收并不引起终止这个连接。在发出主机放弃之前,另外8个存活探测间隔75秒相继被发出。这一次返回给应用程序的错误产生了不同的信息“没有达到主机的路由”。
23.4 小结(略)