3.3 TCP连接的建立和关闭

本节我们讨论建立和关闭TCP连接的过程。

3.3.1 使用tcpdump观察TCP连接的建立和关闭

首先从ernest-laptop上执行telnet命令登录Kongming20的80端口,然后抓取这一过程中客户端和服务器交换的TCP报文段。具体操作过程如下:

$ sudo tcpdump -i eth0 –nt '(src 192.168.1.109 and dst 192.168.1.108) or (src 192.168.1.108 and dst 192.168.1.109)' 
$ telnet 192.168.1.109 80
Trying 192.168.1.109...
Connected to 192.168.1.109.
Escape character is '^]'.
^](回车)  # 输入ctrl+]并回车

telnet> quit(回车)
Connection closed.

当执行telnet命令并在两台通信主机之间建立TCP连接后(telnet输出“Connected to 192.168.1.109”),输入Ctrl+]以调出telnet程序的命令提示符,然后在telnet命令提示符后输入quit以退出telnet客户端程序,从而结束TCP连接。整个过程中(从连接建立到结束),tcpdump输出的内容如代码清单3-2所示。

centos如何关闭tcp连接 linux关闭tcp端口_网络

因为整个过程并没有发生应用层数据的交换,所以TCP报文段的数据部分的长度(length)总是0。为了更清楚地表示建立和关闭TCP连接的整个过程,我们将tcpdump输出的内容绘制成图3-6所示的时序图。

centos如何关闭tcp连接 linux关闭tcp端口_操作系统_02

第1个TCP报文段包含SYN标志,因此它是一个同步报文段,即ernest-laptop(客户端)向Kongming20(服务器)发起连接请求。同时,该同步报文段包含一个ISN值为535734930的序号。第2个TCP报文段也是同步报文段,表示Kongming20同意与ernest-laptop建立连接。同时它发送自己的ISN值为2159701207的序号,并对第1个同步报文段进行确认。确认值是535734931,即第1个同步报文段的序号值加1。前文说过,序号值是用来标识TCP数据流中的每一字节的。但同步报文段比较特殊,即使它并没有携带任何应用程序数据,它也要占用一个序号值。第3个TCP报文段是ernest-laptop对第2个同步报文段的确认。至此,TCP连接就建立起来了。建立TCP连接的这3个步骤被称为TCP三次握手。

从第3个TCP报文段开始,tcpdump输出的序号值和确认值都是相对初始ISN值的偏移。当然,我们可以开启tcpdump的-S选项来选择打印序号的绝对值。

后面4个TCP报文段是关闭连接的过程。第4个TCP报文段包含FIN标志,因此它是一个结束报文段,即ernest-laptop要求关闭连接。结束报文段和同步报文段一样,也要占用一个序号值。Kongming20用TCP报文段5来确认该结束报文段。紧接着Kongming20发送自己的结束报文段6,ernest-laptop则用TCP报文段7给予确认。实际上,仅用于确认目的的确认报文段5是可以省略的,因为结束报文段6也携带了该确认信息。确认报文段5是否出现在连接断开的过程中,取决于TCP的延迟确认特性。延迟确认将在后面讨论。

在连接的关闭过程中,因为ernest-laptop先发送结束报文段(telnet客户端程序主动退出),故称ernest-laptop执行主动关闭,而称Kongming20执行被动关闭。

一般而言,TCP连接是由客户端发起,并通过三次握手建立(特殊情况是所谓同时打开[1])的。TCP连接的关闭过程相对复杂一些。可能是客户端执行主动关闭,比如前面的例子;也可能是服务器执行主动关闭,比如服务器程序被中断而强制关闭连接;还可能是同时关闭(和同时打开一样,非常少见)。

3.3.2 半关闭状态

TCP连接是全双工的,所以它允许两个方向的数据传输被独立关闭。换言之,通信的一端可以发送结束报文段给对方,告诉它本端已经完成了数据的发送,但允许继续接收来自对方的数据,直到对方也发送结束报文段以关闭连接。TCP连接的这种状态称为半关闭(half close)状态,如图3-7所示。

centos如何关闭tcp连接 linux关闭tcp端口_网络_03

请注意,在图3-7中,服务器和客户端应用程序判断对方是否已经关闭连接的方法是:read系统调用返回0(收到结束报文段)。当然,Linux还提供其他检测连接是否被对方关闭的方法,这将在后续章节讨论。

socket网络编程接口通过shutdown函数提供了对半关闭的支持,我们将在后续章节讨论它。这里强调一下,虽然我们介绍了半关闭状态,但是使用半关闭的应用程序很少见。

3.3.3 连接超时

前面我们讨论的是很快建立连接的情况。如果客户端访问一个距离它很远的服务器,或者由于网络繁忙,导致服务器对于客户端发送出的同步报文段没有应答,此时客户端程序将产生什么样的行为呢?显然,对于提供可靠服务的TCP来说,它必然是先进行重连(可能执行多次),如果重连仍然无效,则通知应用程序连接超时。

为了观察连接超时,我们模拟一个繁忙的服务器环境,在ernest-laptop上执行下面的操作:

$ sudo iptables -F
$ sudo iptables -I INPUT -p tcp --syn -i eth0 -j DROP

iptable命令用于过滤数据包,这里我们利用它来丢弃所有接收到的连接请求(丢弃所有同步报文段,这样客户端就无法得到任何确认报文段)。

接下来从Kongming20上执行telnet命令登录到ernest-laptop,并用tcpdump抓取这个过程中双方交换的TCP报文段。具体操作如下:

$ sudo tcpdump -n -i eth0 port 23    #仅抓取telnet客户端和服务器交换的数据包
$ date; telnet 192.168.1.108; date    #在telnet命令前后都执行date命令,以计算超时时间
Mon Jun 11 21:23:35 CST 2012
Trying 192.168.1.108...
telnet: connect to address 192.168.1.108: Connection timed out
Mon Jun 11 21:24:38 CST 2012

从两次date命令的输出来看,Kongming20建立TCP连接的超时时间是63?s。本次tcpdump的输出如代码清单3-3所示。

centos如何关闭tcp连接 linux关闭tcp端口_centos如何关闭tcp连接_04

这次抓包我们保留了tcpdump输出的时间戳(不使用其-t选项),以便推理Linux的超时重连策略。

我们一共抓取到6个TCP报文段,它们都是同步报文段,并且具有相同的序号值,这说明后面5个同步报文段都是超时重连报文段。观察这些TCP报文段被发送的时间间隔,它们分别为1?s、2?s、4?s、8?s和16?s(由于定时器精度的问题,这些时间间隔都有一定偏差),可以推断最后一个TCP报文段的超时时间是32?s(63?s-16?s-8?s-4?s-2?s-1?s)。因此,TCP模块一共执行了5次重连操作,这是由/proc/sys/net/ipv4/tcp_syn_retries内核变量所定义的。每次重连的超时时间都增加一倍。在5次重连均失败的情况下,TCP模块放弃连接并通知应用程序。

在应用程序中,我们可以修改连接超时时间,具体方法将在本书后续章节中进行介绍。