Search code examples
c++boost-asioresolve

Boost::ASIO resolve muliple addresses


I'm having some problems resolving multiple addresses using Boost::ASIO. I can call async_resolve once, get an endpoint, and connect to it without issues. But if I want to call async_resolve again at a later point, async_resolve hangs forever and never calls my event handler.

Is there a way to finish or close the previous async_resolve? Or is it something else?

hostResolver = new boost::asio::ip::tcp::resolver(*internalEventPump->getIoService());
    
hostResolver->async_resolve(boost::asio::ip::tcp::v4(), hostName, portString,
    boost::bind(&DTcpSocket::resolveFinished, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator));

...

void DTcpSocket::resolveFinished(const boost::system::error_code &errorCode,
                                 boost::asio::ip::tcp::resolver::iterator iterator)
{
    endPointIter = iterator;

    if (errorCode)
    {
        errMsg = "Couldn't resolve hostname: " + errorCode.message();
        connectionHandler(errMsg, DA_HOSTNAME_ERROR);
    }
    else
    {
        boost::asio::ip::tcp::endpoint endpoint = *endPointIter;
        cout << endpoint.address() << endl;

        endPointIter++;//we'll try to connect to this endpoint if the first endpoint failed.
        clientSocket->async_connect(endpoint, boost::bind(&DTcpSocket::connectFinished, this,
                                                          boost::asio::placeholders::error));
    }
}//resolveFinished

Solution

  • It was indeed a problem that could be solved with work_guard, as per sehe's comment. Complete example demonstrating the issue, with fix in comment:

    #include <boost/asio.hpp>
    #include <boost/bind/bind.hpp>
    #include <chrono>
    #include <iostream>
    #include <thread>
    
    using namespace std;
    
    static boost::asio::ip::tcp::resolver* HostResolver = nullptr;
    static boost::asio::ip::tcp::resolver::iterator EndPointIter = boost::asio::ip::tcp::resolver::iterator();
    static boost::asio::ip::tcp::socket* ClientSocket = nullptr;
    static bool ConnectInProgress = false;
    static const int MAX_ASIO_EVENTS = 1000;
    static const unsigned int BUFFER_SIZE = 16384;
    static unsigned char* ReceiveBuffer;
    
    void StartConnect();
    void ResolveFinished(const boost::system::error_code &errorCode, boost::asio::ip::tcp::resolver::iterator iterator);
    void ConnectionFinished(const boost::system::error_code &errorCode);
    void ConnectFinished(const boost::system::error_code &errorCode);
    void ReceiveFinished(const boost::system::error_code &errorCode, size_t bytesReceived);
    bool Close();
    bool CurrentlyConnected();
    
    int main(int argc,
             char* argv[])
    {
        ReceiveBuffer = new unsigned char[BUFFER_SIZE];
        boost::asio::io_service* ioService = new boost::asio::io_service;
        ClientSocket = new boost::asio::ip::tcp::socket(*ioService);
        HostResolver = new boost::asio::ip::tcp::resolver(*ioService);
        
        //The fix:
        //boost::asio::executor_work_guard<decltype(ioService->get_executor())> work{ioService->get_executor()};
     
        StartConnect();
        
        while (true)
        {    
            int eventsRun = 0;
                
            try
            {
                while (ioService->poll_one() > 0 && eventsRun < MAX_ASIO_EVENTS)
                    eventsRun++;
            
            }
            catch (exception &e)
            {
                cerr << "ASIO error " << e.what() << endl;
                return 1;
            }
            
            if (!ConnectInProgress)
            {
                if (!CurrentlyConnected())
                    StartConnect();
            }
            
            if (eventsRun == 0)            
                this_thread::sleep_for(chrono::milliseconds(1));
        }
        
        delete ClientSocket;
        delete HostResolver;
        delete ioService;
        delete[] ReceiveBuffer;
        
        return 0;
    }//main
    
    bool CurrentlyConnected()
    {
        boost::system::error_code errorCode;//error code returned by boost
    
        if (ClientSocket->is_open())
        {
            ClientSocket->remote_endpoint(errorCode);
            if (!errorCode)
                return true;
        }
        
        return false;
    }//CurrentlyConnected
    
    void StartConnect()
    {
        this_thread::sleep_for(chrono::milliseconds(2000));
        ConnectInProgress = true;
        cout << "Running async_resolve..." << endl;
        HostResolver->async_resolve(boost::asio::ip::tcp::v4(), "127.0.0.1", "5127", &ResolveFinished);
    }//StartConnect
    
    void ResolveFinished(const boost::system::error_code &errorCode,
                         boost::asio::ip::tcp::resolver::iterator iterator)
    {
        cout << "ResolveFinished() called" << endl;
        EndPointIter = iterator;
    
        if (errorCode)
        {
            cout << "Couldn't resolve hostname: " << errorCode.message() << endl;
            ConnectInProgress = false;
        }
        else
        {
            boost::asio::ip::tcp::endpoint endpoint = *EndPointIter;
            EndPointIter++;
            
            cout << "Running async_connect to " << endpoint.address() << endl;
            ClientSocket->async_connect(endpoint, &ConnectFinished);
        }
    }//ResolveFinished
    
    void ConnectFinished(const boost::system::error_code &errorCode)
    {
        cout << "ConnectFinished() called" << endl;
    
        boost::asio::ip::tcp::endpoint endpoint;//the IP address and port number to connect to
    
        //### Try a different endpoint if we couldn't connect ###
        if (errorCode)
        {
            if (errorCode != boost::asio::error::operation_aborted)
            {
                //Verify that there's another endpoint
                if (EndPointIter == boost::asio::ip::tcp::resolver::iterator())
                {
                    cout << "Couldn't connect: " + errorCode.message() << endl;
                    ConnectInProgress = false;
                    return;
                }
    
                //Close the socket
                if (!Close())
                {
                    ConnectInProgress = false;
                    return;
                }
    
                //Start another asynchronous connect
                endpoint = *EndPointIter;
                EndPointIter++;
    
                cout << "Running async_connect to " << endpoint.address() << endl;
                ClientSocket->async_connect(endpoint, &ConnectFinished);
            }
        }
    
        //### We connected successfully ###
        else
        {
            cout << "We connected!" << endl;
            ConnectInProgress = false;
            ClientSocket->async_receive(boost::asio::buffer(ReceiveBuffer, BUFFER_SIZE), &ReceiveFinished);
        }
    }//ConnectFinished
    
    void ReceiveFinished(const boost::system::error_code &errorCode,
                         size_t bytesReceived)
    {
        if (errorCode)
        {
            if (errorCode != boost::asio::error::operation_aborted)
            {
                cout << errorCode.message() << endl;
                Close();
            }
        }
        else
        {
            if (bytesReceived > 0)
                ClientSocket->async_receive(boost::asio::buffer(ReceiveBuffer, BUFFER_SIZE), &ReceiveFinished);
    
            else
            {
                cout << "Received zero bytes." << endl;
                Close();
            }
        }
    }//ReceiveFinished
    
    bool Close()
    {
        boost::system::error_code errorCode;
    
        cout << "Close() called" << endl;
    
        if (ClientSocket->is_open())
        {
            ClientSocket->shutdown(boost::asio::ip::tcp::socket::basic_stream_socket::shutdown_both, errorCode);
            if (errorCode)
            {
                cout << "Got an error invoking shutdown()" << endl;
                boost::system::error_condition theError = errorCode.default_error_condition();
    
                if (theError.value() != boost::system::errc::not_connected)
                {
                    cout << "Couldn't shutdown socket: " << theError.message() << endl;
                    return false;
                }
            }
    
            ClientSocket->close(errorCode);
            if (errorCode)
            {
                boost::system::error_condition theError = errorCode.default_error_condition();
    
                cout << "Couldn't close socket: " << theError.message() << endl;
                return false;
            }
        }
        else
            cout << "The socket is already closed" << endl;
    
        return true;
    }//Close