在官方的正式文档中,TCP/IP 协议簇也称为国际互联网协议簇。TCP/IP 协议簇是目前使用最为广泛的全球互联网技术,其分层结构如图 1 所示:
如图 1 所示,数据链路层主要负责处理传输媒介等众多的物理接口细节;网络层负责处理数据分组在网络中的活动,包括上层数据报文的分割、选路 phost2008-08-21T00:00:00等;传输层则负责为两台主机提供端到端的通信;应用层将负责处理应用程序的特定细节。其中,IP 协议是网络层的核心协议,用来提供不可靠、无连接的数据传递服务;而 TCP 协议则处于传输层,其基于不可靠无连接的 IP 协议能够为两台主机提供面向连接的、可靠的通信。UDP?
由于 TCP 是面向连接的协议,因此在两台主机通信之前,需要首先建立起一条连接。下面我们将简要介绍 TCP 连接的建立以及通信双方是如何保持已建立的 TCP 连接的。
一个 TCP 连接的建立需要通过著名的“三次握手”来完成。下面的例子将直观给出一个 TCP 连接的建立过程。
在本文的下述描述中,客户端主机均为 testClient.cn.ibm.com(Linux),服务器主机均为 testServer.cn.ibm.com(AIX)。在 testClient 主机的一终端上执行 tcpdump –i eth0 host testServer 命令,启动 tcpdump 监听网络数据(其中,eth0 是客户主机与外部网络进行通信所使用的网卡);与此同时,在客户主机的另一个终端上执行下述命令: (root@testClient /)>telnet testServer。此时客户主机上 tcpdump 的输出如清单 1 所示。
# tcpdump –S -i en0 host testServer1 14:02:38.384918 IP testClient.cn.ibm.com.43370 > testServer.cn.ibm.com.telnet: S 3392458353:3392458353(0) …2 14:02:38.629578 IP testServer.cn.ibm.com.telnet > testClient.cn.ibm.com.43370: S 881279296:881279296(0) ack 3392458354 …3 14:02:38.629592 IP testClient.cn.ibm.com.43370 > testServer.cn.ibm.com.telnet: . ack 881279297 … |
注意:我们删除了 tcpdump 输出结果中的部分无关信息。为了便于理解,我们将上述输出转换为实际序列图 2。
从图 2 中我们可以清楚地看到,在 testClient 与 testServer 之间建立连接时,要经过以下三次握手过程:
一个 TCP 连接在完成上述的三次握手之后便建立完毕;此后,连接的两端即可进行信息的相互传递。因此,TCP 连接可以认为是以两端 IP 地址和端口进行标识的一个通信信道,而 TCP 连接的建立就是向通信双方进行上述通信信道注册的过程。TCP 连接一旦建立,只要通信双方之间的中间结点(包括网关和交换机、路由器等网络设备)工作正常,那么在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。
TCP 连接的这种特性,使得一个长期不交换任何信息的空闲连接可以长期保持数小时、数天甚至数月。中间路由器可以崩溃、重启,网线可以被挂断再连通,只要两端的主机没有被重启,TCP 连接就可以被一直保持下来。
理想状态下,一个 TCP 连接可以被长期保持。然而,在实际应用中,客户端或服务器端上维持的一个看似正常的 TCP 连接可能已经断连。TCP 连接主要受到两个方面的影响而导致断连:网络中间节点和客户端 / 服务器节点参与通信的两方节点?
在实际网络应用中,两个主机之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等。因此,两个主机之间 TCP 连接的保持同样会受到中间节点的影响,尤其是会受到防火墙(软件或硬件防火墙)的限制。防火墙是一种装置,有多种不同的实现方式(软件实现、硬件设备实现或是软硬件相结合实现),它需要依据一系列规则对进出的信息流进行扫描,并允许安全(符合规则)的信息交互、阻止不安全(违反规则)的信息交互。防火墙的工作特性决定了要维护一个网络连接就需要耗费较多的资源,并且企业防火墙常常位于企业网络的出入口,长时间维护非活跃的 TCP 连接必将导致网络性能的下降。因此,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导致 TCP 连接断连。类似的,如果中间节点异常导致来自客户端关闭连接的请求无法传递到服务器端,也将导致服务器端的相应连接发生断连。
另一方面,对于一个 TCP 连接两端的主机而言,创建 TCP 连接需要耗费一定的系统资源。如果不再使用某个连接,那么我们总是希望进行通信的两个主机能够主动关闭相应的连接,以便释放所占用的系统资源。然而,如果由于客户端出现异常 ( 例如崩溃或异常重启 ) 而导致连接未能正常关闭,这将导致服务器端的连接断连。
无论是客户端节点或是服务器端节点,断连的 TCP 连接已经不能传递任何信息,因此,维护大量断连的 TCP 连接将导致系统资源的浪费。这种系统资源的浪费可能并不会对客户端节点带来太大问题;然而,对于服务器主机而言,这可能会导致系统资源(尤指内存资源和 socket 资源)被耗尽而拒绝为新的用户请求提供服务。因此在实际应用中,服务器端需要采取相应的方法来探测 TCP 连接是否已经断连。
探测 TCP 连接是否断连或是工作正常的原理比较简单:定期向连接的远程通信节点发送一定格式的信息并等待远程通信节点的反馈,如果在规定时间内收到来自远程节点的正确的反馈信息,那么该连接就是正常的,否则该连接已经断连。依据该原理,目前常用的探测方法有以下三种。
应用程序本身附带探测其自身建立的 TCP 连接的功能。这种方法具有极大的灵活性,可以依据应用本身的特点选择相应的探测机制和功能实现。然而,实际应用中,大部分应用程序均没有附带自我探测的功能。
此种方法就是在服务节点上安装相应的第三方应用程序来探测该节点上所有的 TCP 连接是否正常或是已经断连。该方法最大的不足就是需要所有支持探测的客户端能够识别来自该探测应用的数据报文,因此,实际应用中比较少见。
最常用的探测方法就是采用 TCP 协议层提供的保活探测功能即 TCP 连接保活定时器。尽管该功能并不是 RFC 规范的一部分,但是几乎所有的类 Unix 系统均实现了该功能,所以使得该探测方法被广泛使用。
接下来的部分,我们将重点讨论来自 TCP 协议层的保活探测方法。
TCP 连接的保活定时器可以在应用层实现,也可以在 TCP 中提供。这个问题存在争议,因此 TCP 连接的保活探测并不是 TCP 规范中的一部分。但为了方便,几乎所有类 Unix 系统均在 TCP 中提供了相应的功能。
操作系统 | 保活定时器 |
---|---|
AIX | # no -a | grep keep tcp_keepcnt = 8 tcp_keepidle = 14400 tcp_keepintvl = 150 |
Linux | # sysctl -A | grep keep net.ipv4.tcp_keepalive_intvl = 75 net.ipv4.tcp_keepalive_probes = 9 net.ipv4.tcp_keepalive_time = 7200 |
FreeBSD | #sysctl -A | grep net.inet.tcp net.inet.tcp.keepidle=… net.inet.tcp.keepintvl=… |
不同系统上的各参数的时间单位不尽相同。在 AIX 上,tcp_keeidle/tcp_keepinit/tcp_keepintvl 的时间单位是 0.5 秒;而在 Linux 上,net.ipv4.tcp_keepalive_intvl 和 net.ipv4.tcp_keepalive_time 的时间单位则为秒。并且,上述参数仅对运行在其上的服务器应用连接有效。
注:在 Solaris 上可通过“ndd /dev/tcp \?”命令显示上述类似参数信息,而在 HP Unix 上则可通过 nettune 或 ndd 命令进行查询。
由于所有类 Unix 系统上均支持这种功能,因此,在接下来的部分中我们将基于 AIX 系统具体讲述上述参数的意义和作用机制。
正如清单 2 中列出的一样,AIX 上的保活探测机制由 4 个参数来控制,其具体意义见清单 3:
控制参数 | 参数说明 |
---|---|
tcp_keepcnt | 关闭一个非活跃连接之前进行探测的最大次数,默认为 8 次 |
tcp_keepidle | 对一个连接进行有效性探测之前运行的最大非活跃时间间隔,默认值为 14400(即 2 个小时) |
tcp_keepintvl | 两个探测的时间间隔,默认值为 150 即 75 秒 |
我们来看一个具体的例子。在 testServer 端(AIX 主机)采用 tcp_keepidel=240(即 2 分钟):tcp_keepcnt=8:tcp_keepintvl=150(即 75 秒)的参数值;启动 testServer 上的 tcpdump 查看网络包的交互情况;从 testClient 端发起请求建立和 testServer 之间的一个 telnet 连接。在连接建立完成之后,拔出 testClient 端的网线并观察服务器端的数据输出(见清单 4)。
1 # tcpdump -i en1 host testServer.cn.ibm.com2 04:51:51.379716 IP testClient.cn.ibm.com.telnet.40621 > testServer.cn.ibm.com.telnet: S 4097149880:4097149880(0)3 04:51:51.379755 IP testServer.cn.ibm.com.telnet > testClient.cn.ibm.com.40621: S 2543529892:2543529892(0) ack 40971498814 04:51:51.380609 IP testClient.cn.ibm.com.telnet.40621 > testServer.cn.ibm.com.telnet: . ack 1 5 ...6 04:51:54.924058 IP testServer.cn.ibm.com.telnet > testClient.cn.ibm.com.40621: P 676:696(20) ack 87 7 04:51:54.924909 IP testClient.cn.ibm.com.telnet.40621 > testServer.cn.ibm.com.telnet: . ack 696 8 04:53:54.550192 IP testServer.cn.ibm.com.telnet > testClient.cn.ibm.com.40621: . 695:696(1) ack 869 04:55:09.550997 IP testServer.cn.ibm.com.telnet > testClient.cn.ibm.com.40621: . 695:696(1) ack 8610 04:56:24.552053 IP testServer.cn.ibm.com.telnet > testClient.cn.ibm.com.40621: . 695:696(1) ack 8611 04:57:39.552615 IP testServer.cn.ibm.com.telnet > testClient.cn.ibm.com.40621: . 695:696(1) ack 8612 04:58:54.553446 IP testServer.cn.ibm.com.telnet > testClient.cn.ibm.com.40621: . 695:696(1) ack 8613 05:00:09.554287 IP testServer.cn.ibm.com.telnet > testClient.cn.ibm.com.40621: . 695:696(1) ack 8614 05:01:24.555117 IP testServer.cn.ibm.com.telnet > testClient.cn.ibm.com.40621: . 695:696(1) ack 8615 05:02:39.555958 IP testServer.cn.ibm.com.telnet > testClient.cn.ibm.com.40621: . 695:696(1) ack 8616 05:03:54.557282 IP testServer.cn.ibm.com.telnet > testClient.cn.ibm.com.40621: . 695:696(1) ack 8617 05:05:09.559795 IP testServer.cn.ibm.com.telnet > testClient.cn.ibm.com.40621: R 696:696(0) ack 87 |
从清单 4 中可以看出,第 6 行的报文是本连接发送的最后数据,而第 7 行则是对第 6 行数据的确认。其后,该连接上没有任何数据交互,从而使得该连接一直处于非活跃状态。经过 2 分钟(第 8 行数据报时间 04:53:54 和第 7 行数据报时间 04:51:54 之差,即 tcp_keepidle 的值)的非活跃时间后,第 8 行是服务器端发起第一个保活探测数据报。由于服务器端没有收到客户端关于探测报文的相应,因此再经过 tcp_keepintvl 的时间间隔(75 秒)之后,第 9 行显示服务器端再次发起保活探测数据报。服务器端持续发送了 tcp_keepcnt 个探测报文(上面结果显示,在 AIX 上是持续发送 tcp_keepcnt+1 个探测报文)之后,仍然没有收到来自客户端的任何回应,所以服务器在第 17 行向客户端发送复位报文同时在服务器端关闭了该连接。
需要注意的是,保活探测虽然通过发送 TCP 探测报文,但探测报文不会对正常的 TCP 连接产生任何影响。从清单 4 可以看出,第 8 行发送数据的 TCP 报文序号为 695 起始的 1Byte 数据,而该数据在第 6 行已经发送并被客户端确认。对于正常状态的连接,客户端在收到探测报文之后将返回一个第 7 行所示的 ACK 报文并借此向服务器端表明连接工作正常。
接下来,我们将通过一个实际的 TCP 断连的例子来分析上述机制对 TCP 连接保持的影响,并针对需要长时间保持 TCP 连接的应用提出两种可选的解决方案。
所有服务器主机均划为一个局域网,并处于防火墙 B 之后。由于工作需要,来自工作区局域网的主机 testClient 需和服务器局域网内的 testServer 上的数据库使用 TCP/IP 建立一个连接,testClient 上的上层应用将通过该连接对 testServer 上的数据库进行相应操作。
在实际测试中,我们发现,在 testClient 和 testServer 均工作正常的情况下,testClient 上的客户端在事先没有收到任何异常信息的情况下,所持有的连接会出现非预期的断连现象(在试图通过连接进行数据库操作时,会被告知 connection is reset by foreign host 的错误)。
由于该现象不断出现,并且网络内的中间节点(路由器和交换机等)均工作正常,因此可以排除物理因素(如掉电、宕机等)的可能。为了便于分析断连原因,我们首先查看了 testServer 机器上的默认保活设置:
# no -a | grep keeptcp_keepcnt = 8 tcp_keepidle = 14400tcp_keepintvl = 150 |
testServer 上的 tcp_keepidle 为 14400,即 2 个小时。既然中间节点工作正常,为什么保活机制没有其作用呢?为了进行分析,我们采用 tcpdump 工具捕获 testClient 和 testServer 上的报文信息,见清单 5 和清单 6 所示。
1 10:18:58.881950 IP testClient.cn.ibm.com.59098 > testServer.cn.ibm.com.telnet: S 1182666808:1182666808(0) ...2 10:18:58.882001 IP testServer.cn.ibm.com.telnet > testClient.cn.ibm.com.59098: S 3333341833:3333341833(0) ack 1182666809 ...3 10:18:58.882845 IP testClient.cn.ibm.com.59098 > testServer.cn.ibm.com.telnet: . ack 1 ...4 ...5 10:19:03.165568 IP testServer.cn.ibm.com.telnet > testClient.cn.ibm.com.59098: P 1010:1032(22) ack 87 ...6 10:19:03.166457 IP testClient.cn.ibm.com.59098 > testServer.cn.ibm.com.telnet: . ack 1032 ...7 12:19:05.445336 IP testServer.cn.ibm.com.telnet > testClient.cn.ibm.com.59098: . 1031:1032(1) ack 86 ...8 12:19:05.445464 IP testClient.cn.ibm.com.59098 > testServer.cn.ibm.com.telnet: R 86:87(1) ack 1031 ... |
1 # tcpdump -e -i eth0 host testServer.cn.ibm.com2 10:18:55.800553 IP testClient.cn.ibm.com.59098 > testServer.cn.ibm.com.telnet: S 1182666808:1182666808(0) ...3 10:18:55.801778 IP testServer.cn.ibm.com.telnet > testClient.cn.ibm.com.59098: S 3333341833:3333341833(0) ack 1182666809 ...4 10:18:55.801799 IP testClient.cn.ibm.com.59098 > testServer.cn.ibm.com.telnet: . ack 1 ...5 ...6 10:19:00.084662 IP testServer.cn.ibm.com.telnet > testClient.cn.ibm.com.59098: P 1010:1032(22) ack 87 ...7 10:19:00.084678 IP testClient.cn.ibm.com.59098 > testServer.cn.ibm.com.telnet: . ack 1032 ... |
从清单 5 中可以看出,在该连接处于非活跃状态的时间达到 tcp_keepidle 设定的 2 小时时,服务器主机发出了第一个连接保活的探测报文(清单 5 中的第 7 行)。紧接着,服务器主机就收到了来自 testClient 的连接复位报文(清单 5 中的第 8 行)。之后,服务器便关闭了该连接(可以通过 netstat –ni 来查看)。然而,从清单 6 的 tcpdump 数据可以看出, testClient 端并未发送任何报文。那么,是谁向 testServer 发送了复位报文呢?
为了查看上述复位报文的发送者,同样采用上述 tcpdump 命令再次捕获服务器端和防火墙 B 的报文信息(注意:通常需要捕获防火墙主机上网络数据的出口网卡和入口网卡数据),结果显示,防火墙 B 在收到来自 testServer 的第一个探测报文之后就立刻向 testServer 发送了一个复位报文。
上述分析说明,在连接传递完最后一个交互数据之后到服务器端发送第一个保活探测之间,该连接已经被防火墙 B 终止;在此之后,基于该连接的任何报文传递在试图穿过防火墙的时候均会被防火墙丢弃并发送复位报文。
针对上述 TCP 断连现象,有两种常用的解决方案可供选择:
方案 1、延长防火墙终止非活跃的 TCP 连接的时间。例如,针对上述案例,可以调节防火墙设置,将时间设置为大于服务器端设定的 2 小时。
方案 2、缩短服务器端的 TCP 连接保活时间。缩短该时间的目的是为了在连接被防火墙终止之前发送保活探测报文,既可以探测客户端状态,又可以使连接变为活跃状态。
对于第一种方案而言,延长 TCP 连接的保持时间可能会导致防火墙性能的降低,尤其是在维持大量长时间处于非活跃状态的连接的情况下更是如此;而对于第二种方案,如果缩短服务器端的 TCP 连接保活时间,意味着会增加网络中的数据报文数而占用额外的网络带宽。因此,两种方案各有利弊,需要依据不同的实际应用情况进行选择。
本文介绍了 TCP 连接的建立和保持的相关概念以及影响 TCP 连接保持的常见因素。给出了常见的类 Unix 系统上 TCP 连接保活探测的相关配置参数,并基于 AIX 借助 tcpdump 工具分析了一个实际的 TCP 断连的案例。最后,针对 TCP 断连的情况给出了两种可行的解决方案。
联系客服