本篇核心内容实现带自动断线重连功能的 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采用C11 强类型枚举相比普通 enum 会做严格类型检查避免状态值冲突覆盖客户端完整生命周期 5 种状态/** * brief 连接状态机枚举C11 强类型枚举 * * 定义 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人类可读字符串 IPn 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 -stdc11 -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 连接状态机枚举C11 强类型枚举 * * 定义 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 连接生命周期把断线、重连、正常通信、关闭四种场景拆分通过状态流转消除复杂嵌套判断代码可读性、可维护性大幅提升。不管是游戏客户端、物联网设备、后台长连接组件这套状态机重连思路都可以直接复用。