CppGuide社区 CppGuide社区
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
  • C++语言面试问题集锦
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • 🔥C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 🔥从零用C语言写一个Redis
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 🔥使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
  • C++语言面试问题集锦
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • 🔥C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 🔥从零用C语言写一个Redis
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 🔥使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
  • 第1章高频C++11重难点知识解析

  • 第2章Linux GDB高级调试指南

  • 第3章C++多线程编程从入门到进阶

  • 第4章C++网络编程重难点解析

  • 第5章网络通信故障排查常用命令

  • 第6章高性能网络通信协议设计精要

  • 第7章高性能服务结构设计

  • 第8章Redis 网络通信模块源码分析

  • 第9章后端服务重要模块设计探索

    • 9.1 如何设计断线自动重连机制
      • 9.2 保活机制与心跳包
      • 9.3 日志模块的设计
      • 9.4 错误码系统的设计
      • 9.5 监控端口
    • C++后端开发进阶
    • 第9章后端服务重要模块设计探索
    zhangxf
    2023-04-05
    目录

    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 连接成功)是没有任何意义的,网络连接成功以后,接下来还得再次向服务器发送账号验证信息等等(如登录数据包),只有这些数据验签成功后,才能算是真正的重连成功,这里说的发送账号验证信息并验签成功就是业务上的重连成功。复杂的系统可能会需要连续好几道验签流程。因此,我们在设计断线重连机制的时候,不仅要考虑技术上的重连,还要考虑业务上的重连。只有完整地包含这两个流程,才算是完备的断线自动重连功能。

    本节介绍的知识点主要是思路性的内容,一旦搞清楚了思路,技术上实现起来并不会存在什么困难,因此本节没有给出具体的代码示例。

    8.5 总结
    9.2 保活机制与心跳包

    ← 8.5 总结 9.2 保活机制与心跳包→

    最近更新
    01
    第二章 关键字static及其不同用法
    03-27
    02
    第一章 auto与类型推导
    03-27
    03
    C++语言面试问题集锦 目录与说明
    03-27
    更多文章>
    Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式