Мое клиентское приложение использует a boost::asio::ip::tcp::socket
соединяться с удаленным сервером. Если бы приложение теряет соединение с этим сервером (например, из-за катастрофического отказа сервера или быть завершением работы), я хотел бы, чтобы это делало попытку повторно подключения равномерно, пока это не успешно выполняется.
То, что я должен сделать на клиентском, чтобы чисто обработать разъединение, убраться и затем неоднократно пытаться, снова соединяется?
В настоящее время интересные биты моего кода выглядят примерно так.
Я 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();
}
И если какой-либо из асинхронных обработчиков чтений возвращает ошибку затем, они просто называют это 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"
. Это просто, потому что я пытаюсь завершить работу сокета, который не имеет никакого чтения-записей, ожидающего на нем? Или это - что-то больше?
Вам необходимо создавать новый 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
, а затем пытаетесь подключить
к тому же сокету, вы, вероятно, пытаетесь подключить недопустимый файловый дескриптор. На этом этапе вы должны увидеть сообщение об ошибке, начинающееся с «Ошибка подключения: ...» в соответствии с вашей логикой, но затем вы вызываете mydisconnect
, который, вероятно, затем пытается вызвать shutdown
на недопустимый дескриптор файла. Порочный круг!
В прошлом я делал нечто подобное, используя Boost.Asio. Я использую асинхронные методы, поэтому переподключение обычно заключается в том, чтобы вывести существующий объект ip::tcp::socket из области видимости, а затем создать новый для вызова async_connect. Если async_connect не удается, я использую таймер, чтобы немного поспать, а затем повторить попытку.
Для ясности вот окончательный подход, который я использовал (но он основан на ответе 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)
}
Примечание: этот вопрос был первоначально задан и отвечен еще в 2010 году, но если вы сейчас используете C++11 или более поздние версии, то std::unique_ptr
обычно будет лучшим выбором, чем boost::scoped_ptr