9.1 如何设计断线自动重连机制
# 9.1.1 断线自动重连应用场景和逻辑设计
在有连接依赖关系的服务与服务之间,或客户端与服务器之间,无论是出于方便使用、降低运维成本、提高通信效率(服务与服务之间),还是优化用户体验(客户端与服务器之间)自动重连功能通常是一个非常重要的功能。
自动重连功能一般用于如下场景:
- 情景一
对于一组服务之间,如果其中一些服务(主动连接方,下文以 A 代称)需要与另外一些服务(被连接方,下文以 B 代称)建立 TCP 长连接,如果 A 没有自动连接 B 的功能,那么在部署或者测试这些服务的时候,必须先启动 B,再启动 A,因为一旦先启动 A,A 此时去尝试连接 B(由于 B 还没有启动)会失败,之后 A 再也不会去连接 B了(即使随后 B 被启动了),从而导致整个系统不能正常工作。
- 情景二
即使部署或测试的时候,先启动了 B,再启动 A,A 与 B 之间的连接在运行期间内,可能由于网络波动等原因导致 A 与 B 之间连接断开,之后整个系统也不能再正常工作了。
- 情景三
如果我们想升级 B,更新完程序后,重启 B,也必须重启 A。如果这种依赖链比较长(例如 A 连接 B,B 连接 C,C 连接 D,D 连接 E,等等),那么更新某个程序的效率会变得很低,更新成本非常高。
- 情景四
对于客户端软件来说,如果因为用户的网络短暂故障导致客户端与服务器失去连接,等网络恢复后,较好的用户体验是客户端能检测到用户网络变化后,自动与服务器重连,以便用户能及时收到最新的消息。
以上四个情景说明了断线自动重连功能的重要性,那如何去设计好的断线重连机制呢?
重连本身的功能开发很简单,其实就是调用 socket 函数 connect 函数,不断去“重试”。这里的“重试”我使用了双引号,是为了说明重试的技巧非常有讲究:
对于服务器端程序,例如 A 连接 B,如果连接不上,整个系统将无法工作,那么我们开发 A 服务时,重连的逻辑可以很简单,即 A 一旦发现与 B 断开了连接,就立即尝试与 B 重新连接,如果连接不上,隔一段时间再重试(一般设置为 3 秒或 5 秒即可),一直到连接成功为止。当然,期间可以发送报警邮件或者输出错误日志,让开发或者运维人员尽快干预,以便尽快解决问题。
对于客户端软件,以上做法也是可以的,但是不是最优的。客户端所处的网络环境比服务器程序所处的网络环境一般要恶劣的多,等间隔的定时去重连,一般作用不大(例如用户拔掉了网线)。因此,对于客户端软件,当出现断线会尝试去重连,如果连接不上,会隔一个比前一次时间更长的时间间隔去重连,例如这个时间间隔可以是 2 秒、4 秒、8 秒、16秒等等。但是,这样也存在一个问题,随着重连次数的变多,重连的时间间隔会越来越大(当然,你也可以设置一个最大重连时间间隔,之后恢复到之前较小的时间间隔)。如果网络此时已经恢复(例如用户重新插上网线),我们的程序需要等待一个很长的时间间隔(如 16 秒)才能恢复连接,这同样用户体验不好。解决办法是,如果网络发生波动,我们的程序应该检测网络状态,如果网络状态恢复正常此时应该立即进行一次重连,而不是一成不变地按照设置的时间间隔去重连。
操作系统提供了检测网络状态变化的 API 函数,例如对于 Windows 可以使用 IsNetworkAlive() 函数去检测,对于 Android,网络变化时会发送消息类型是 WifiManager.NETWORK_STATE_CHANGED_ACTION 的广播通知。
另外,还需要注意的是,如果客户端网络断开,应该在界面某个地方显式地告诉用户当前连接状态,并告诉用户当前正在进行断线重连,且应该有一个可以让用户放弃断线重连或者立即进行一次断线重连的功能。
综上所述,归纳一下:对于服务器程序之间的重连可以设计成等时间间隔的定时重连,对于客户端程序要结合依次放大重连时间间隔、网络状态变化立即重连或用户主动发起重连这三个因素来设计。
# 9.1.2 不需要重连的情形
不需要重连一般有以下情形:
用户使用客户端主动放弃重连;
因为一些业务上的规定,禁止客户端重连;
举个例子,如果某个系统同一时刻同一个账户只允许登录一个,某个账户在机器 A 上登录,此时接着又在机器 B 上登录,此时 A 将被服务器踢下线,那么此时 A 客户端的逻辑就应该禁止自动重连。
# 9.1.3 技术上的断线重连和业务上的断线重连
这里说的技术上的重连,指的是调用 connect 函数连接,在实际开发中,大多数系统光有技术上的重连成功(即 connect 连接成功)是没有任何意义的,网络连接成功以后,接下来还得再次向服务器发送账号验证信息等等(如登录数据包),只有这些数据验签成功后,才能算是真正的重连成功,这里说的发送账号验证信息并验签成功就是业务上的重连成功。复杂的系统可能会需要连续好几道验签流程。因此,我们在设计断线重连机制的时候,不仅要考虑技术上的重连,还要考虑业务上的重连。只有完整地包含这两个流程,才算是完备的断线自动重连功能。
本节介绍的知识点主要是思路性的内容,一旦搞清楚了思路,技术上实现起来并不会存在什么困难,因此本节没有给出具体的代码示例。