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





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(); не вернется, пока соединение не будет установлено.
@ Kyu96 Это m было просто случайной опечаткой. timer также принимает службу ввода-вывода в качестве первого параметра.
Понимаю. Большое спасибо за ваш быстрый ответ! Однако меня все еще беспокоят две вещи: 1) При попытке подключения он некоторое время ждет первой попытки, но после этого начинает спамить. Не удалось подключиться в консоли без паузы? Кажется, игнорируются 2 секунды таймера, за исключением самой первой попытки. 2) Прямо сейчас программа завершает работу после подключения. Но что делать, если соединение установлено, а затем случайно разорвано. Это также вызовет процедуру повторного подключения?
@ Kyu96 1) О, правильно, после вызова обратного вызова таймера таймер будет в состоянии с истекшим сроком действия, поэтому его следует сбросить перед вызовом async_wait. 2) Если в какой-то момент программа отключается, то соединение следует восстановить вручную (возможно, где-то вызвав try_connect).
Спасибо за объяснение! Еще несколько вещей, которые я хотел бы спросить: я хочу реализовать правильную базу для отправки и получения пользовательских пакетов данных. 1) Имеет ли смысл добавить два отдельных обработчика для чтения и записи, один для заголовка, другой для тела? 2) Как я могу гарантировать, что я могу получать данные, а также отправлять данные, не вызывая конфликтов?
3) Поскольку отправляемые и получаемые данные должны соответствовать определенному формату, должен ли я добавить обработчики как для получения заголовка и тела, так и для отправки заголовка и тела? 4) Как бы я отправил такой пользовательский пакет данных? Структура? Предоставляет ли boost какой-либо сериализатор или что-то подобное? Может быть, вы приведете мне очень простой пример? Кстати, я принял ваш ответ, так как на главный вопрос был дан ответ.
@Kyu96 Было бы лучше создать отдельные темы для этих вопросов.
Честно говоря, я создал новую тему для этого вопроса: stackoverflow.com/questions/55676585/…
Большое спасибо за ваш ответ, очень помогает!! У меня вопрос по вашему примеру.
::boost::asio::steady_timer m_timer{m::boost::asio::chrono::seconds{2}};Откуда взялосьm? Я пропустил некоторые включения? Я попытался удалить его, но затем мне было предложено указать, что нет соответствующего конструктора? Также я бы поместил эту переменную в файл заголовка как приватную переменную, верно? Вместо глобальной переменной.