【Linux】connect的断线重连

断线重连在我们日常生活中经常遇到,比如正在打游戏,此时服务器出现了问题,或者网络没了,客户端会主动重连几次服务器,如果重连都失败了,就可以放弃重连。在实现断线重连时,使用状态机的方式,实现tcp client断线重连的效果。所谓状态机,就是指根据客户端的状态来执行不同的操作。

我们设置如下的几种状态:

cpp 复制代码
enum class Status // C++11的强类型枚举
{
    NEW,          // 新建状态
    CONNECTING,   //正在连接
    CONNECTED,    //连接成功
    DISCONNECTED, //重连失败
    CLOSED        //连接失败,经历重连,无法连接
};

代码的主要框架如下:

cpp 复制代码
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:

cpp 复制代码
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:

cpp 复制代码
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即可:

cpp 复制代码
void Disconnect()
{
    if(_sockfd > defaultsockfd)
    {
        ::close(_sockfd);
        _sockfd = defaultsockfd;
        _status = Status::CLOSED;
    }
}

在Process中,做一个简单的echo服务,并根据send和recv的返回值改变_status:

cpp 复制代码
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;
        }
    }
}

到此,一个使用状态机的客户端断线重连完成。


完整代码:

cpp 复制代码
#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;
}
相关推荐
李小白2020020212 分钟前
Linux 生成/proc/config.gz
linux·服务器·前端
我们的五年20 分钟前
【Linux课程学习】:《简易版shell实现和原理》 《哪些命令可以让子进程执行,哪些命令让shell执行(内键命令)?为什么?》
linux·运维·服务器·学习
阿俊仔(摸鱼版)30 分钟前
GPT分区、格式化与自动挂载
linux·服务器·云计算
身如柳絮随风扬无论云泥意贯一32 分钟前
计算机网络 实验八 应用层相关协议分析
服务器·网络·计算机网络·wireshark
yaoxin52112332 分钟前
第三十三章 UDP 客户端 服务器通信 - IPv4 和 IPv6
服务器·网络协议·udp
康熙38bdc43 分钟前
Linux 线程互斥
linux·运维·开发语言
Wthzdq1 小时前
正则表达式(二)
linux·运维·服务器
逆风水手1 小时前
Python3交叉编译arm-linux放入设备中运行方式
linux·运维·arm开发
学习编程之路1 小时前
【Linux】软件包管理与vim工具使用详解
linux·运维·服务器·科技
PasteSpider1 小时前
新版本PasteSpider开发中专用部署工具介绍(让2GB的服务器也能使用CI/CD,简化你的部署过程)
运维·服务器·crud·pastetemplate