tcp连接在关闭的时候,需要进行4次传输过程,图片如下:

tcpdump——分析tcp关闭4次过程-编程知识网

首先是client发送FIN到Server,通常是client调用了close,client进入FIN_WAIT1状态。

Server的tcp在收到FIN之后,立刻返回ACK给客户端,同时Server进入CLOSE_WAIT状态。

client在收到server的ack之后,进入FIN_WAIT2状态。

server在发送ACK之后,通常主动调用close来关闭与客户端连接的socket,这个是时候发送FIN给客户端,server进入LAST_ACK状态。

客户端收到server的FIN之后,进入TIME_WAIT, 同事返回ACK,server收到ACK后进入CLOSED状态

现在来写程序验证一下这个过程,同时用tcpdump进行抓包分析。

1、client主动关闭socket,server不关闭对应的socket。

server程序:

addr = ('127.0.0.1', 9988)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(addr)
server.listen(10)

while True:
        connection, address = server.accept()
        print 'connection ip:', address

client程序:

addr = ('127.0.0.1', 9988)
client = socket(AF_INET, SOCK_STREAM)
client.connect(addr)
client.send('hello server123456789')
client.close()

先运行server,再运行client,用tcpdump抓包(只贴出tcp关闭部分)。

16:41:03.411439 IP localhost.40464 > localhost.9988: Flags [F.], seq 4077955702, ack 4081074301, win 513, options [nop,nop,TS val 19893050 ecr 19893050], length 0

16:41:03.450137 IP localhost.9988 > localhost.40464: Flags [.], ack 4077955703, win 512, options [nop,nop,TS val 19893060 ecr 19893050], length 0

从抓获的数据包来看,client给server发送了FIN,并且server端返回了ACK,然后就没有其他的数据包了。

用netstat -an | grep 9988查看现在的套接字信息是这样的:

tcp        0      0 127.0.0.1:9988          0.0.0.0:*               LISTEN     
tcp       22      0 127.0.0.1:9988          127.0.0.1:40464         CLOSE_WAIT 
tcp        0      0 127.0.0.1:40464         127.0.0.1:9988          FIN_WAIT2  

客户端的端口处于FIN_WAIT2, server与client连接的端口处于CLOSE_WAIT状态。这个是符合一开始的状态图的。

由于server并没有主动关闭本地的socket,所以没有发送FIN给client,client一直等待server的FIN,所以一直处在FIN_WAIT2.

2、server在客户端断开连接后也调用close

server程序:

addr = ('127.0.0.1', 9988)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(addr)
server.listen(10)

while True:
        connection, address = server.accept()
        print 'connection ip:', address
        while True:
                readBuf = connection.recv(1024)
                if len(readBuf) == 0:
                        connection.close()
                        print 'connection close'
                        break
                print readBuf

client程序还是使用之前的。

server需要在client关闭的时候,也调用close,那么有一个问题就是server如何知道client调用了close。在tcp协议里,如果client关闭了,那么server在read的时候会返回长度为0的数据。

server程序为了测试,只是很简单的对一个客户端sock进行read,如果read到的buf长度是0,就认为client关闭连接了,然后server也关闭对应的sock。

运行程序,抓包结果如下:

17:06:10.932798 IP localhost.47835 > localhost.9988: Flags [F.], seq 1945810449, ack 1949135243, win 513, options [nop,nop,TS val 20269930 ecr 20269930], length 0
0x0000: 4500 0034 fd7c 4000 4006 3f45 7f00 0001 E..4.|@.@.?E….
0x0010: 7f00 0001 badb 2704 73fa b611 742d 718b ……’.s…t-q.
0x0020: 8011 0201 fe28 0000 0101 080a 0135 4b6a …..(…….5Kj
0x0030: 0135 4b6a .5Kj

17:06:10.932885 IP localhost.9988 > localhost.47835: Flags [F.], seq 1949135243, ack 1945810450, win 512, options [nop,nop,TS val 20269930 ecr 20269930], length 0
0x0000: 4500 0034 3ae1 4000 4006 01e1 7f00 0001 E..4:.@.@…….
0x0010: 7f00 0001 2704 badb 742d 718b 73fa b612 ….’…t-q.s…
0x0020: 8011 0200 fe28 0000 0101 080a 0135 4b6a …..(…….5Kj
0x0030: 0135 4b6a .5Kj

17:06:10.932923 IP localhost.47835 > localhost.9988: Flags [.], ack 1949135244, win 513, options [nop,nop,TS val 20269930 ecr 20269930], length 0
0x0000: 4500 0034 fd7d 4000 4006 3f44 7f00 0001 E..4.}@.@.?D….
0x0010: 7f00 0001 badb 2704 73fa b612 742d 718c ……’.s…t-q.
0x0020: 8010 0201 fe28 0000 0101 080a 0135 4b6a …..(…….5Kj
0x0030: 0135 4b6a .5Kj

分析抓获的数据包我们能看到,服务器返回了[F.],然后客户端就ACK了。

通常来说tcp关闭连接是4次过程,这里我们只看到了3次,是因为server返回给client的ACK和FIN合并成一个分组发给client了。

为了验证是这样的情况,我们分析一下tcp数据包头。我们知道IP数据包头是20个字节,后面开始是tcp数据包头,也就是说从2704 badb开始是tcp数据包。

tcpdump——分析tcp关闭4次过程-编程知识网

前12个字节是两边的端口号和数据包序列号,最重要的是第13个字节,也就是tcpdump抓取的“8011”,转换成二进制就是“1000 0000 0001 0001”

后面6个字节也就是”010001″ 对应”urg ack psh rst syn fin”,也就是说ACK=1,FIN=1,可以说明这个数据包是将ACK和FIN合并发送的。

 

为了清晰的看见tcp关闭的4次过程,我们修改一下server程序,在connection.close()调用之前,sleep 1秒。

这样的情况下,在进行抓包,我们能看到完成的4次分组数据包。

17:53:07.149572 IP localhost.40583 > localhost.9988: Flags [F.], seq 3147749708, ack 3163302012, win 513, options [nop,nop,TS val 20973985 ecr 20973983], length 0
    0x0000:  4500 0034 d0d6 4000 4006 6beb 7f00 0001  E..4..@.@.k.....
    0x0010:  7f00 0001 9e87 2704 bb9e d94c bc8c 287c  ......'....L..(|
    0x0020:  8011 0201 fe28 0000 0101 080a 0140 09a1  .....(.......@..
    0x0030:  0140 099f                                .@..
17:53:07.153457 IP localhost.9988 > localhost.40583: Flags [.], ack 3147749709, win 512, options [nop,nop,TS val 20973986 ecr 20973985], length 0
    0x0000:  4500 0034 21e3 4000 4006 1adf 7f00 0001  E..4!.@.@.......
    0x0010:  7f00 0001 2704 9e87 bc8c 287c bb9e d94d  ....'.....(|...M
    0x0020:  8010 0200 fe28 0000 0101 080a 0140 09a2  .....(.......@..
    0x0030:  0140 09a1                                .@..
17:53:08.151568 IP localhost.9988 > localhost.40583: Flags [F.], seq 3163302012, ack 3147749709, win 512, options [nop,nop,TS val 20974235 ecr 20973985], length 0
    0x0000:  4500 0034 21e4 4000 4006 1ade 7f00 0001  E..4!.@.@.......
    0x0010:  7f00 0001 2704 9e87 bc8c 287c bb9e d94d  ....'.....(|...M
    0x0020:  8011 0200 fe28 0000 0101 080a 0140 0a9b  .....(.......@..
    0x0030:  0140 09a1                                .@..
17:53:08.152182 IP localhost.40583 > localhost.9988: Flags [.], ack 3163302013, win 513, options [nop,nop,TS val 20974235 ecr 20974235], length 0
    0x0000:  4500 0034 0000 4000 4006 3cc2 7f00 0001  E..4..@.@.<.....
    0x0010:  7f00 0001 9e87 2704 bb9e d94d bc8c 287d  ......'....M..(}
    0x0020:  8010 0201 1f82 0000 0101 080a 0140 0a9b  .............@..
    0x0030:  0140 0a9b                                .@..

不过为什么server在发送FIN的时候还是发送了一个ACK,暂时我也不知道为什么,后面清楚的时候,进行更新。