断线重连机制,它成为确保应用在网络不稳定情况下仍能持续提供服务的关键技术之一。本文旨在深入探讨TCP(传输控制协议)连接中的断线重连机制,解析其工作原理、设计原则、实现策略以及在实际应用中的挑战与解决方案。试着写一个客户端重连的代码,模拟并理解一些客户端行为,比如游戏客户端等。
断线重连代码
#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
enum Status
{
NEW, // 新建状态,就是单纯的连接
CONNECTING, // 正在连接,仅仅方便查询 conn 状态
CONNECTED, // 连接或者重连成功
DISCONNECTED, // 重连失败
CLOSED // 连接失败,经历重连,无法连接
};
const static int defaultretryinterval = 1; // 重试时间间隔
const static int defaultmaxretries = 5; // 最大重试次数
const static int defaultsockfd = -1; // 默认的sockfd
class ConnectClient
{
public:
ConnectClient(std::string &serverip, uint16_t serverport)
: _serverip(serverip),
_serverport(serverport),
_sockfd(defaultsockfd),
_retry_interval(defaultretryinterval),
_max_retries(defaultmaxretries),
_status(Status::NEW)
{
}
void Connect() // 建立连接
{
// 创建 流式socket
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if (_sockfd < 0)
{
std::cout << "socket create fail..." << std::endl;
exit(2);
}
// 连接
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = _serverport;
serveraddr.sin_addr.s_addr = inet_addr(_serverip.c_str());
int n = connect(_sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if (n < 0) // 连接失败
{
Disconnect(); // 重置_sockfd
_status = Status::DISCONNECTED;
return;
}
// 连接成功
_status = Status::CONNECTED;
std::cout << "connect success..." << std::endl;
}
void Reconnect() // 重新连接
{
_status = Status::CONNECTING;
int cnt = 1;
while (true)
{
Connect();
if (_status == Status::CONNECTED)
{
return;
}
std::cout << "重连次数: " << cnt << ", 最大上限: " << _max_retries << std::endl;
cnt++;
if (cnt > _max_retries) //超过连接设置的最大次数,直接连接失败,关闭client
{
_status = Status::CLOSED;
return;
}
sleep(defaultretryinterval);
}
}
void process() //客服端连接成功执行的业务
{
while (true)
{
std::string str = "Hello world!";
ssize_t n = ::send(_sockfd, str.c_str(), str.size(), 0);
if (n > 0) // 发送成功
{
char buffer[1024]; // 接收buffer
ssize_t m = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
if (m > 0) // 接收成功
{
buffer[m] = 0;
std::cout << "server echo#" << buffer << std::endl;
}
else // 接收失败
{
_status = Status::DISCONNECTED; // 接受失败,说明连接断开了 ,设置状态,重新连接
break;
}
}
else // 发送失败
{
_status = Status::CLOSED; // 连接成功,但是发送失败,说明服务器异常,直接将Client关闭
break;
}
}
}
void Disconnect() //关闭Client
{
if (_sockfd > defaultsockfd)
{
::close(_sockfd);
_status = Status::CLOSED;
_sockfd = defaultsockfd;
}
}
Status GetStatus() // 获取当前状态
{
return _status;
}
~ConnectClient()
{
}
private:
int _sockfd;
uint16_t _serverport; // server port 端口号
std::string _serverip; // server ip 地址
int _retry_interval; // 重试时间间隔
int _max_retries; // 重试次数
Status _status; // 连接状态
};
class TcpClinet
{
public:
TcpClinet(std::string &serverip, uint16_t serverport)
: _connect(serverip, serverport)
{
}
void Excute() // 执行client
{
while (true)
{
switch (_connect.GetStatus())
{
case Status::NEW: //如果状态是新创建,则连接
_connect.Connect();
break;
case Status::CONNECTED: //已经建立连接了
_connect.process(); //进行通信
break;
case Status::DISCONNECTED: //断开连接了
_connect.Reconnect(); //进行重连
break;
case Status::CLOSED: //关闭
_connect.Disconnect(); //则关闭文件描述符
return;
default:
break;
}
}
}
~TcpClinet()
{
}
private:
ConnectClient _connect;
};
void Usage(std::string args)
{
std::cout << "Usage:\n\t" << args << " serverip serverport" << std::endl;
}
// ./Tcp_Client serverip serverport
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
std::string serverip = argv[1];
int serverport = std::stoi(argv[2]);
std::shared_ptr<TcpClinet> TcpClientptr = std::make_shared<TcpClinet>(serverip, serverport);
TcpClientptr->Excute();
return 0;
}