Элегантный способ повторного подключения цикла с помощью boost:: asio?

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

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
1 346
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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 13.04.2019 19:19

@ Kyu96 Это m было просто случайной опечаткой. timer также принимает службу ввода-вывода в качестве первого параметра.

user7860670 13.04.2019 19:33

Понимаю. Большое спасибо за ваш быстрый ответ! Однако меня все еще беспокоят две вещи: 1) При попытке подключения он некоторое время ждет первой попытки, но после этого начинает спамить. Не удалось подключиться в консоли без паузы? Кажется, игнорируются 2 секунды таймера, за исключением самой первой попытки. 2) Прямо сейчас программа завершает работу после подключения. Но что делать, если соединение установлено, а затем случайно разорвано. Это также вызовет процедуру повторного подключения?

Kyu96 13.04.2019 21:02

@ Kyu96 1) О, правильно, после вызова обратного вызова таймера таймер будет в состоянии с истекшим сроком действия, поэтому его следует сбросить перед вызовом async_wait. 2) Если в какой-то момент программа отключается, то соединение следует восстановить вручную (возможно, где-то вызвав try_connect).

user7860670 13.04.2019 21:21

Спасибо за объяснение! Еще несколько вещей, которые я хотел бы спросить: я хочу реализовать правильную базу для отправки и получения пользовательских пакетов данных. 1) Имеет ли смысл добавить два отдельных обработчика для чтения и записи, один для заголовка, другой для тела? 2) Как я могу гарантировать, что я могу получать данные, а также отправлять данные, не вызывая конфликтов?

Kyu96 14.04.2019 01:51

3) Поскольку отправляемые и получаемые данные должны соответствовать определенному формату, должен ли я добавить обработчики как для получения заголовка и тела, так и для отправки заголовка и тела? 4) Как бы я отправил такой пользовательский пакет данных? Структура? Предоставляет ли boost какой-либо сериализатор или что-то подобное? Может быть, вы приведете мне очень простой пример? Кстати, я принял ваш ответ, так как на главный вопрос был дан ответ.

Kyu96 14.04.2019 01:51

@Kyu96 Было бы лучше создать отдельные темы для этих вопросов.

user7860670 14.04.2019 10:49

Честно говоря, я создал новую тему для этого вопроса: stackoverflow.com/questions/55676585/…

Kyu96 14.04.2019 16:51

Другие вопросы по теме