金亚洲技术笔记
还技术债:优雅地断开TCP套接字
最近一段时间在研究TCP/IP底层,接触到了以前不了解的知识,联想到由于以前网络基础薄弱,可能影响到了两个项目的可靠性。
两个项目:一个类似路由,下位机上线都要从这里获取下一步通讯地址;一个是干电池驱动的采集器,因为续航要求,需要休眠x小时后通讯一次,通讯完成立马断开连接继续休眠。
项目中的不足之处
主要记录TCP短连接
和上位机主动断开下位机连接
(下位机情况比较复杂,可能因为网络抖动、欠费或者卡商问题,导致下位机无法及时告知上位机连接状态)。
第一个项目是2019年使用golang所写,大致通讯流程如下:
- 下位机与上位机建立连接
- 下位机 -> #我是离谱,告诉我要去哪# -> 上位机
- 下位机 <- #离谱,你应该回家# <- 上位机
- 上位机主动踢掉下位机
通讯流程如上图所示。上位机回复之后,会立马使用tcpPacket.con.close()
踢掉设备。发送缓冲区可能还没发送完成就被清空,下位机可能还没收到消息,就断开连接了。发送消息后sleep一下再close也不是很好的解决方案,只是减少了概率。
第二个项目是2021年使用c#所写,大致通讯流程如下:
- 下位机与上位机建立连接
- 下位机 -> #传感器1温度,传感器2温度,传感器3温度..# -> 上位机
- 下位机 <- #收到# <- 上位机
- 下位机断开连接,进入休眠
通讯流程如上图所示。我看了下代码,下位机断开后,会依据条件执行networkStream.Close();
和tcpClient.Close();
。虽然是强制断开,但没有太大影响,因为下位机是在收到上位机消息后才断开连接,上位机没有后续消息需要回复,不会造成消息丢失,只不过断开过程比较生硬。如果是上位机回复完成后强制断开下位机,才会出现消息丢失、下位机收不到消息的概率。
TCP套接字硬断开和软断开底层原理
使用抓包工具抓取以下示例:
1、下位机与上位机建立TCP套接字连接,下位机发送数据、上位机接收后
立即硬断开连接;
2、下位机与上位机建立TCP套接字连接,下位机发送数据、上位机回复后
立即硬断开连接;
经过抓包可以看到,两种情况的断开是不一样的:
- (示例一)上位机
接收后
硬断开会一直处在TIME_WAIT
并停留相当长时间(默认为60s,cat /proc/sys/net/ipv4/tcp_fin_timeout
)才变为CLOSED
; - (示例二)上位机
回复后
硬断开,会导致连接从FIN_WAIT_2
直接变为RST
重置并CLOSED
连接。
TCP套接字硬断开对服务器的影响
如果大量连接处于TIME_WAIT
,会占用服务器大量端口和系统资源,所以需要及时、正确关闭连接。
代码优化
代码优化前,close()硬断开连接,TCP两次挥手:
代码优化后,先shutdown()再close()。
优化后变为四次挥手:
监控上位机网络通信
代码优化完成后,突然有想监控TCP详细通讯的冲动。找了uptime kuma、ServerBee、HertzBeat等都不满足需求,Prometheus太重。阿里云有服务可用监控,但有两个缺点:
1)、收费(但是性价比非常高,忙时1分钟监控一次,一天只有1块多);
2)、无法查看TCP时序;
阿里云服务可用监控主界面如下:
于是用了大概三周的晚上写了个自用版:
点击曲线图任意节点,查看通讯详情、TCP时序:
可以展开查看Hex/AscII数据包:
再加上邮箱告警,功能基本完成。
捐赠
如果您觉得博客对您有所帮助,不妨赏博主一杯☕。