Bituki
Bituki

Reputation: 43

asio - Wait for async operations to finish

I am using boost.asio and I have a simple server that needs to handle multiple connections. Before starting the "connected" status, I need to perform a handshake with the client in a limited amount of time. I am using chained asynchronous operations for each step of the handshake as I need to use a timer and (as far as I know I can't do that with synchronous reads and writes). I need a way to block each connection until the deadline timer ends or the handshake succeeds, without blocking the server.

Is there any way to achieve this?

UPDATE Some code to clarify things

A simple server

typedef boost::asio::ip::tcp::socket    sock;

class server
{
public:
    server() : _ios(), _acceptor(_ios)
    {
        boost::asio::ip::tcp::resolver  resolver(_ios);
        boost::asio::ip::tcp::endpoint  endpoint(boost::asio::ip::tcp::v4(), PORT);

        _acceptor.open(endpoint.protocol());
        _acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
        _acceptor.bind(endpoint);
        _acceptor.listen();

        do_accept();
    }

    void run()
    {
        _ios.run();
    }

private:
    void do_accept()
    {
        auto socket = std::make_shared<TSock>(_ios);
        _acceptor.async_accept(*socket, std::bind(&server::handle_accept,
                                                  this, socket, std::placeholders::_1));
    }

    void handle_accept(std::shared_ptr<sock> socket, const boost::system::error_code& ec)
    {
        auto connection = std::make_shared<CCon>();

        if (connection->Accept(socket))
        {
            std::cout << "Connection accepted" << std::endl;
        }
        else
        {
            std::cout << "Connection not accepted" << std::endl;
        }

        do_accept();
    }

    boost::asio::io_service         _ios;
    boost::asio::ip::tcp::acceptor  _acceptor;

    std::set<std::shared_ptr<CCon>> _connections;
};

int32_t main(int32_t argc, char *argv[])
{
    server s;
    s.run();
}

connection->Accept(socket) explained

bool CCon::Accept(<std::shared_ptr<sock>> tcpSocket)
{
    // set handshake sequence
    SendGreeting();

    // I NEED TO WAIT HERE UNTIL connectionAccepted gets a value

    if (connectionAccepted)
    {
        // Connection Accepted
        return(true)
    }
    else
    {
        //Connection Rejected
        return(false)
    }
}

SendGreeting() contains

boost::asio::async_write(*tcpSocket, 
                         boost::asio::buffer(oBuff,bytesBuffered),
                         std::bind(&CCon::WaitForResp, 
                                   this, std::placeholders::_1));

The problem is that WaitForResp never gets called. It only gets called if I restart the io_service (stop() -> reset() -> run()) after setting the new handler, which isn't a solution at all.

I think I am missing something about Asio and I'd be glad if anyone could help me with it.

Upvotes: 3

Views: 4303

Answers (1)

user562566
user562566

Reputation:

Your example code isn't complete, so I can't guarantee my answer is a solution, but I'll point out a few things that I think are the problem.

Point 1 - The io_service doesn't just keep running. If it thinks there's no more work to be done (nothing in the cue), then ::run() will exit, and since you're expecting it not to, you're going to get unexpected behavior. You need to prevent it from running out of work, by creating and keeping an io_service::work object alive. From the docs:

boost::asio::io_service io_service;
boost::asio::io_service::work work(io_service);
...

More on that here.

Secondly, I'm concerned about how the CCon object lifetime is maintained. Who is responsible for keeping it alive? I ask because you're binding to this inside of it, so you're spawning async callbacks internally but apparently relying on an external object to make sure that this is alive if and when the callback returns. If you're failing to do this correctly, you're going to get undefined behavior, which doesn't necessarily mean that you're going to get a big crash and error code, but maybe somehow the handler completes or hangs forever, or some other strange inconclusive behavior because of the undefined state.

Based on the incomplete code here, it looks like nothing is keeping CCon alive externally, which means that things are probably falling out of scope and getting destroyed. I'm going to guess that you always get "connection not accepted" in your server, if anything at all. If you break down the code you have here, you spawn a CCon wrapped in a shared_ptr, then call ::Accept(), which in turn invokes non-blocking async methods which may complete immediately, or 100 years from now. Since they're non-blocking, the code is going to step into the if(connectionAccepted) statement immediately and therefore return one way another immediately, at which time your shared_ptr of CCon falls out of scope in the server handle_accept, the ref count gets decremented to 0 and the destructor is invoked.

What you should be doing is making CCon inherit from std::enable_shared_from_this or boost::enable_shared_from_this and in all of the async callbacks that CCon binds internally, you should bind to shared_from_this(), which will feed another shared_ptr into the cue from the bind, incrementing the ref count of your shared CCon object and guaranteeing the lifetime of CCon to at least extend into the completion handler.

Third, I think your design approach isn't the best solution, as a side note. You shouldn't be forcing every other possible incoming connection to wait for the previous connection to go through some authentication process. You're holding up the acceptor from doing its job for new clients until the previous client goes does some talking with the server. That's a completely unnecessary bottleneck.

I believe you're doing this because you want to, at the ::server object level, determine if clients have properly passed the authentication process. You should instead just pass a function or something to the CCon::Accept(...) method which it can invoke to report things to the server, if you really need the server to know anything at all. Copy the asio model and change your Accept method to AsyncAccept, make it non-blocking and give it a callback for when it completes, at which time you can check its final state.

Upvotes: 2

Related Questions