粗略探讨一下tcp四次挥手

此前我们已经简单介绍了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+1x+1,此时服务器的状态为LCLOSE WAIT。当客户端A接收到报文段后,客户端的状态为FIN WAIT 2

  • 当服务器B的数据也发送完毕后,会给客户端发送一个报文段,并且将FINACK都置为1,并且生成一个随机数z作为seq,且将ack_seq的值置为第一次客户端A向服务器B发送报文段中的seq+1,即x+1,此时服务器的状态是LAST ACK

  • 当客户端A收到服务器B的信息后,并回复给服务器B一个报文段,并将ACK置为1,seqx+1ack_seqz+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换为了$NFNFawk中表示最后一列。

放到服务器上,执行结果为:

通过上述可以看到:

  • 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,展示完整的报文。

抓取结果为:

上述结果中,tcpdumpACK会标记为.,将FIN会标记为F,但是实际上,通过上述截图可以看到,挥手只有3条记录,上述理论知识不是4条么? 这是因为服务器在发送第二次和第三次报文段的时候给合并了,所以我们才发现只有3条记录。

根本原因是因为TCP的延迟确认机制引起的。它并不会来一个报文段立即发送一个报文段,而是会等待一段时间(和默认是100ms),若还有报文段,则将其封装在一个报文段中发送来。第二次和第三次报文段就是这样的。

在linux中如何关闭TCP延迟确认机制

如果能否将其延迟确认机制给关闭呢?在linux中是可以的,仅需将TCP_QUICKACK设置为1即可,这里简单写一个web服务器,代码如下:

socket.TCP_QUICKACKlinux中特有的,其他平台未进行测试。

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启动一个ipv4tcp服务,绑定到所有网卡上,对外的端口为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)就可以关闭延迟确认机制。

posted @ 2023-06-08 18:30  pdudos  阅读(0)  评论(0编辑  收藏  举报  来源