【ESP8266学习记录-4】TCP连接之为什么需要心跳包?-基于MicroPython
“嘿,姑娘,我是真系钟意你!”——Heartbeat
前面一篇文章内简单的讲了一下TCP连接,在实际的“瞎玩”过程中,有许多需要考虑的现实因素。 其中一个重要的条件时:保证设备在线 只有硬件设备(以下简称客户端)在线的情况下才能对其控制,客户端的信息才可以上传到服务端。本文主要从以下两种情况分析设备的在线情况,再介绍心跳包存在的意义。
- 客户端主动断开TCP连接
- 客户端非主动断开TCP连接
1.服务端主动断开TCP连接 很多时候一个TCP的连接从建立开始到结束的周期并不长,大概10-60S内就完成了这个业务流程,另一方面因为服务端要同时服务数以万计的客户端,因此在需要的时候再次建立连接比建立一个长连接更划算。在服务端断开连接之后客户端的会知道连接已经断开了,等待下次有需求的时候再次请求连接。 这种断开方式可以合理的利用服务端,使服务端处在一个可控的范围内,也避免了服务端因业务量大出现宕机的情况。 2.客户端主动断开TCP连接 在主动断开连接的情况中,博主更推荐使用客户端主动断开连接,因为大多数情况数据的请求都在客户端(具体应用具体分析)。一般来讲客户端发起断开连接请求,如:
s.close()#其中s为先前已经建立的连接
发起请求之后经过四次握手断开,向服务端发送一个fin包,服务端这时候才“知道”客户端已经断开连接了。服务端可能会触发某些事件(本文中的服务端主动断开TCP连接依然会触发!),如:
public static function onClose($client_id) { GateWay::sendToAll(“client[$client_id] logout\n”); } //此处以GatewayWorker为例
在触发上述时间后,会执行相应的业务代码。 3.路由器/网关/防火墙/等断开TCP连接 在本次折腾下,会出现因为拔掉USB而客户端会出现掉电的情况,重启路由器出现断网的情况,或者你家的猫嫉妒你看其他小猫咪所以把线咬断了…… 且两个主机之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等。因此,两个主机之间 TCP 连接的保持同样会受到中间节点的影响。总而言之就是你建立一个TCP链接就别让它“闲着”。一般来讲TCP连接有一个保活机制(Keepalive):
当一个 TCP 连接建立之后,启用 TCP Keepalive 的一端便会启动一个计时器,当这个计时器数值到达 0 之后(也就是经过tcp_keep-alive_time时间后,这个参数之后会讲到),一个 TCP 探测包便会被发出。这个 TCP 探测包是一个纯 ACK 包(规范建议,不应该包含任何数据,但也可以包含1个无意义的字节,比如0x0。),其 Seq号 与上一个包是重复的,所以其实探测保活报文不在窗口控制范围内。 如果一个给定的连接在两小时内(默认时长)没有任何的动作,则服务器就向客户发一个探测报文段,客户主机必须处于以下4个状态之一: 1. 客户主机依然正常运行,并从服务器可达。客户的TCP响应正常,而服务器也知道对方是正常的,服务器在两小时后将保活定时器复位。 2. 客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的TCP都没有响应。服务端将不能收到对探测的响应,并在75秒后超时。服务器总共发送10个这样的探测 ,每个间隔75秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。 3. 客户主机崩溃并已经重新启动。服务器将收到一个对其保活探测的响应,这个响应是一个复位,使得服务器终止这个连接。 4. 客户机正常运行,但是服务器不可达,这种情况与2类似,TCP能发现的就是没有收到探测的响应。 对于linux内核来说,应用程序若想使用TCP Keepalive,需要设置SO_KEEPALIVE套接字选项才能生效。 有三个重要的参数: 1. tcp_keepalive_time,在TCP保活打开的情况下,最后一次数据交换到TCP发送第一个保活探测包的间隔,即允许的持续空闲时长,或者说每次正常发送心跳的周期,默认值为7200s(2h)。 2. tcp_keepalive_probes 在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包次数,默认值为9(次) 3. tcp_keepalive_intvl,在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包的发送频率,默认值为75s。 原文链接:https://blog.csdn.net/chrisnotfound/article/details/80111559
虽然有保活机制但是默认两个小时为一个心跳周期,在很多情况下并不能”即时”判断客户端是否在线。因此心跳包在此条件下存在的意义就是使长连接保持活跃,告诉通信的各个环节“我还在,别断开我”,这是心跳包存在的第一个意义,本例中的代码可以适用MicroPython的定时器中断完成,代码如下:
from machine import Timer tim = Timer(-) def heart():
s.send(‘connection’)
tim.init(period=5000, mode=Timer.PERIODIC, callback=heart) “””其中5000为循环周期为5S,Timer.PERIODIC为持续循环,相应的还有仅执行一次的ONE_SHOT”””
4.非主动断开TCP连接 大多数业务的流程为一个服务端,两个客户端,一个客户端为受控硬件,另外一个客户端为Android/ios/PC 控制端。硬件客户端要发送心跳包给服务器表明其在线情况,服务器要将这种“情况”发送到Android/ios/PC 控制端,因此心跳包存在的第二个意义就在此处。 基本的流程为: 具体的可参考代码如下:
from machine import Timer tim = Timer(-) def heart():
s.send(‘connection’)
while True:
data = s.recv(50).decode(‘utf8’)
if data != “back_msg”:
def connect_again()”””再次连接的函数”””
tim.init(5000, mode=Timer.PERIODIC, callback=heart)
总结: 心跳包它像心跳一样每隔固定时间发一次,这样即通过心跳检测请求维持了连接(避免连接因长时间不活跃而被网关防火墙关闭),也能让服务端比较及时的知道客户端是否异常掉线。 歪克士 2019.8.28