断线重连在我们日常生活中经常遇到,比如正在打游戏,此时服务器出现了问题,或者网络没了,客户端会主动重连几次服务器,如果重连都失败了,就可以放弃重连。在实现断线重连时,使用状态机的方式,实现tcp client断线重连的效果。所谓状态机,就是指根据客户端的状态来执行不同的操作。
我们设置如下的几种状态:
enum class Status // C++11的强类型枚举
{
NEW, // 新建状态
CONNECTING, //正在连接
CONNECTED, //连接成功
DISCONNECTED, //重连失败
CLOSED //连接失败,经历重连,无法连接
};
代码的主要框架如下:
const static int defaultsockfd = -1;
const static int defaultretryinterval = 1;
const static int defaultmaxretries = 5;
class ClientConnection
{
public:
ClientConnection(const std::string& serverip, uint16_t serverport)
:_sockfd(defaultsockfd),
_serverip(serverip),
_serverport(serverport),
_status(Status::NEW),
_retry_interval(defaultretryinterval), //重试的时间间隔
_max_reties(defaultmaxretries) //重试次数
{}
Status Status()
{
return _status;
}
void Connect()
{
}
void Process() //正常通信
{
}
void Reconnect()
{
}
void Disconnect()
{
}
~ClientConnection()
{}
private:
int _sockfd;
std::string _serverip;
uint16_t _serverport;
Status _status;
int _retry_interval; //重试的时间间隔
int _max_reties; //最大重试次数
};
class TcpClient
{
public:
TcpClient(const std::string& serverip, uint16_t serverport):_connection(serverip, serverport)
{}
void Excute()
{
}
~TcpClient()
{}
private:
ClientConnection _connection;
};
void Usage(std::string process)
{
std::cout << "Usage: " << process << " serverip serverport" << std::endl;
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(0);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
std::unique_ptr<TcpClient> tcpclient = std::make_unique<TcpClient>();
tcpclient->Excute();
return 0;
}
另外,在TcpClient中,为了容易获取客户端状态从而执行对应操作,需要在ClientConnection中提供获取状态的接口GetStatus。关于状态机的实现如下:
在Connect中,需要先创建套接字,然后发起连接,如果连接失败,先要关掉之前打开的sockfd,并更改_status:
void Connect()
{
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if(_sockfd < 0)
{
std::cerr << "create sockfd error" << std::endl;
exit(SOCKET_ERR);
}
struct sockaddr_in server;
memset(&server, 0 , sizeof(server));
server.sin_port = htons(_serverport);
server.sin_family = AF_INET;
inet_pton(AF_INET, _serverip.c_str(), &server.sin_addr.s_addr);
int n = ::connect(_sockfd, (struct sockaddr *)&server, sizeof(server));
if(n < 0)
{
//1.关闭fd
Disconnect();
//2.更新状态
_status = Status::DISCONNECTED;
//3.返回
return;
}
//连接成功,只需更新一下状态
_status = Status::CONNECTED;
}
在Reconnect中,我们需要循环Connect,循环_max_retries次,如果重连时连接成功,那么在Connect中_status被修改为connected,如果重连一定次数后,未连接成功,那么把_status修改为disconnected:
void Reconnect()
{
_status = Status::CONNECTING;
int cnt = 0;
while(true)
{
Connect();
if(_status == Status::CONNECTED)
{
break;
}
cnt++;
if(cnt > _max_reties)
{
_status = Status::CLOSED;
std::cout << "重连失败..., 请检查你的网络" << std::endl;
break;
}
std::cout << "断线重连中,重连次数:" << cnt << std::endl;
sleep(_retry_interval);
}
}
Disconnect函数中,只需要关闭之前打开的sockfd即可:
void Disconnect()
{
if(_sockfd > defaultsockfd)
{
::close(_sockfd);
_sockfd = defaultsockfd;
_status = Status::CLOSED;
}
}
在Process中,做一个简单的echo服务,并根据send和recv的返回值改变_status:
void Process() //正常通信
{
while(true)
{
std::string message = "hello server";
ssize_t n = ::send(_sockfd, message.c_str(), sizeof(message), 0);
if(n < 0)
{
std::cerr << "send error" << std::endl;
_status = Status::CLOSED;
break;
}
char buffer[1024];
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;
}
}
}
到此,一个使用状态机的客户端断线重连完成。
-------------------------------------------------------------------------------------------------------
完整代码:
#include <iostream>
#include <string>
#include <cstring>
#include <memory>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
const static int defaultsockfd = -1;
const static int defaultretryinterval = 1;
const static int defaultmaxretries = 5;
enum ExitCode
{
USAGE_ERR = 1,
SOCKET_ERR
};
enum class Status // C++11的强类型枚举
{
NEW, // 新建状态
CONNECTING, //正在连接
CONNECTED, //连接成功
DISCONNECTED, //重连失败
CLOSED //连接失败,经历重连,无法连接
};
class ClientConnection
{
public:
ClientConnection(const std::string& serverip, uint16_t serverport)
:_sockfd(defaultsockfd),
_serverip(serverip),
_serverport(serverport),
_status(Status::NEW),
_retry_interval(defaultretryinterval), //重试的时间间隔
_max_reties(defaultmaxretries) //重试次数
{}
Status GetStatus()
{
return _status;
}
void Connect()
{
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if(_sockfd < 0)
{
std::cerr << "create sockfd error" << std::endl;
exit(SOCKET_ERR);
}
struct sockaddr_in server;
memset(&server, 0 , sizeof(server));
server.sin_port = htons(_serverport);
server.sin_family = AF_INET;
inet_pton(AF_INET, _serverip.c_str(), &server.sin_addr.s_addr);
int n = ::connect(_sockfd, (struct sockaddr *)&server, sizeof(server));
if(n < 0)
{
//1.关闭fd
Disconnect();
//2.更新状态
_status = Status::DISCONNECTED;
//3.返回
return;
}
//连接成功,只需更新一下状态
_status = Status::CONNECTED;
}
void Process() //正常通信
{
while(true)
{
std::string message = "hello server";
ssize_t n = ::send(_sockfd, message.c_str(), sizeof(message), 0);
if(n < 0)
{
std::cerr << "send error" << std::endl;
_status = Status::CLOSED;
break;
}
char buffer[1024];
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;
}
}
}
void Reconnect()
{
_status = Status::CONNECTING;
int cnt = 0;
while(true)
{
Connect();
if(_status == Status::CONNECTED)
{
break;
}
cnt++;
if(cnt > _max_reties)
{
_status = Status::CLOSED;
std::cout << "重连失败..., 请检查你的网络" << std::endl;
break;
}
std::cout << "断线重连中,重连次数:" << cnt << std::endl;
sleep(_retry_interval);
}
}
void Disconnect()
{
if(_sockfd > defaultsockfd)
{
::close(_sockfd);
_sockfd = defaultsockfd;
_status = Status::CLOSED;
}
}
~ClientConnection()
{}
private:
int _sockfd;
std::string _serverip;
uint16_t _serverport;
Status _status;
int _retry_interval; //重试的时间间隔
int _max_reties; //最大重试次数
};
class TcpClient
{
public:
TcpClient(const std::string& serverip, uint16_t serverport):_connection(serverip, serverport)
{}
void Excute()
{
while(true)
{
switch(_connection.GetStatus())
{
case Status::NEW:
_connection.Connect();
break;
case Status::CONNECTED:
_connection.Process();
break;
case Status::DISCONNECTED:
_connection.Reconnect();
break;
case Status::CLOSED:
_connection.Disconnect();
return;
default:
//Do Nothing
break;
}
}
}
~TcpClient()
{}
private:
ClientConnection _connection;
};
void Usage(std::string process)
{
std::cout << "Usage: " << process << " serverip serverport" << std::endl;
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
std::unique_ptr<TcpClient> tcpclient = std::make_unique<TcpClient>(serverip, serverport);
tcpclient->Excute();
return 0;
}