还技术债:优雅地断开TCP套接字

2025/02/24 » »linux 

最近一段时间在研究TCP/IP底层,接触到了以前不了解的知识,联想到由于以前网络基础薄弱,可能影响到了两个项目的可靠性。

两个项目:一个类似路由,下位机上线都要从这里获取下一步通讯地址;一个是干电池驱动的采集器,因为续航要求,需要休眠x小时后通讯一次,通讯完成立马断开连接继续休眠。

项目中的不足之处

主要记录TCP短连接上位机主动断开下位机连接(下位机情况比较复杂,可能因为网络抖动、欠费或者卡商问题,导致下位机无法及时告知上位机连接状态)。

第一个项目是2019年使用golang所写,大致通讯流程如下:

  1. 下位机与上位机建立连接
  2. 下位机 -> #我是离谱,告诉我要去哪# -> 上位机
  3. 下位机 <- #离谱,你应该回家# <- 上位机
  4. 上位机主动踢掉下位机

33

通讯流程如上图所示。上位机回复之后,会立马使用tcpPacket.con.close()踢掉设备。发送缓冲区可能还没发送完成就被清空,下位机可能还没收到消息,就断开连接了。发送消息后sleep一下再close也不是很好的解决方案,只是减少了概率。


第二个项目是2021年使用c#所写,大致通讯流程如下:

  1. 下位机与上位机建立连接
  2. 下位机 -> #传感器1温度,传感器2温度,传感器3温度..# -> 上位机
  3. 下位机 <- #收到# <- 上位机
  4. 下位机断开连接,进入休眠

05

通讯流程如上图所示。我看了下代码,下位机断开后,会依据条件执行networkStream.Close();tcpClient.Close();。虽然是强制断开,但没有太大影响,因为下位机是在收到上位机消息后才断开连接,上位机没有后续消息需要回复,不会造成消息丢失,只不过断开过程比较生硬。如果是上位机回复完成后强制断开下位机,才会出现消息丢失、下位机收不到消息的概率。

TCP套接字硬断开和软断开底层原理

使用抓包工具抓取以下示例:

1、下位机与上位机建立TCP套接字连接,下位机发送数据、上位机接收后立即硬断开连接;

34_2

2、下位机与上位机建立TCP套接字连接,下位机发送数据、上位机回复后立即硬断开连接;

WX20241217-134705

经过抓包可以看到,两种情况的断开是不一样的:

  • (示例一)上位机接收后硬断开会一直处在TIME_WAIT并停留相当长时间(默认为60s,cat /proc/sys/net/ipv4/tcp_fin_timeout)才变为CLOSED
  • (示例二)上位机回复后硬断开,会导致连接从FIN_WAIT_2直接变为RST重置并CLOSED连接。

TCP套接字硬断开对服务器的影响

如果大量连接处于TIME_WAIT,会占用服务器大量端口和系统资源,所以需要及时、正确关闭连接。

代码优化

代码优化前,close()硬断开连接,TCP两次挥手:

项目bug

代码优化后,先shutdown()再close()。

WX20250218-114830

优化后变为四次挥手:

项目bug修复

监控上位机网络通信

代码优化完成后,突然有想监控TCP详细通讯的冲动。找了uptime kuma、ServerBee、HertzBeat等都不满足需求,Prometheus太重。阿里云有服务可用监控,但有两个缺点:
1)、收费(但是性价比非常高,忙时1分钟监控一次,一天只有1块多);
2)、无法查看TCP时序;

阿里云服务可用监控主界面如下:

主界面

于是用了大概三周的晚上写了个自用版:

2025-02-24 23 23.22.48
2025-02-24 23.22.35

点击曲线图任意节点,查看通讯详情、TCP时序:

2025-02-24 23 23.23.13

可以展开查看Hex/AscII数据包:

2025-02-24 23 23.23.57

再加上邮箱告警,功能基本完成。

×