Goodies
Goodies

Reputation: 4681

Standalone ASIO Asynchronous Not Connecting

ASIO seems like the best async cross-platform networking library for my project. However, I'm having trouble getting it to actually connect.

First off, I'm not using Boost. I'm compiling this on Windows for the time being, so I had to manually add definitions to inform ASIO that I'm using a C++11-compliant compiler.

Source.cpp

#define TCPCLIENT_DEBUG
#include "TCPClient.hpp"
#include <iostream>

#define PORT "1234"
#define HOST "127.0.0.1"

int main() {
    DEBUG("Starting program...\n");
    namespace ip = asio::ip;

    asio::io_service io;
    ip::tcp::resolver::query query(HOST, PORT);
    ip::tcp::resolver resolver(io);
    decltype(resolver)::iterator ep_iter = resolver.resolve(query);

    TCPClient client(io, ep_iter);

    try {
        std::cin.get();
    }
    catch (const std::exception &e) { // mainly to catch Ctrl+C
        std::cout << e.what() << std::endl;
    }
    return 0;
}

TCPClient.hpp

#ifndef TCPCLIENT_HPP
#define TCPCLIENT_HPP

#include <functional>

#if defined(_DEBUG) || defined(TCPCLIENT_DEBUG)
#include <iostream>
#define DEBUG(dbg_msg) std::cerr << dbg_msg
#else
#define DEBUG(dbg_msg)
#endif

#define ASIO_STANDALONE
#define ASIO_HAS_CSTDINT
#define ASIO_HAS_STD_ARRAY
#define ASIO_HAS_STD_ADDRESSOF
#define ASIO_HAS_STD_SHARED_PTR
#define ASIO_HAS_STD_TYPE_TRAITS

#include <asio.hpp>
#ifndef BUFFER_SIZE
#define BUFFER_SIZE 1024
#endif

class TCPClient {
public:
    TCPClient(asio::io_service& io, asio::ip::tcp::resolver::iterator endpoint_iter);
    void on_connect(const asio::error_code& err);

private:
    asio::io_service& m_io;                         // store the io service reference
    asio::ip::tcp::socket m_sock;                   // object's socket
    static const size_t bufSize{ BUFFER_SIZE };     // default buffer size
    char m_buffer[bufSize];                         // store the received data in a buffer
};

#endif//TCPCLIENT_HPP

TCPClient.cpp

#include "TCPClient.hpp"

TCPClient::TCPClient(asio::io_service& io, asio::ip::tcp::resolver::iterator endpoint_iter) : m_io{ io }, m_sock(io) {
    asio::ip::tcp::endpoint endpoint = *endpoint_iter;
    asio::error_code ec;

    m_sock.async_connect(
        endpoint,
        std::bind(
            &TCPClient::on_connect,
            this,
            std::placeholders::_1
        )
    );
}

void TCPClient::on_connect(const asio::error_code& err) {
    DEBUG("Connected successfully!\n");
}

It seems to me that the on_connect is never being called. It only prints "Starting program...".

Using netcat, I can spawn a listener that sees the connection successfully go through.

enter image description here

What is obviously wrong with my code? I'm only working on the connection function for right now.

Upvotes: 0

Views: 401

Answers (2)

lisyarus
lisyarus

Reputation: 15567

By calling async_connect, you only register an asynchronous operation. You should explicitly call io_service.run() somewhere, - probably, in main instead of std::cin.get(), - to get your asynchronous operations really executed and callbacks called.

Under the hood, asio uses epoll or something similar: it registers events it is interested in (a socket connection in your case) and then waits for the events to happen. io_service.run() is precisely the place where waiting is done.

I'd advise you to look at some boost::asio asyncronous tutorials, like this one.

Upvotes: 2

Tanner Sansbury
Tanner Sansbury

Reputation: 51951

Handlers are only executed within threads that are currently running the io_service. As the io_service is never ran, the connect handler is never executed. To resolve this, run the io_service by calling io_service::run():

TCPClient client(io, ep_iter);

try {
  io.run();
}
catch (const std::exception &e) {
  std::cout << e.what() << std::endl;
}

The Using a timer asynchronously Tutorial notes the importance of running the io_service:

Finally, we must call the io_service::run() member function on the io_service object.

The asio library provides a guarantee that callback handlers will only be called from threads that are currently calling io_service::run(). Therefore unless the io_service::run() function is called the callback for the asynchronous wait completion will never be invoked.

The io_service::run() function will also continue to run while there is still "work" to do. In this example, the work is the asynchronous wait on the timer, so the call will not return until the timer has expired and the callback has completed.

It is important to remember to give the io_service some work to do before calling io_service::run(). For example, if we had omitted the above call to deadline_timer::async_wait(), the io_service would not have had any work to do, and consequently io_service::run() would have returned immediately.

Upvotes: 2

Related Questions