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数据包。
前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,暂时我也不知道为什么,后面清楚的时候,进行更新。