目录
[ClientConnection 类](#ClientConnection 类)
[TcpClient 类](#TcpClient 类)
[main 函数](#main 函数)
客户端会面临服务器崩溃的情况,我们可以试着写一个客户端重连的代码,模拟并理解一些客户端行为,比如游戏客户端等
TcpClient.cc
cpp#include <iostream> #include <string> #include <cstring> #include <cstdlib> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> void Usage(const std::string &process) { std::cout << "Usage: " << process << " server_ip server_port" << std::endl; } enum class Status // C++11强类型枚举 { NEW, // 新建状态,就是单纯的连接 CONNECTING, // 正在连接,仅仅方便查询conn状态 CONNECTED, // 连接或者重连成功 DISTCONNECTED, // 重连失败 CLOSED // 连接失败,经历重连,无法连接 }; class ClientConnection { public: ClientConnection(uint16_t serverport, const std::string serverip) : _sockfd(-1), _serverport(serverport), _serverip(serverip), _retry_interval(1), _max_retries(5), _status(Status::NEW) { } void Connect() { // 1.创建socket _sockfd = socket(AF_INET, SOCK_STREAM, 0); if (_sockfd < 0) { std::cerr << "socket error" << std::endl; exit(1); } // 2.要不要bind?必须要有Ip和Port,需要bind,但是不需要用户显示的bind,client系统随机端口 // 发起连接的时候,client会被OS自动进行本地绑定 // 2.connect struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(_serverport); // p:process(进程),n(网络) -- 不太准确,但是好记忆 inet_pton(AF_INET, _serverip.c_str(), &server.sin_addr); // 1.字符串ip->4字节IP 2.网络序列 int n = connect(_sockfd, (struct sockaddr *)&server, sizeof(server)); // 自动进行bind if (n < 0) { Disconnect(); // 恢复sockfd的默认值,是连接没有成功,不代表sockfd创建没有成功 _status = Status::DISTCONNECTED; // 没有连接成功 return; } _status = Status::CONNECTED; // 连接成功 } int SockFd() { return _sockfd; } 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; // 重连失败,可以关闭了 } void Disconnect() { if (_sockfd != -1) { close(_sockfd); _status = Status::CLOSED; _sockfd = -1; } } Status GetStatus() { return _status; } void Process() { // 简单的IO即可 while (true) { std::string inbuffer; std::cout << "Please Enter#"; getline(std::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; std::cout << buffer << std::endl; } else if (m == 0) // 这里证明server端掉线了 { _status = Status::DISTCONNECTED; break; } else { std::cout << "read m : " << m << "errno: " << errno << "errno string: " << strerror(errno) << std::endl; _status = Status::CLOSED; break; } } else { std::cout << "write n : " << n << "errno: " << errno << "errno string: " << strerror(errno) << std::endl; _status = Status::CLOSED; break; } } } ~ClientConnection() { Disconnect(); } private: int _sockfd; uint16_t _serverport; // server port端口号 std::string _serverip; // server ip地址 int _retry_interval; // 重试时间间隔 int _max_retries; // 重试次数 Status _status; // 连接状态 }; class TcpClient { public: TcpClient(uint16_t serverport, const std::string serverip) : _conn(serverport, serverip) { } void Execute() { while (true) { switch (_conn.GetStatus()) { case Status::NEW: _conn.Connect(); break; case Status::CONNECTED: std::cout << "连接成功,开始进行通信." << std::endl; _conn.Process(); break; case Status::DISTCONNECTED: std::cout << "连接失败或者对方掉线,开始重连." << std::endl; _conn.Reconnect(); break; case Status::CLOSED: _conn.Disconnect(); std::cout << "重连失败,退出." << std::endl; return; default: break; } } } ~TcpClient() { } private: ClientConnection _conn; // 简单组合起来即可 }; // class Tcp //./tcpclient 127.0.0.1 8888 int main(int argc, char *argv[]) { if (argc != 3) { Usage(argv[0]); return 1; } std::string serverip = argv[1]; uint16_t serverport = std::stoi(argv[2]); TcpClient client(serverport, serverip); client.Execute(); return 0; }
我们一块一块来讲
辅助函数
这个函数用于提示用户程序的正确使用方法,当命令行参数不正确时会被调用。
连接状态枚举
使用 C++11 的强类型枚举定义了客户端连接的各种状态,使状态管理更清晰。
ClientConnection 类
这个类封装了与服务器连接的所有操作:
成员变量
核心方法
构造函数:
- 作用 :用于初始化
ClientConnection
类的成员变量。- 成员变量初始化 :
_sockfd
初始化为 - 1,表示尚未创建 socket。_serverport
和_serverip
分别由构造函数参数初始化,对应服务器的端口和 IP 地址。_retry_interval
初始化为 1,即重连时间间隔为 1 秒。_max_retries
初始化为 5,即最大重连次数为 5 次。_status
初始化为Status::NEW
,表示初始连接状态为新建。Connect():
功能概述
该函数完成了 TCP 客户端与服务器建立连接的核心流程,包括创建 socket、设置服务器地址结构、发起连接并根据结果更新连接状态。
步骤解析
- 创建 socket
- 调用
socket
函数创建基于 IPv4(AF_INET
)的流式套接字(SOCK_STREAM
,即 TCP 协议)。- 若创建失败,输出错误信息并退出程序。
- 设置服务器地址结构
- 定义
sockaddr_in
类型的服务器地址变量server
,并通过memset
初始化。- 设置地址族为
AF_INET
,端口号(通过htons
转换为网络字节序),以及服务器 IP 地址(通过inet_pton
将字符串 IP 转换为网络字节序的 4 字节 IP)。- 发起连接
- 调用
connect
函数尝试与服务器建立连接。- 若连接失败,调用
Disconnect
函数恢复 socket 状态,并将连接状态设置为DISTCONNECTED
(重连失败);若连接成功,将状态设置为CONNECTED
。Disconnect():
功能与逻辑
- 该函数用于安全地关闭客户端与服务器的 socket 连接。
- 首先判断
sockfd
(socket 文件描述符)是否有效(不为 - 1),若有效则调用close
函数关闭 socket。- 随后将连接状态
_status
设置为CLOSED
,并将sockfd
重置为 - 1,确保资源释放和状态一致性。SockFd():
返回sockfd套接字
Reconnect():
功能与流程
- 状态设置 :首先将连接状态置为
CONNECTING
,表示正在重连。- 重连循环 :通过
while
循环,在最大重试次数(_max_retries
)内反复调用Connect
函数尝试重连。- 重试控制 :每次重连失败后,休眠
_retry_interval
时间(秒),并输出重连次数和最大上限的提示信息。- 结果处理 :若重连成功(状态为
CONNECTED
)则直接返回;若达到最大重试次数仍失败,将状态置为CLOSED
。GetStatus():
获取当前状态。
Process():
功能与流程
- 输入处理 :通过
getline
读取用户输入,若输入为空则跳过本次循环。- 数据发送 :调用
write
函数将用户输入发送给服务器。- 数据接收 :调用
read
函数接收服务器返回的数据,若接收成功则输出;若接收长度为 0(服务器端掉线),则将状态置为DISTCONNECTED
并退出循环;若接收失败,输出错误信息并将状态置为CLOSED
。- 发送失败处理 :若
write
失败,输出错误信息并将状态置为CLOSED
。析构函数:
调用Disconnect使sockfd和status回到初始状态。
TcpClient 类
这个类是客户端的入口,通过组合 ClientConnection 对象来实现功能:
核心方法
Execute():
功能与流程
- 循环控制 :通过
while(true)
循环持续监控连接状态。- 状态分支 :
- 当状态为
NEW
时,调用Connect
尝试建立连接。- 当状态为
CONNECTED
时,输出连接成功提示并调用Process
进行通信交互。- 当状态为
DISTCONNECTED
时,输出重连提示并调用Reconnect
尝试重连。- 当状态为
CLOSED
时,调用Disconnect
并输出重连失败提示后退出函数。main 函数
- 检查命令行参数
- 解析服务器 IP 和端口
- 创建 TcpClient 对象并调用 Execute () 开始运行
程序工作流程
- 程序启动时检查命令行参数
- 创建客户端对象,初始状态为 NEW
- 尝试连接服务器,成功则进入 CONNECTED 状态
- 在 CONNECTED 状态下,用户可以输入消息发送给服务器,并接收服务器响应
- 如果连接断开(服务器关闭或网络问题),进入 DISTCONNECTED 状态并尝试重连
- 重连失败达到最大次数后,进入 CLOSED 状态并退出程序
运行截图
重连成功
重连失败
connect 的断线重连
板鸭〈小号〉2025-10-19 14:29
相关推荐
wydaicls4 小时前
Linux 内核伙伴系统在快速路径分配内存时,对一个内存区域(Zone)进行水位线检查和内存压力评估的关键逻辑黄昏晓x4 小时前
Linux----权限小白不想白a4 小时前
【shell】每日shell练习(系统服务状态监控/系统性能瓶颈分析)一匹电信狗5 小时前
【MySQL】数据库的相关操作迦蓝叶5 小时前
JAiRouter v1.0.0 正式发布:企业级 AI 服务网关的开源解决方案bugtraq20215 小时前
为什么.NET的System.IO.Compression无法解压zlib流insight^tkk6 小时前
【Docker】记录一次使用docker部署dify网段冲突的问题K_i1347 小时前
Hadoop 集群自动化运维实战TH_17 小时前
cmd_常用命令