boost::asio::ip::tcp::socket is connected?

24,758

Solution 1

TCP is meant to be robust in the face of a harsh network; even though TCP provides what looks like a persistent end-to-end connection, it's all just a lie, each packet is really just a unique, unreliable datagram.

The connections are really just virtual conduits created with a little state tracked at each end of the connection (Source and destination ports and addresses, and local socket). The network stack uses this state to know which process to give each incoming packet to and what state to put in the header of each outgoing packet.

Virtual TCP Conduit

Because of the underlying — inherently connectionless and unreliable — nature of the network, the stack will only report a severed connection when the remote end sends a FIN packet to close the connection, or if it doesn't receive an ACK response to a sent packet (after a timeout and a couple retries).

Because of the asynchronous nature of asio, the easiest way to be notified of a graceful disconnection is to have an outstanding async_read which will return error::eof immediately when the connection is closed. But this alone still leaves the possibility of other issues like half-open connections and network issues going undetected.

The most effectively way to work around unexpected connection interruption is to use some sort of keep-alive or ping. This occasional attempt to transfer data over the connection will allow expedient detection of an unintentionally severed connection.

The TCP protocol actually has a built-in keep-alive mechanism which can be configured in asio using asio::tcp::socket::keep_alive. The nice thing about TCP keep-alive is that it's transparent to the user-mode application, and only the peers interested in keep-alive need configure it. The downside is that you need OS level access/knowledge to configure the timeout parameters, they're unfortunately not exposed via a simple socket option and usually have default timeout values that are quite large (7200 seconds on Linux).

Probably the most common method of keep-alive is to implement it at the application layer, where the application has a special noop or ping message and does nothing but respond when tickled. This method gives you the most flexibility in implementing a keep-alive strategy.

Solution 2

TCP promises to watch for dropped packets -- retrying as appropriate -- to give you a reliable connection, for some definition of reliable. Of course TCP can't handle cases where the server crashes, or your Ethernet cable falls out or something similar occurs. Additionally, knowing that your TCP connection is up doesn't necessarily mean that a protocol that will go over the TCP connection is ready (eg., your HTTP webserver or your FTP server may be in some broken state).

If you know the protocol being sent over TCP then there is probably a way in that protocol to tell you if things are in good shape (for HTTP it would be a HEAD request)

Solution 3

If you are sure that the remote socket has not sent anything (e.g. because you haven't sent a request to it yet), then you can set your local socket to a non blocking mode and try to read one or more bytes from it.

Given that the server hasn't sent anything, you'll either get a asio::error::would_block or some other error. If former, your local socket has not yet detected a disconnection. If latter, your socket has been closed.

Here is an example code:

#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/steady_timer.hpp>

using namespace std;
using namespace boost;
using tcp = asio::ip::tcp;

template<class Duration>
void async_sleep(asio::io_service& ios, Duration d, asio::yield_context yield)
{
  auto timer = asio::steady_timer(ios);
  timer.expires_from_now(d);
  timer.async_wait(yield);
}

int main()
{
  asio::io_service ios;
  tcp::acceptor acceptor(ios, tcp::endpoint(tcp::v4(), 0));

  boost::asio::spawn(ios, [&](boost::asio::yield_context yield) {
    tcp::socket s(ios);
    acceptor.async_accept(s, yield);
    // Keep the socket from going out of scope for 5 seconds.
    async_sleep(ios, chrono::seconds(5), yield);
  });

  boost::asio::spawn(ios, [&](boost::asio::yield_context yield) {
    tcp::socket s(ios);
    s.async_connect(acceptor.local_endpoint(), yield);

    // This is essential to make the `read_some` function not block.
    s.non_blocking(true);

    while (true) {
      system::error_code ec;
      char c;
      // Unfortunately, this only works when the buffer has non
      // zero size (tested on Ubuntu 16.04).
      s.read_some(asio::mutable_buffer(&c, 1), ec);
      if (ec && ec != asio::error::would_block) break;
      cerr << "Socket is still connected" << endl;
      async_sleep(ios, chrono::seconds(1), yield);
    }

    cerr << "Socket is closed" << endl;
  });

  ios.run();
}

And the output:

Socket is still connected
Socket is still connected
Socket is still connected
Socket is still connected
Socket is still connected
Socket is closed

Tested on:

Ubuntu: 16.04
Kernel: 4.15.0-36-generic
Boost: 1.67

Though, I don't know whether or not this behavior depends on any of those versions.

Share:
24,758

Related videos on Youtube

coelhudo
Author by

coelhudo

Updated on June 24, 2021

Comments

  • coelhudo
    coelhudo almost 3 years

    I want to verify the connection status before performing read/write operations.

    Is there a way to make an isConnect() method?

    I saw this, but it seems "ugly".

    I have tested is_open() function as well, but it doesn't have the expected behavior.

  • jmucchiello
    jmucchiello over 14 years
    But what happens to the other end when it receives this dummy byte if you are connected. That's not a good solution.
  • vehomzzz
    vehomzzz over 14 years
    Usually this sort of application have concept of heartbeat send periodically. Moreover, on windows (win32) doesn't have a way to check the connection reliably, and boost::asio::ip::tcp::socket would be implemented on top of it. @jmucchiello -- if you have ever worked on a distributed systems, you would have known that connection is always checked via a heartbeat, which is typically a byte. Hope this helps the OP.
  • coelhudo
    coelhudo over 14 years
    There is one problem, i don't know the behavior on the server side. The solution that i found, is call connect before operations and check error condition.
  • vehomzzz
    vehomzzz over 14 years
    You need to know the server's protocol (look in the documentation or ask a vendor); Trying to connect can be very expensive.
  • jmucchiello
    jmucchiello over 14 years
    Andrei: His socket could be the client side of a HTTP/1.1 Keep Alive connection. Does that protocol have a "send a byte" heartbeat? I've only been working with distributed systems for 20+ years and not all of them have heartbeats. Some of them, like HTTP/1.1, use a "reconnect on error" method. Your answer says "send a dummy byte" without including any of the caveats found in your comments. It is not the correct answer. Your comment says "you need to know the protocol". Please make this your actual answer as the current one is flawed.
  • coelhudo
    coelhudo over 14 years
    There is one problem with keep-alive aproach: I don't have access to the servers where the application will run, therefore I can't change the keep-alive timeout (7200 seconds here). But I like this response, so if I need this again I will enable TCP keel-alive. Thanks
  • coelhudo
    coelhudo over 14 years
    I forgot to say what I did, I just try to read/write, check the error and make a decision based on this error.
  • coelhudo
    coelhudo over 12 years
    The image is not appearing anymore, can you fix this please? I would like to see the illustration. Thanks.
  • John
    John over 2 years
    @joshperry Does the default timeout values have to be modified both on the client end and the server end?
  • John
    John over 2 years
    @vehomzzz "on windows (win32) doesn't have a way to check the connection reliably"? Any reference?

Related