此前我们已经简单介绍了tcp
报文段结构、3次握手的流程,以及 使用tcpdump
来抓包查看三次握手流程,最后探讨了一下 linux
中对于全连接 和 半连接 的解释和调优,如果还没有看过上一篇文章内容,建议先看下该>篇文章,以便做到承上启下:
网络|学习一下tcp三次握手:juejin.cn/post/724186…
tcp如何通过四次挥手释放连接
假设客户端是A、服务器是B,当客户端发送完全部数据后,要断开连接的时候,需要执行以下四步操作:
- 客户端A向服务器B发送一个报文段,将
FIN
置为1
,并且取一个随机数x
作为seq
发送,此时客户端状态为FIN WAIT 1
。 - 当服务器B接收到后客户端发送来的
FIN
信息后,向客户端A发送一个报文段,将ACK
置为1
,并且生成一个随机数y
作为seq
发送、ack_seq
值为客户端报文中的seq+1
即x+1
,此时服务器的状态为LCLOSE WAIT
。当客户端A
接收到报文段后,客户端的状态为FIN WAIT 2
。 - 当服务器B的数据也发送完毕后,会给客户端发送一个报文段,并且将
FIN
和ACK
都置为1
,并且生成一个随机数z
作为seq
,且将ack_seq
的值置为第一次客户端A
向服务器B
发送报文段中的seq+1
,即x+1
,此时服务器的状态是LAST ACK
。 - 当客户端
A
收到服务器B
的信息后,并回复给服务器B
一个报文段,并将ACK
置为1,seq
为x+1
,ack_seq
为z+1
,此时客户端的状态是TIME WAIT
,将等待一定时间,此状态将会变化为CLOSE
。 - 当服务器
B
收到客户端A
的报文段后,将断开连接,此时服务器状态置为CLOSE
。
注意,上述只是假设客户端先发送请求释放连接的报文段,其实服务器也是可以提出挥手请求的。
上述过程可以图示如下:
如何使用awk统计机器网络状态
如何查看网络状态呢?上篇文章中国,我们介绍的使用ss
来统计,命令如下:
ss -a | grep ^tcp | awk '{status[$2]=status[$2]+1} END{for (i in status) {print i,status[i]}}'
执行的结果为:
还有一种方法,是使用netstat
命令,只不过在awk
中要换一下计算的列而已,命令如下:
netstat -a | grep ^tcp | awk '{status[$NF]=status[$NF]+1} END{for (i in status) {print i,status[i]}}'
上述代码中,我们将$2
换为了$NF
,NF
在awk
中表示最后一列。
放到服务器上,执行结果为:
通过上述可以看到:
LISTEN
:服务器监听了7个套接字。ESTABLISHED
: 有21个是已经处于连上服务器的。FIN_WAIT2
:表示释放连接对方已经收到,等待对方再次发送FIN
报文段。TIME_WAIT
:已经释放,等待超时回收。
如果有上述工具,我们就能轻松获取服务器状态信息,比如nginx
服务器的TIME WAIT
过高,如果上面有反向代理,可以考虑是否有增加长链接,从而逐步优化,使其服务器达到最佳状态。
使用tcpdump抓取一个会话包
如上一篇所述,还是使用nginx
搭建一个简单的页面,使用tcpdump
进行抓包,抓包命令还是还是使用上一篇中的tcpdump
:
tcpdump port 80 -S -s 0 -A -i lo
上述命令表示抓取本地回环地址lo
的数据包,抓取的端口为80
,展示完整的报文。
抓取结果为:
上述结果中,tcpdump
将ACK
会标记为.
,将FIN
会标记为F
,但是实际上,通过上述截图可以看到,挥手只有3条记录,上述理论知识不是4条么? 这是因为服务器在发送第二次和第三次报文段的时候给合并了,所以我们才发现只有3条记录。
根本原因其实原因是因为TCP
的延迟确认机制引起的。它并不会来一个报文段立即发送一个报文段,而是会等待一段时间,若还有报文段,则将其封装在一个报文段中发送来。第二次和第三次报文段就是这样的。
在linux中如何关闭TCP延迟确认机制
如果能否将其延迟确认机制给关闭呢?在linux
中是可以的,仅需将TCP_QUICKACK
设置为1
即可,这里简单写一个web
服务器,代码如下:
socket.TCP_QUICKACK
是linux
中特有的,其他平台未进行测试。
import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.bind(("0.0.0.0",80)) s.setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1) s.listen() client , addr = s.accept() data = client.recv(1024) print(data) client.send(b"HTTP/1.1 200 OK\r\n" b"Content-Type: text/html\r\n" b"Content-Length: 11\r\n" b"\r\n" b"hello pdudo") client.close()
如上代码,使用socket
启动一个ipv4
的tcp
服务,绑定到所有网卡上,对外的端口为80
,在bind
之后,将其TCP_QUICKACK
设置为1,表示关闭延迟确认机制,而后使用listen
开始监听服务,当客户端数据来之后,不管是什么内容,都返回一个http
响应报文,报文主体为hello pdudo
。
接着将nginx
服务给关闭,将该python
服务器给开起来,使用tcpdump
继续监听,使用curl
访问,继而查看报文数据。
这里测试多次发现,如果使用本地回环地址访问(127.0.0.1) ,还是会启用延迟确认机制,需要通过eth1
网卡访问代码才能生效。
上述结果,可以看到,挥手报文已经是4次了。
总结
因为有上篇文章的铺垫,这篇文章,直接开门见山介绍4次挥手,在具体抓包时,因为有延迟确认机制,所以抓包大多数都是3次,因为第二次和第三次报文段给合并发送了,所以想展示完整的四次挥手,需要将延迟确认机制给关闭,在linux
操作系统中,我们使用python socket
直接调用setsockopt(socket.IPPROTO_TCP, socket.TCP_QUICKACK, 1)
就可以关闭延迟确认机制。