【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;
}
相关推荐
AI逐月1 小时前
tmux 常用命令总结:从入门到稳定使用的一篇实战博客
linux·服务器·ssh·php
想逃离铁厂的老铁1 小时前
Day55 >> 并查集理论基础 + 107、寻找存在的路线
java·服务器
小白跃升坊1 小时前
基于1Panel的AI运维
linux·运维·人工智能·ai大模型·教学·ai agent
杨江2 小时前
seafile docker安装说明
运维
舰长1152 小时前
linux 实现文件共享的实现方式比较
linux·服务器·网络
好好沉淀2 小时前
Docker开发笔记(详解)
运维·docker·容器
zmjjdank1ng2 小时前
Linux 输出重定向
linux·运维
路由侠内网穿透.2 小时前
本地部署智能家居集成解决方案 ESPHome 并实现外部访问( Linux 版本)
linux·运维·服务器·网络协议·智能家居
树℡独2 小时前
ns-3仿真之应用层(三)
运维·服务器·ns3
VekiSon2 小时前
Linux内核驱动——基础概念与开发环境搭建
linux·运维·服务器·c语言·arm开发