【问题标题】:Elegant way of reconnecting loop with boost::asio?用 boost::asio 重新连接循环的优雅方式?
【发布时间】:2019-09-02 16:35:57
【问题描述】:

我正在尝试使用 boost async_connect(...) 编写一种非常优雅的方法来处理重新连接循环。问题是,我没有办法优雅地解决以下问题:

我有一个 TCP 客户端,它应该尝试异步连接到服务器,如果由于服务器脱机或发生任何其他错误而导致连接失败,请等待给定的时间并尝试重新连接。这里有很多事情需要考虑:

  • 尽可能避免使用全局变量
  • 必须是异步连接

一个非常基本的客户端是这样实例化的:

tcpclient::tcpclient(std::string host, int port) : _endpoint(boost::asio::ip::address::from_string(host), port), _socket(_ios) {
    logger::log_info("Initiating client ...");
}

尝试连接服务器:

void tcpclient::start() {

    bool is_connected = false;

    while (!is_connected) {
        _socket.async_connect(_endpoint, connect_handler);
        _ios.run();
    }

    // read write data (?)

}

处理程序:

void tcpclient::connect_handler(const boost::system::error_code &error) {

    if(error){
        // trigger disconnect (?)
        logger::log_error(error.message());
        return;
    }

    // Connection is established at this point
    // Update timer state and start authentication on server ?

    logger::log_info("Connected?");

}

每次连接失败(或断开)时,如何正确开始重新连接?由于处理程序是静态的,我不能修改指示连接状态的类属性?我想避免使用 hacky 的全局变量解决方法。 我怎样才能以适当的方式解决这个问题?

我的尝试是这样的:

tcpclient.h

enum ConnectionStatus{
    NOT_CONNECTED,
    CONNECTED
};


class tcpclient {

public:

    tcpclient(std::string host, int port);

    void start();

private:

    ConnectionStatus _status = NOT_CONNECTED;

    void connect_handler(const boost::system::error_code& error);

    boost::asio::io_service _ios;
    boost::asio::ip::tcp::endpoint _endpoint;
    boost::asio::ip::tcp::socket _socket;

};

tcpclient.cpp

#include "tcpclient.h"
#include <boost/chrono.hpp>
#include "../utils/logger.h"

tcpclient::tcpclient(std::string host, int port) : _endpoint(boost::asio::ip::address::from_string(host), port),
                                                   _socket(_ios) {
    logger::log_info("Initiating client ...");
    logger::log_info("Server endpoint: " + _endpoint.address().to_string());
}


void tcpclient::connect_handler(const boost::system::error_code &error) {

    if(!error){
        _status = CONNECTED;
        logger::log_info("Connected.");
    }
    else{
        _status = NOT_CONNECTED;
        logger::log_info("Failed to connect");
        _socket.close();
    }

}

void tcpclient::start() {

    while (_status == NOT_CONNECTED) {
        std::this_thread::sleep_for(std::chrono::milliseconds(2000));
        _socket.close();
        _socket.async_connect(_endpoint, std::bind(&tcpclient::connect_handler, this, std::placeholders::_1));
        _ios.run();
    }
}

问题是重新连接无法正常工作并且应用程序似乎由于某种原因冻结?除此之外,一旦建立连接然后断开连接(例如由于服务器崩溃/关闭),重新连接似乎也会出现问题。

【问题讨论】:

    标签: c++ asynchronous boost tcp asio


    【解决方案1】:

    std::this_thread::sleep_for(std::chrono::milliseconds(2000)); 将冻结程序 2 秒。您可以在这里做的是在连接尝试失败时启动异步计时器:

    ::boost::asio::steady_timer m_timer{_ios, boost::asio::chrono::seconds{2}};
    
    void tcpclient::connect_handler(const boost::system::error_code &error)
    {
        if(!error)
        { 
            _status = CONNECTED;
            logger::log_info("Connected.");
        }
        else
        {
            _status = NOT_CONNECTED;
            logger::log_info("Failed to connect");
           _socket.close();
           m_timer.expires_from_now(boost::asio::chrono::seconds{2});
           m_timer.async_wait(std::bind(&tcpclient::on_ready_to_reconnect, this, std::placeholders::_1));
        }
    }
    
    void tcpclient::on_ready_to_reconnect(const boost::system::error_code &error)
    {
        try_connect();
    }
    
    void tcpclient::try_connect()
    {
        m_socket.async_connect(_endpoint, std::bind(&tcpclient::connect_handler, this, std::placeholders::_1));
    }
    
    void tcpclient::start()
    {
        try_connect();
        _ios.run();
    }
    

    也不需要while (_status == NOT_CONNECTED)循环,因为io服务会很忙,_ios.run();在连接建立之前不会返回。

    【讨论】:

    • 非常感谢您的回复,非常有帮助!我对你的例子有疑问。 ::boost::asio::steady_timer m_timer{m::boost::asio::chrono::seconds{2}}; m 来自哪里?我错过了一些包括吗?我尝试删除它,但随后提示我没有匹配的构造函数?另外我会把这个变量作为私有变量放在头文件中,对吧?而不是全局变量。
    • @Kyu96 那 m 只是一个随机的错字。 timer 也将 io 服务作为第一个参数。
    • 我明白了。非常感谢您的快速回复!但是,我仍然担心两件事:1)尝试连接时,第一次尝试会等待一段时间,但之后会发送垃圾邮件无法在控制台中连接而没有任何暂停?除了第一次尝试外,它似乎忽略了计时器的 2 秒。 2)现在程序一旦连接就退出。但是,如果连接建立然后随机断开怎么办。这也会触发重新连接例程吗?
    • @Kyu96 1) 哦,对了,定时器回调被调用后定时器将处于过期状态,所以它应该在调用async_wait之前重置。 2)如果程序在某个时候断开连接,则应手动重新建立连接(可能通过在某处调用try_connect)。
    • @Kyu96 为这些问题创建一个单独的主题会更好。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-07
    • 2023-01-21
    • 1970-01-01
    • 2023-03-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多