Элегантный способ повторного подключения петли с boost :: asio? - PullRequest
0 голосов
/ 12 апреля 2019

Я пытаюсь написать очень элегантный способ обработки цикла повторного подключения с помощью 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?");

}

Как правильно начать переподключение каждый раз, когда соединение не удается (или обрывается)? Поскольку обработчик является статическим, я не могу изменить атрибут класса, который указывает на состояние соединения? Я хочу избежать использования хакерских обходных путей для глобальных переменных. Как правильно решить эту проблему?

Моя попытка будет выглядеть примерно так:

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();
    }
}

Проблема в том, что переподключение не работает должным образом, и приложение по какой-то причине зависает? Кроме того, переподключение также кажется проблематичным, как только соединение было установлено и затем сброшено (например, из-за сбоя / закрытия сервера).

1 Ответ

1 голос
/ 13 апреля 2019

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(); не вернется, покасоединение установлено.

...