Как я могу повторно подключить boost:: socket после отключения?
В моем клиентском приложении используется boost::asio::ip::tcp::socket
для подключения к удаленному серверу.
Если приложение теряет соединение с этим сервером (например, из-за сбоя или завершения работы сервера), я хотел бы, чтобы он пытался повторно подключиться через равные промежутки времени, пока он не удастся.
Что мне нужно делать на стороне клиента, чтобы очистить отключение, убрать, а затем повторно пытаться переподключиться?
В настоящее время интересные фрагменты моего кода выглядят примерно так.
I connect
следующим образом:
bool MyClient::myconnect()
{
bool isConnected = false;
// Attempt connection
socket.connect(server_endpoint, errorcode);
if (errorcode)
{
cerr << "Connection failed: " << errorcode.message() << endl;
mydisconnect();
}
else
{
isConnected = true;
// Connected so setup async read for an incoming message.
startReadMessage();
// And start the io_service_thread
io_service_thread = new boost::thread(
boost::bind(&MyClient::runIOService, this, boost::ref(io_service)));
}
return (isConnected)
}
Где метод runIOServer()
справедлив:
void MyClient::runIOService(boost::asio::io_service& io_service)
{
size_t executedCount = io_service.run();
cout << "io_service: " << executedCount << " handlers executed." << endl;
io_service.reset();
}
И если какой-либо из обработчиков чтения async возвращает ошибку, они просто называют этот метод disconnect
:
void MyClient::mydisconnect(void)
{
boost::system::error_code errorcode;
if (socket.is_open())
{
// Boost documentation recommends calling shutdown first
// for "graceful" closing of socket.
socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, errorcode);
if (errorcode)
{
cerr << "socket.shutdown error: " << errorcode.message() << endl;
}
socket.close(errorcode);
if (errorcode)
{
cerr << "socket.close error: " << errorcode.message() << endl;
}
// Notify the observer we have disconnected
myObserver->disconnected();
}
.., который пытается изящно отключить и затем уведомить наблюдателя, который начнет вызывать connect()
с интервалом в пять секунд, пока он не будет повторно подключен.
Есть ли что-нибудь еще, что мне нужно сделать?
В настоящее время это работает. Если я убью сервер, к которому он подключен, я получаю ожидаемую ошибку "End of file"
у моих обработчиков чтения и mydisconnect()
вызывается без каких-либо проблем.
Но когда он пытается снова подключиться и не работает, я вижу отчет "socket.shutdown error: Invalid argument"
. Это только потому, что я пытаюсь отключить сокет, который не читает/пишет в ожидании? Или это нечто большее?
Ответы
Ответ 1
Вам нужно создать новый boost::asio::ip::tcp::socket
при каждом повторном подключении. Самый простой способ сделать это, вероятно, просто выделить сокет в куче, используя boost::shared_ptr
(вы, возможно, также можете уйти с scoped_ptr
, если ваш сокет полностью инкапсулирован в класс). Например:.
bool MyClient::myconnect()
{
bool isConnected = false;
// Attempt connection
// socket is of type boost::shared_ptr<boost::asio::ip::tcp::socket>
socket.reset(new boost::asio::ip::tcp::socket(...));
socket->connect(server_endpoint, errorcode);
// ...
}
Затем, когда вызывается mydisconnect
, вы можете освободить сокет:
void MyClient::mydisconnect(void)
{
// ...
// deallocate socket. will close any open descriptors
socket.reset();
}
Ошибка, которую вы видите, вероятно, является результатом того, что ОС очистит дескриптор файла после того, как вы вызвали close
. Когда вы вызываете close
, а затем пытаетесь выполнить connect
в том же сокете, вероятно, вы пытаетесь подключить неверный дескриптор файла. На этом этапе вы увидите сообщение об ошибке, начинающееся с "Connection failed:..." на основе вашей логики, но затем вы вызываете mydisconnect
, который, вероятно, затем пытается вызвать shutdown
в недопустимом дескрипторе файла. Порочный цикл!
Ответ 2
Для ясности здесь используется заключительный подход, который я использовал (но это основано на ответе bjlaub, поэтому, пожалуйста, дайте ему какие-либо варианты):
Я объявил элемент socket
как scoped_ptr
:
boost::scoped_ptr<boost::asio::ip::tcp::socket> socket;
Затем я изменил свой метод connect
следующим образом:
bool MyClient::myconnect()
{
bool isConnected = false;
// Create new socket (old one is destroyed automatically)
socket.reset(new boost::asio::ip::tcp::socket(io_service));
// Attempt connection
socket->connect(server_endpoint, errorcode);
if (errorcode)
{
cerr << "Connection failed: " << errorcode.message() << endl;
socket->close();
}
else
{
isConnected = true;
// Connected so setup async read for an incoming message.
startReadMessage();
// And start the io_service_thread
io_service_thread = new boost::thread(
boost::bind(&MyClient::runIOService, this, boost::ref(io_service)));
}
return (isConnected)
}
Ответ 3
Я сделал что-то подобное, используя Boost.Asio в прошлом. Я использую асинхронные методы, поэтому повторное подключение обычно позволяет моему существующему объекту ip:: tcp:: socket выйти из области видимости, а затем создать новый для вызовов async_connect. Если async_connect терпит неудачу, я использую таймер, чтобы немного поспать, а затем повторить попытку.
Ответ 4
Я пробовал использовать метод close() и метод shutdown, и для меня это просто сложно. Close() может выдать ошибку, которую вам нужно поймать, и является грубым способом делать то, что вы хотите:) и shutdown(), кажется, лучше, но на многопоточном программном обеспечении, я считаю, что это может быть суетливо. Так что лучший способ, как сказал Сэм, позволить ему выйти из сферы действия. Если сокет является членом класса, вы можете: 1) перепроектировать так, чтобы класс использовал объект "соединение", чтобы обернуть сокет и позволить ему выйти из области видимости или 2) обернуть его в интеллектуальный указатель и reset умный указатель. Если вы используете boost, в том числе shared_ptr, это дешево и работает как шарм. Никогда не было проблемы с очисткой сокета, делающей это с помощью shared_ptr. Только мой опыт.