3

I currently use Windows 7 64bit, MSVC2010 and Boost.Asio 1.57. I would like to connect to a TCP server with a timeout. If the timeout expires, I should close the connection as soon as possible as the IP address (chosen by a user) is probably wrong.

I know I should use async requests because sync requests have no timeouts options included. So I'm using async_connect with an external timeout. This is a solution I have found in many places, including stackoverflow.

The problem is that the following code does not behave like I wished. async_connect is not "cancelled" by the socket.close(). With my computer, closing the socket takes about 15 seconds to complete, which makes my program not responsive for a while... I would like to have a decent timeout (approx. 3 seconds) and close the socket after this time, so that the user can try to connect with another IP address (from the HMI)

#include <iostream>
#include <boost\asio.hpp>
#include <boost\shared_ptr.hpp>
#include <boost\bind.hpp>

using boost::asio::ip::tcp;

class tcp_client
{
public:
    tcp_client(boost::asio::io_service& io_service, tcp::endpoint& endpoint, long long timeout = 3000000) 
        :m_io_service (io_service),
        m_endpoint(endpoint),
        m_timer(io_service),
        m_timeout(timeout)
    {
        connect();
    }

    void stop()
    {
        m_socket->close();
    }

private:

    void connect()
    {
        m_socket.reset(new tcp::socket(m_io_service));

        std::cout << "TCP Connection in progress" << std::endl;
        m_socket->async_connect(m_endpoint,
            boost::bind(&tcp_client::handle_connect, this,
            m_socket,
            boost::asio::placeholders::error)
            );

        m_timer.expires_from_now(boost::posix_time::microseconds(m_timeout));
        m_timer.async_wait(boost::bind(&tcp_client::HandleWait, this, boost::asio::placeholders::error));
    }

    void handle_connect(boost::shared_ptr<tcp::socket> socket, const boost::system::error_code& error)
    {
        if (!error)
        {
            std::cout << "TCP Connection : connected !" << std::endl;
            m_timer.expires_at(boost::posix_time::pos_infin); // Stop the timer !
            // Read normally
        }
        else
        {
            std::cout << "TCP Connection failed" << std::endl;
        }
    }


public:
    void HandleWait(const boost::system::error_code& error)
    {
        if (!error)
        {
            std::cout << "Connection not established..." << std::endl;
            std::cout << "Trying to close socket..." << std::endl;
            stop();
            return;
        }
    }

    boost::asio::io_service&        m_io_service;
    boost::shared_ptr<tcp::socket>  m_socket;
    tcp::endpoint                   m_endpoint;
    boost::asio::deadline_timer     m_timer;
    long long                       m_timeout;
};

int main()
{
    boost::asio::io_service io_service;
    tcp::endpoint endpoint(boost::asio::ip::address_v4::from_string("192.168.10.74"), 7171); // invalid address
    tcp_client tcpc(io_service, endpoint);

    io_service.run();

    system("pause");
}

The only solution I found is to run io_service:run() in many threads, and create a new socket for each connection. But this solution does not appear valid to me as I have to specify a number of threads and I don't know how many wrong address the user will enter in my HMI. Yes, some users are not as clever as others...

What's wrong with my code ? How do I interrupt a TCP connection in a clean and fast way ?

Best regards, Poukill

Tanner Sansbury
  • 51,153
  • 9
  • 112
  • 169
poukill
  • 540
  • 8
  • 18
  • 1
    It's generally better to use `/` instead of `\` in your code; `/` is more portable (`/` works on Windows, while `\` doesn't work on Unixes). – Griwes Dec 29 '14 at 15:49

1 Answers1

2

There's nothing elementary wrong with the code, and it does exactly what you desire on my Linux box:

TCP Connection in progress
Connection not established...
Trying to close socket...
TCP Connection failed

real    0m3.003s
user    0m0.002s
sys 0m0.000s

Notes:

  1. You may have success adding a cancel() call to the stop() function:

    void stop()
    {
        m_socket->cancel();
        m_socket->close();
    }
    
  2. You should check for abortion of the timeout though:

    void HandleWait(const boost::system::error_code& error)
    {
        if (error && error != boost::asio::error::operation_aborted)
        {
            std::cout << "Connection not established..." << std::endl;
            std::cout << "Trying to close socket..."     << std::endl;
            stop();
            return;
        }
    }
    

    Otherwise the implicit cancel of the timer after successful connect will still close() the socket :)

  3. If you want to run (many) connection attempts in parallel, you don't need any more threads or even more than one io_service. This is the essence of Boost Asio: you can do asynchronous IO operations on a single thread.

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • 1. Cancel does not help 2. Thanks for the tip 3. I know quite well how boost.Asio works. I have always been using only one thread running io_service::run. But for now this is the only "trick" that saved me. – poukill Dec 29 '14 at 15:29
  • @poukill sadly you might be looking at an operating system specific glitch/difference. I might try later when I have access to a windows machine (might take a while though) – sehe Dec 29 '14 at 15:30
  • Well, maybe. I tried both MSVC2013 and MSVC2010, I have the same result. I tried Boost 1.55 and the result was quite different. socket.close() returns immediately, like yours did. But it took 15 seconds for the program to exit. The tcp::socket dtor I guess. So Boost 1.55 is better for me, but not that much. Maybe I will open a ticket on that particular issue. – poukill Dec 29 '14 at 15:39
  • Boost 1.55 works good in my case. socket.close() returns immediately, so I guess this is a regression in boost 1.57. I have opened a ticket. Thanks sehe for the help. – poukill Dec 30 '14 at 11:18