本篇核心内容:实现带自动断线重连功能的 TCP 客户端。
断线重连是线上长连接程序高频刚需,大家日常打游戏时就能直观感受到:本地网络波动、服务器崩溃、网络完全中断,游戏客户端都会自动重试连接;达到最大重试次数后,会提示用户离线,不再继续尝试。下面我们就用代码完整复现这套逻辑。
服务端相关的代码其实之前文章就已经实现了:
socket 套接字 - TCPhttps://rosetea.blog.csdn.net/article/details/147653994这里上我们从零手写实现,核心设计思路采用状态机驱动 TCP 连接全生命周期,清晰管理连接、断线、重连、关闭全流程。
开发环境:VS Code 远程连接 Linux 服务器,新建单文件代码TcpClient.cc,所有逻辑、入口函数全部写在一个文件内,便于简化演示,降低理解成本。
代码基础头文件与基础框架搭建
引入基础头文件:
#include <iostream> // std::cout, std::cerr, std::endl #include <string> // std::string, std::getline #include <cstring> // memset, strerror #include <cstdlib> // exit #include <unistd.h> // close, sleep, read, write #include <sys/types.h> // ssize_t #include <sys/socket.h> // socket, connect, sockaddr_in, AF_INET, SOCK_STREAM #include <netinet/in.h> // htons, sockaddr_in #include <arpa/inet.h> // inet_pton using namespace std;工具函数Usage:打印程序启动参数提示
/** * @brief 打印程序使用说明 * @param process 程序名称(通常取 argv[0]) */ void Usage(const std::string &process) { std::cout << "Usage: " << process << " server_ip server_port" << std::endl; }编译指令:
TcpClient: TcpClient.cc g++ -o $@ $^ .PHONY: clean clean: rm -rf TcpClient运行格式:./TcpClient 服务端IP 服务端端口
整体代码架构分层设计
整套程序分为三层结构,自上而下逻辑清晰:
第一层:连接核心类ClientConnection
代表单条 TCP 客户端连接,支持多实例创建,每个对象独立管理一条连接。
1. 私有成员变量(连接全量属性)
int _sockfd:套接字文件描述符,初始化默认值-1uint16_t _serverport:服务端端口std::string _serverip:服务端 IP 地址int _retry_interval:重连间隔(单位:秒),默认 1 秒int _max_retries:最大重连次数,默认 5 次Status _status:连接状态枚举,管控全流程流转
2. 对外提供核心接口
void Connect():单次发起 TCP 连接void Reconnect():断线自动重试逻辑void Disconnect():关闭套接字、释放资源Status GetStatus():获取当前连接状态void Process():正常连接后的 IO 通信业务逻辑析构函数:自动调用
Disconnect释放套接字资源
3. 设计说明
所有接口执行操作时,都会主动修改内部_status状态;上层仅通过GetStatus()读取状态,就能判断下一步执行动作,这是状态机的核心设计思想。
第二层:上层控制类TcpClient
仅做封装,内部组合持有一个ClientConnection对象,统一对外提供运行入口:
构造函数:接收服务端 IP、端口,初始化底层连接对象
void Execute():主循环状态机调度函数,程序核心驱动私有成员:
ClientConnection _conn;组合持有连接实例
第三层:main入口函数
校验命令行参数,参数数量不等于 3 则打印用法并退出
解析传入的服务端 IP、端口,端口通过
stoi转为整数创建
TcpClient实例,调用Execute()启动完整客户端逻辑
核心:连接状态枚举enum class Status
采用C++11 强类型枚举,相比普通 enum 会做严格类型检查,避免状态值冲突,覆盖客户端完整生命周期 5 种状态:
/** * @brief 连接状态机枚举(C++11 强类型枚举) * * 定义 TCP 客户端连接生命周期中的各种状态。 * 状态之间的转换由 TcpClient::Execute() 中的主循环驱动。 * * 状态流转图: * @code * NEW ──Connect()──> CONNECTED ──Process()──> DISCONNECTED * │ │ │ * │ read()==0 Reconnect() * │ write()<0 │ * │ │ ┌─────┴──────┐ * │ │ ▼ ▼ * │ └──────────────> CONNECTED CLOSED * │ (max retries * │ exhausted) * └──────────────────────────────────────────────────────┘ * @endcode */ enum class Status { NEW, ///< 新建状态,尚未发起任何连接尝试 CONNECTING, ///< 正在重连过程中(仅用于标识重连流程) CONNECTED, ///< 连接/重连成功,可以正常进行业务通信 DISCONNECTED, ///< 连接断开(对方关闭)或单次连接尝试失败 CLOSED ///< 达到最大重试次数,永久关闭,程序退出 };各状态流转规则重点区分
DISCONNECTED:临时断线,只是单次连接失败,允许进入重连流程;
CLOSED:重连全部失败后的最终状态,直接关闭连接、终止程序;
CONNECTING:仅在Reconnect重连函数内部临时赋值,上层主循环无需单独处理该状态。
上层主循环TcpClient::Execute()状态机调度逻辑
函数内部是无限while(true)循环,每次循环读取当前连接状态,通过switch分支分发对应处理逻辑:
/** * @class TcpClient * @brief TCP 客户端的对外封装,基于状态机驱动主循环 * * 该类组合持有 ClientConnection 对象,通过状态机模式 * 将"连接建立 → 业务通信 → 断开重连 → 终止退出"的完整生命周期 * 编码为清晰的状态转换逻辑。 * * 设计模式:状态机(State Machine)+ 组合(Composition) */ class TcpClient { public: /** * @brief 构造函数:初始化底层连接对象 * @param serverport 服务器监听端口 * @param serverip 服务器 IP 地址 */ TcpClient(uint16_t serverport, const std::string &serverip) : _conn(serverport, serverip) { } /** * @brief 主执行循环:基于状态机驱动全部业务逻辑 * * 状态调度策略: * | 当前状态 | 执行动作 | 下一状态 | * |---------------|---------------------|--------------------------| * | NEW | Connect() | CONNECTED / DISCONNECTED | * | CONNECTED | Process() | DISCONNECTED / CLOSED | * | DISCONNECTED | Reconnect() | CONNECTED / CLOSED | * | CLOSED | Disconnect() + 退出 | (程序终止) | * * 这是一个无限循环,唯一的退出路径是 _status 变为 Status::CLOSED。 */ void Execute() { while (true) { switch (_conn.GetStatus()) { case Status::NEW: // 初始状态:发起首次连接 _conn.Connect(); break; case Status::CONNECTED: // 连接就绪:进入业务 IO 循环 std::cout << "连接成功, 开始进行通信." << std::endl; _conn.Process(); break; case Status::DISCONNECTED: // 连接断开:启动重连流程 std::cout << "连接失败或者对方掉线, 开始重连." << std::endl; _conn.Reconnect(); break; case Status::CLOSED: // 永久关闭:释放资源并退出程序 _conn.Disconnect(); std::cout << "重连失败, 退出." << std::endl; return; default: break; } } } private: ClientConnection _conn; ///< 底层连接对象(组合关系) };流转完整链路
初始化NEW→ 调用Connect();
连接成功 → 状态改为
CONNECTED→ 下一轮循环执行Process()收发数据;连接失败 → 状态改为
DISCONNECTED→ 下一轮循环进入重连;
CONNECTED通信中检测到掉线 → 状态改为DISCONNECTED→ 触发重连;
DISCONNECTED进入Reconnect():
重连中途成功 → 状态改为
CONNECTED,回到通信逻辑;全部重试次数耗尽 → 状态改为
CLOSED,程序退出;
CLOSED直接关闭套接字,结束客户端进程。
底层接口逐行实现讲解
1.ClientConnection::Connect()单次连接函数
步骤 1:创建套接字
_sockfd = socket(AF_INET, SOCK_STREAM, 0); if (_sockfd < 0) { cerr << "socket error" << endl; exit(1); }重点知识点:客户端无需手动bind,调用connect时操作系统会自动分配随机本地端口并完成绑定。
步骤 2:填充服务端地址结构体
struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(_serverport); // inet_pton:字符串IP转网络序4字节IP inet_pton(AF_INET, _serverip.c_str(), &server.sin_addr);inet_pton记忆技巧:
p= presentation(人类可读字符串 IP)n= network(机器网络二进制 IP) 功能:安全替代老旧inet_ntoa,同时支持 IPv4/IPv6。
步骤 3:发起connect系统调用
int n = connect(_sockfd, (struct sockaddr *)&server, sizeof(server)); if (n < 0) { // 连接失败:关闭当前套接字,标记临时断开状态 Disconnect(); _status = Status::DISCONNECTED; return; } // 连接成功,更新状态 _status = Status::CONNECTED;关键细节:
连接失败不会直接退出进程,仅标记
DISCONNECTED,留给上层重连;每次重连都会销毁旧套接字、新建套接字,提升容错性。
2.ClientConnection::Reconnect()自动重连逻辑
void Reconnect() { _status = Status::CONNECTING; // 标记正在重连 int count = 0; while (count < _max_retries) { Connect(); if (_status == Status::CONNECTED) { // 重连成功,直接返回恢复通信 return; } sleep(_retry_interval); // 间隔指定秒数重试 count++; cout << "重连次数:" << count << ",最大上限:" << _max_retries << endl; } // 所有重试全部失败,标记永久关闭 _status = Status::CLOSED; }逻辑说明:
进入重连先标记临时状态
CONNECTING;循环次数小于最大重试值时持续调用
Connect();单次重连成功直接 return,回到正常通信;
循环耗尽后,状态置为
CLOSED,上层执行退出。
3.ClientConnection::Disconnect()关闭连接资源
void Disconnect() { if (_sockfd != -1) { close(_sockfd); _status = Status::CLOSED; _sockfd = -1; } }功能:判断套接字合法,执行close(),重置文件描述符为-1,统一置为关闭状态。
4.ClientConnection::Process()IO 通信业务函数
void Process() { while (true) { string inbuffer; cout << "Please Enter#"; getline(cin, inbuffer); if (inbuffer.empty()) continue; // 发送用户输入数据 ssize_t n = write(_sockfd, inbuffer.c_str(), inbuffer.size()); if (n > 0) { char buffer[1024]; ssize_t m = read(_sockfd, buffer, sizeof(buffer) - 1); if (m > 0) { buffer[m] = 0; cout << "echo messsge->" << buffer << endl; } else if (m == 0) { // TCP标准规则:read返回0 = 对端优雅关闭连接(收到FIN包) _status = Status::DISCONNECTED; break; } else { // read读取异常,连接彻底失效 cout << "read m:" << m << "errno:" << errno << "errno string:" << strerror(errno) << endl; _status = Status::CLOSED; break; } } else { // write写入失败,套接字失效 cout << "write n:" << n << "errno:" << errno << "errno string: " << strerror(errno) << endl; _status = Status::CLOSED; break; } } }核心 TCP 知识点:
read返回值 > 0:正常读到服务端回显数据;read返回值 = 0:服务端主动关闭连接,判定为掉线,置DISCONNECTED触发重连;read/write返回负值:套接字出现致命错误,直接置CLOSED永久关闭。
5. 析构函数
~ClientConnection() { Disconnect(); }对象销毁时自动释放套接字,避免文件描述符泄漏。
完整代码
/** * @file TcpClient.cc * @brief 基于状态机的 TCP 客户端实现 * * @details * 本文件实现了一个带自动重连机制的 TCP 客户端。 * 核心设计采用有限状态机(FSM)驱动连接生命周期,包含以下状态: * - NEW 新建状态,尚未发起连接 * - CONNECTING 正在执行重连流程 * - CONNECTED 连接已建立,可正常通信 * - DISCONNECTED 连接断开或单次连接尝试失败 * - CLOSED 达到最大重试次数,永久关闭连接 * * 主要类: * - ClientConnection 底层连接管理与业务 IO 处理 * - TcpClient 对外封装,基于状态机分发调度 * * 编译方式: * g++ -std=c++11 -Wall -o tcp_client TcpClient.cc * * 运行方式: * ./tcp_client <server_ip> <server_port> * * @author lfz-coder * @date 2026-07-27 * @version 1.0 */ #include <iostream> // std::cout, std::cerr, std::endl #include <string> // std::string, std::getline #include <cstring> // memset, strerror #include <cstdlib> // exit #include <unistd.h> // close, sleep, read, write #include <sys/types.h> // ssize_t #include <sys/socket.h> // socket, connect, sockaddr_in, AF_INET, SOCK_STREAM #include <netinet/in.h> // htons, sockaddr_in #include <arpa/inet.h> // inet_pton using namespace std; // ============================================================================ // 全局辅助函数 // ============================================================================ /** * @brief 打印程序使用说明 * @param process 程序名称(通常取 argv[0]) */ void Usage(const std::string &process) { std::cout << "Usage: " << process << " server_ip server_port" << std::endl; } // ============================================================================ // 枚举类型定义 // ============================================================================ /** * @brief 连接状态机枚举(C++11 强类型枚举) * * 定义 TCP 客户端连接生命周期中的各种状态。 * 状态之间的转换由 TcpClient::Execute() 中的主循环驱动。 * * 状态流转图: * @code * NEW ──Connect()──> CONNECTED ──Process()──> DISCONNECTED * │ │ │ * │ read()==0 Reconnect() * │ write()<0 │ * │ │ ┌─────┴──────┐ * │ │ ▼ ▼ * │ └──────────────> CONNECTED CLOSED * │ (max retries * │ exhausted) * └──────────────────────────────────────────────────────┘ * @endcode */ enum class Status { NEW, ///< 新建状态,尚未发起任何连接尝试 CONNECTING, ///< 正在重连过程中(仅用于标识重连流程) CONNECTED, ///< 连接/重连成功,可以正常进行业务通信 DISCONNECTED, ///< 连接断开(对方关闭)或单次连接尝试失败 CLOSED ///< 达到最大重试次数,永久关闭,程序退出 }; // ============================================================================ // ClientConnection 类 - 底层连接管理 // ============================================================================ /** * @class ClientConnection * @brief 负责 TCP 连接的建立、重连、业务通信与资源释放 * * 该类封装了 POSIX socket API 的底层操作,提供: * - 单次 TCP 连接建立(Connect) * - 自动重连循环(Reconnect) * - 回显式业务通信(Process) * - 连接资源释放(Disconnect / 析构函数) * * @note 所有方法均非线程安全,调用者需自行同步 */ class ClientConnection { public: /** * @brief 构造函数:初始化连接参数与默认状态 * * @param serverport 服务器监听端口(主机字节序) * @param serverip 服务器 IPv4 地址(点分十进制字符串) * * 初始化成员变量: * - _sockfd 置为 -1,表示尚未创建套接字 * - _retry_interval 默认为 1 秒 * - _max_retries 默认为 5 次 * - _status 置为 Status::NEW */ ClientConnection(uint16_t serverport, const std::string &serverip) : _sockfd(-1), _serverport(serverport), _serverip(serverip), _retry_interval(1), _max_retries(5), _status(Status::NEW) { } /** * @brief 执行单次 TCP 连接尝试 * * 流程: * 1. 调用 socket() 创建 IPv4 流式套接字(SOCK_STREAM) * 2. 填充目标服务器地址结构体 sockaddr_in * 3. 调用 connect() 发起三次握手 * * @note * 客户端无需手动 bind() 端口。 * 操作系统在 connect() 时自动从临时端口范围分配本地端口并完成隐式绑定。 * * @post * - 成功时:_sockfd 为有效文件描述符,_status = Status::CONNECTED * - 失败时:_sockfd 保持已创建(供重连复用),_status = Status::DISCONNECTED */ void Connect() { // 1. 创建 TCP 流式套接字 // AF_INET : IPv4 协议族 // SOCK_STREAM : 面向连接的字节流(TCP) // 0 : 自动选择协议(即 TCP) _sockfd = socket(AF_INET, SOCK_STREAM, 0); if (_sockfd < 0) { cerr << "socket error" << endl; exit(1); } /* * [设计说明] 为什么客户端不需要显式 bind()? * * 服务端必须 bind() 到一个众所周知的端口,客户端才能找到它。 * 而客户端只需让服务端知道"谁在跟我说话"即可,这个身份由 * connect() 时内核自动分配的临时端口(ephemeral port)来标识。 * * 内核在 connect() 内部执行: * 1. 查询路由表,确定出口网卡和源 IP * 2. 从 /proc/sys/net/ipv4/ip_local_port_range 范围内分配临时端口 * 3. 隐式 bind(src_ip, ephemeral_port) * 4. 发起 TCP 三次握手 */ // 2. 填充服务器地址结构体 struct sockaddr_in server; memset(&server, 0, sizeof(server)); // 清零结构体,避免残留值干扰 server.sin_family = AF_INET; // 地址族:IPv4 server.sin_port = htons(_serverport); // 端口号:主机字节序 → 网络字节序 // inet_pton: "presentation to network" — 将点分十进制 IP 字符串转换为 4 字节网络序二进制 inet_pton(AF_INET, _serverip.c_str(), &server.sin_addr); // 3. 发起 TCP 连接请求(三次握手) int n = connect(_sockfd, (struct sockaddr *)&server, sizeof(server)); if (n < 0) { // 连接失败:常见的 errno 包括 ECONNREFUSED(端口未监听)、 // ETIMEDOUT(网络不可达)、EHOSTUNREACH(主机不可达) _status = Status::DISCONNECTED; return; } // 连接建立成功 _status = Status::CONNECTED; } /** * @brief 获取当前 socket 文件描述符 * @return socket 文件描述符,未创建时返回 -1 */ int SocketFd() { return _sockfd; } /** * @brief 执行自动重连流程 * * 循环调用 Connect() 进行连接尝试,间隔 _retry_interval 秒, * 最多重试 _max_retries 次。 * * @pre _status 应处于 Status::DISCONNECTED 状态 * @post * - 重连成功:_status = Status::CONNECTED,函数返回 * - 重连耗尽:_status = Status::CLOSED,调用方应终止程序 */ void Reconnect() { _status = Status::CONNECTING; // 进入重连中间态 int count = 0; while (count < _max_retries) { Connect(); // 尝试单次连接 if (_status == Status::CONNECTED) { return; // 重连成功,立即返回 } sleep(_retry_interval); // 等待指定间隔后重试 count++; std::cout << "重连次数:" << count << ",最大上限:" << _max_retries << std::endl; } // 所有重试次数耗尽,进入永久关闭状态 _status = Status::CLOSED; } /** * @brief 关闭 socket 并重置连接资源 * * 幂等操作:多次调用安全。 * 如果 _sockfd 为有效描述符,则关闭并重置为 -1。 * * @post _sockfd = -1, _status = Status::CLOSED */ void Disconnect() { if (_sockfd != -1) { close(_sockfd); _status = Status::CLOSED; _sockfd = -1; } } /** * @brief 获取当前连接状态 * @return 当前 Status 枚举值 */ Status GetStatus() { return _status; } /** * @brief 业务 IO 处理循环:读用户输入 → 发送 → 接收服务端回显 * * 循环执行以下步骤: * 1. 从标准输入读取一行用户消息 * 2. 通过 write() 将消息发送至服务端 * 3. 通过 read() 接收服务端回显并打印 * * 退出条件(break 出循环): * - read() 返回 0:对端(服务器)正常关闭连接 → DISCONNECTED * - read() 返回 -1:读取出错 → CLOSED * - write() 返回 <= 0:写入失败 → CLOSED * * @note 空输入行(仅回车)会被跳过,不发送 */ void Process() { while (true) { // ---- 1. 读取用户输入 ---- string inbuffer; cout << "Please Enter# "; getline(cin, inbuffer); if (inbuffer.empty()) continue; // 跳过空输入 // ---- 2. 发送数据到服务端 ---- ssize_t n = write(_sockfd, inbuffer.c_str(), inbuffer.size()); if (n > 0) { // ---- 3. 接收服务端回显 ---- char buffer[1024]; ssize_t m = read(_sockfd, buffer, sizeof(buffer) - 1); if (m > 0) { buffer[m] = 0; // 手动添加字符串终止符 cout << "echo messsge->" << buffer << endl; } else if (m == 0) { // read() 返回 0 表示对端已调用 close(),TCP 连接正常终止(FIN) _status = Status::DISCONNECTED; break; } else { // read() 返回 -1:连接异常(如 RST、超时等) std::cout << "read m:" << m << " errno:" << errno << " errno string:" << strerror(errno) << std::endl; _status = Status::CLOSED; break; } } else { // write() 返回 <= 0:连接已失效(可能收到 SIGPIPE 或对端关闭) std::cout << "write n:" << n << " errno:" << errno << " errno string:" << strerror(errno) << std::endl; _status = Status::CLOSED; break; } } } /** * @brief 析构函数:自动释放 socket 资源 * * 通过 RAII 机制确保连接资源在对象销毁时被正确释放, * 避免 socket 文件描述符泄漏。 */ ~ClientConnection() { Disconnect(); } private: int _sockfd; ///< socket 文件描述符(-1 表示未创建或已关闭) uint16_t _serverport; ///< 服务器端口号(主机字节序) std::string _serverip; ///< 服务器 IPv4 地址(点分十进制字符串) int _retry_interval; ///< 重连间隔,单位:秒 int _max_retries; ///< 最大重连尝试次数 Status _status; ///< 当前连接状态(状态机驱动核心) }; // ============================================================================ // TcpClient 类 - 对外封装层 // ============================================================================ /** * @class TcpClient * @brief TCP 客户端的对外封装,基于状态机驱动主循环 * * 该类组合持有 ClientConnection 对象,通过状态机模式 * 将"连接建立 → 业务通信 → 断开重连 → 终止退出"的完整生命周期 * 编码为清晰的状态转换逻辑。 * * 设计模式:状态机(State Machine)+ 组合(Composition) */ class TcpClient { public: /** * @brief 构造函数:初始化底层连接对象 * @param serverport 服务器监听端口 * @param serverip 服务器 IP 地址 */ TcpClient(uint16_t serverport, const std::string &serverip) : _conn(serverport, serverip) { } /** * @brief 主执行循环:基于状态机驱动全部业务逻辑 * * 状态调度策略: * | 当前状态 | 执行动作 | 下一状态 | * |---------------|---------------------|--------------------------| * | NEW | Connect() | CONNECTED / DISCONNECTED | * | CONNECTED | Process() | DISCONNECTED / CLOSED | * | DISCONNECTED | Reconnect() | CONNECTED / CLOSED | * | CLOSED | Disconnect() + 退出 | (程序终止) | * * 这是一个无限循环,唯一的退出路径是 _status 变为 Status::CLOSED。 */ void Execute() { while (true) { switch (_conn.GetStatus()) { case Status::NEW: // 初始状态:发起首次连接 _conn.Connect(); break; case Status::CONNECTED: // 连接就绪:进入业务 IO 循环 std::cout << "连接成功, 开始进行通信." << std::endl; _conn.Process(); break; case Status::DISCONNECTED: // 连接断开:启动重连流程 std::cout << "连接失败或者对方掉线, 开始重连." << std::endl; _conn.Reconnect(); break; case Status::CLOSED: // 永久关闭:释放资源并退出程序 _conn.Disconnect(); std::cout << "重连失败, 退出." << std::endl; return; default: break; } } } private: ClientConnection _conn; ///< 底层连接对象(组合关系) }; // ============================================================================ // 程序入口 // ============================================================================ /** * @brief 程序入口点 * * 命令行参数: * - argv[1] 服务器 IPv4 地址(点分十进制,如 "127.0.0.1") * - argv[2] 服务器端口号(如 "8080") * * @param argc 命令行参数个数 * @param argv 命令行参数数组 * @return 0 表示正常退出,1 表示参数错误 * * 使用示例: * @code * ./tcp_client 127.0.0.1 8080 * @endcode */ int main(int argc, char *argv[]) { // 参数校验:必须提供 server_ip 和 server_port if (argc != 3) { Usage(argv[0]); return 1; } std::string serverip = argv[1]; uint16_t serverport = stoi(argv[2]); // 创建客户端实例并启动状态机主循环 TcpClient client(serverport, serverip); client.Execute(); return 0; }程序测试场景与现象
场景 1:服务端未启动,客户端启动重试
执行命令:./TcpClient 127.0.0.1 8888输出日志:
连接失败或者对方掉线,开始重连. 重连次数:1,最大上限:5 重连次数:2,最大上限:5 重连次数:3,最大上限:5 重连次数:4,最大上限:5 重连次数:5,最大上限:5 重连失败, 退出.现象:连续重试 5 次后判定永久关闭,程序退出。
场景 2:服务端正常运行,客户端正常通信
日志输出:
连接成功, 开始进行通信. Please Enter#输入任意字符串,服务端回显后打印:echo messsge->"输入内容"。
场景 3:通信中途关闭服务端,触发自动重连
./TcpClient localhost 8080 连接成功, 开始进行通信. Please Enter# 你好 echo messsge->你好 Please Enter# 你在吗? 连接失败或者对方掉线, 开始重连. 重连次数:1,最大上限:5 重连次数:2,最大上限:5 重连次数:3,最大上限:5 重连次数:4,最大上限:5 重连次数:5,最大上限:5 重连失败, 退出.正常收发消息时终止 TCP 服务端;
read检测到返回 0,状态改为DISCONNECTED,打印重连提示;
5 次重试期间重启服务端:重连成功,恢复正常收发;
5 次重试全程无服务端:最终提示重连失败,程序退出。
拓展优化方向
本节课代码为同步阻塞简易演示版本,生产环境可做多层优化:
重连间隔优化:固定 1 秒改为指数退避(1s、2s、4s、8s),减少无效连接;
IO 模型升级:替换阻塞
read/write,使用epoll/select多路复用,支持同时处理多连接;线程拆分:新开独立线程专门处理重连逻辑,主线程只负责业务通信;
超时持久重试:耗尽 5 次后不退出,每隔 30 分钟后台静默重试,不直接结束进程;
应用层心跳:增加心跳包检测,快速感知静默断网(TCP 内核保活检测延迟极高);
多连接支持:创建多个
ClientConnection对象,同时维护多条 TCP 连接。
本篇核心是状态机管理 TCP 连接生命周期,把断线、重连、正常通信、关闭四种场景拆分,通过状态流转消除复杂嵌套判断,代码可读性、可维护性大幅提升。不管是游戏客户端、物联网设备、后台长连接组件,这套状态机重连思路都可以直接复用。