代码
主要是借助了状态机来实现断线重连
client.hpp
#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>
using namespace std;
#define RETRY_INTERVAL 1
#define MAX_RETRIES 5
void Usage(const std::string& process)
{
cout << "Usage: " << process << "argv[1]: server_ip and argv[2]: server_port" << endl;
}
enum ERR // 这里的出错状态码不用强枚举类型,因为exit()中必须传入整形,强枚举类型会编译报错
{
SOCKFDERR = 1,
BINDERR
};
enum class Status // 强枚举类型
{
NEW, // 新建立的链接,还未初始化完的状态
CONNECTIGN, // 正在连接
CONNECTED, // 连接成功状态,或者重连成功状态
DISCONNECTED, // 重连失败
CLOSED // 连接关闭状态
};
class Connection
{
private:
int _sockfd;
uint16_t _serverport; // 服务端的端口号
string _serverip; // 服务端的ip
int _retry_interval; // 每次重连的时间间隔,单位是秒
int _max_retries; // 最大尝试重连次数
Status _statu; // 当前连接的状态
public:
Connection(uint16_t port,const string& ip) :
_sockfd(-1),_serverport(port),_serverip(ip),
_retry_interval(RETRY_INTERVAL),_max_retries(MAX_RETRIES),
_statu(Status::NEW) {}
void Connect()
{
// 1.创建socket
_sockfd = socket(AF_INET,SOCK_STREAM,0);
if(_sockfd < 0)
{
cerr << "Create Sockfd Failed!" << endl;
exit(SOCKFDERR);
}
// 2.connet,客户端是不需要bind的,因为在connet的时候,系统会给客户端随机分配端口号
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);
int n = connect(_sockfd,(struct sockaddr*)&server,sizeof(server)); // 自动绑定端口
if(n < 0)
{
Disconnect(); // 先重置连接的状态,关闭原来的套接字
_statu = Status::DISCONNECTED; // 准备进行重连
return;
}
// 连接成功了
_statu = Status::CONNECTED;
}
int Sockfd() {return _sockfd; }
// 重新连接
void ReConnect()
{
// 其实就是不断调用Connect()
int count = _max_retries;
while(count > 0)
{
cout << "正在重新连接,剩余重连次数: " << count << " 次..." << endl;
Connect(); // 重新连接
count--;
if(_statu == Status::CONNECTED) return; // 重连成功直接返回
sleep(_retry_interval);
}
// 重连失败
_statu = Status::CLOSED;
}
// 关闭连接
void Disconnect()
{
if(_sockfd != -1)
{
close(_sockfd);
_sockfd = -1;
_statu = Status::CLOSED;
}
}
Status GetStatu() { return _statu; }
void Process()
{
// 简单的IO
while(true)
{
string inbuffer;
cout << "Please Enter# ";
getline(cin,inbuffer);
if(inbuffer.size() == 0) continue;
ssize_t n = send(_sockfd,inbuffer.c_str(),sizeof(inbuffer),0);
if(n < 0)
{
cout << "Send n : " << n << "errno: " << errno << " errno string: " << strerror(errno) << endl;
_statu = Status::CLOSED;
break;
}
else
{
char buffer[1024];
int m = recv(_sockfd,buffer,1023,0);
if(m < 0)
{
cout << "Recv m : " << "errno: " << errno << " errno string: " << strerror(errno) << endl;
_statu = Status::CLOSED;
break;
}
else if(m == 0)
{
_statu = Status::DISCONNECTED;
break;
}
else
{
buffer[m] = 0;
cout << "Server said: " << endl;
cout << buffer << endl;
}
}
}
}
~Connection() { Disconnect(); }
};
class TcpClient
{
private:
Connection _conn;
public:
TcpClient(uint16_t port,const string& ip) : _conn(port,ip)
{}
void Execute()
{
while(true)
{
switch(_conn.GetStatu())
{
case Status::NEW:
_conn.Connect();
break;
case Status::CONNECTED:
_conn.Process();
break;
case Status::DISCONNECTED:
_conn.ReConnect();
break;
case Status::CLOSED:
cout << "重连失败,关闭连接..." << endl;
_conn.Disconnect();
return;
default:
break;
}
}
}
~TcpClient() {}
};
main.cc
#include "client.hpp"
int main(int argc,char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
string serverip = argv[1];
uint16_t serverport = stoi(argv[2]);
TcpClient client(serverport,serverip);
client.Execute();
return 0;
}
makefile
TcpClient:main.cc
g++ -std=c++11 $^ -o $@
.PHONY::clean
clean:
rm -f TcpClient;