How do I cleanly reconnect a boost::socket following a disconnect?
Solution 1
You need to create a new boost::asio::ip::tcp::socket
each time you reconnect. The easiest way to do this is probably to just allocate the socket on the heap using a boost::shared_ptr
(you could probably also get away with scoped_ptr
if your socket is entirely encapsulated within a class). E.g.:
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);
// ...
}
Then, when mydisconnect
is called, you could deallocate the socket:
void MyClient::mydisconnect(void)
{
// ...
// deallocate socket. will close any open descriptors
socket.reset();
}
The error you're seeing is probably a result of the OS cleaning up the file descriptor after you've called close
. When you call close
and then try to connect
on the same socket, you're probably trying to connect an invalid file descriptor. At this point you should see an error message starting with "Connection failed: ..." based on your logic, but you then call mydisconnect
which is probably then attempting to call shutdown
on an invalid file descriptor. Vicious cycle!
Solution 2
For the sake of clarity here is the final approach I used (but this is based on bjlaub's answer, so please give any upvotes to him):
I declared the socket
member as a scoped_ptr
:
boost::scoped_ptr<boost::asio::ip::tcp::socket> socket;
Then I modified my connect
method to be:
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)
}
Note: this question was originally asked and answered back in 2010, but if you are now using C++11 or later then std::unique_ptr
would normally be a better choice than boost::scoped_ptr
Solution 3
Since C++11 you can write:
decltype(socket)(std::move(socket));
// reconnect socket.
The above creates a local instance of socket's type move constructing it from socket.
Before the next line, the unnamed local instance is destructed, leaving socket in a "freshly constructed from io_service" state.
Solution 4
I've done something similar using Boost.Asio in the past. I use the asynchronous methods, so a reconnect is typically letting my existing ip::tcp::socket object go out of scope, then creating a new one for calls to async_connect. If async_connect fails, I use a timer to sleep a bit then retry.
django11
I'm a Senior Software Engineer, currently based near Newcastle (England) and working remotely for a company based in Fife (Scotland). I've been programming professionally since 1997 in the usual variety of languages (C, C++, C#, Java, Python, VB, HTML/CSS, etc) working on everything from low-level bit-bashing device drivers and microprocessor-based firmware for safety-critical medical devices, up to full Windows applications and enterprise systems. I hate cucumbers. If you're interested then here is my linkedin.com profile. SOreadytohelp
Updated on August 17, 2020Comments
-
django11 over 3 years
My client application uses a
boost::asio::ip::tcp::socket
to connect to a remote server. If the app loses connection to this server (e.g. due to the server crashing or being shutdown) I would like it to attempt a re-connect at regular intervals until it succeeds.What do I need to do on the client-side to cleanly handle a disconnect, tidy up and then repeatedly attempt reconnects?
Currently the interesting bits of my code look something like this.
I
connect
like this: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) }
Where the
runIOServer()
method is just: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(); }
And if any of the async read handlers return an error then they just call this
disconnect
method: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(); }
..which attempts to gracefully disconnect and then notifies an observer, which will start calling
connect()
at five second intervals until it gets reconnected.Is there anything else I need to do?
Currently this does seem to work. If I kill the server that it is connected to I get the expected
"End of file"
error at my read handlers andmydisconnect()
is called without any issues.But when it then attempts to re-connect and fails I see it report
"socket.shutdown error: Invalid argument"
. Is this just because I am attempting to shutdown a socket that has no read/writes pending on it? Or is it something more?