connect 的断线重连
目录
TcpClient.cc
辅助函数
连接状态枚举
ClientConnection 类
成员变量
核心方法
构造函数:
Connect():
Disconnect():
SockFd():
Reconnect():
GetStatus():
Process():
析构函数:
TcpClient 类
核心方法
功能与流程
main 函数
程序工作流程
运行截图
重连成功
重连失败
客户端会面临服务器崩溃的情况,我们可以试着写一个客户端重连的代码,模拟并理解一些客户端行为,比如游戏客户端等
TcpClient.cc
#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.connectstruct 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)); // 自动进行bindif (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 状态并退出程序
运行截图
重连成功
重连失败