Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

libboost ASIO. Simple asynchronous client server

I'm trying to implement a simple client/server in ASIO.

I'd like the following on the serverside:

onConnect()
onDisconnect()
onMessageRecieved(char* data)
sendMessage(char* data)

and on the client side:

onConnect()
onDisconnect()
onMessageRecieved(char* data)
sendMessage(char* data)

I didn't realise things would be so complicated.

Here's the simple echo server which I'm working off of:

#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>

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

class session
{
public:
  session(boost::asio::io_service& io_service)
    : socket_(io_service)
  {
  }

  tcp::socket& socket()
  {
    return socket_;
  }

  void start()
  {
    socket_.async_read_some(boost::asio::buffer(data_, max_length),
        boost::bind(&session::handle_read, this,
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  }

  void handle_read(const boost::system::error_code& error,
      size_t bytes_transferred)
  {
    if (!error)
    {
      boost::asio::async_write(socket_,
          boost::asio::buffer(data_, bytes_transferred),
          boost::bind(&session::handle_write, this,
            boost::asio::placeholders::error));
    }
    else
    {
      delete this;
    }
  }

  void handle_write(const boost::system::error_code& error)
  {
    if (!error)
    {
      socket_.async_read_some(boost::asio::buffer(data_, max_length),
          boost::bind(&session::handle_read, this,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));
    }
    else
    {
      delete this;
    }
  }

private:
  tcp::socket socket_;
  enum { max_length = 1024 };
  char data_[max_length];
};

class server
{
public:
  server(boost::asio::io_service& io_service, short port)
    : io_service_(io_service),
      acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
  {
    session* new_session = new session(io_service_);
    acceptor_.async_accept(new_session->socket(),
        boost::bind(&server::handle_accept, this, new_session,
          boost::asio::placeholders::error));
  }

  void handle_accept(session* new_session,
      const boost::system::error_code& error)
  {
    if (!error)
    {
      new_session->start();
      new_session = new session(io_service_);
      acceptor_.async_accept(new_session->socket(),
          boost::bind(&server::handle_accept, this, new_session,
            boost::asio::placeholders::error));
    }
    else
    {
      delete new_session;
    }
  }

private:
  boost::asio::io_service& io_service_;
  tcp::acceptor acceptor_;
};

int main(int argc, char* argv[])
{
  try
  {
    if (argc != 2)
    {
      std::cerr << "Usage: async_tcp_echo_server <port>\n";
      return 1;
    }

    boost::asio::io_service io_service;

    using namespace std; // For atoi.
    server s(io_service, atoi(argv[1]));

    io_service.run();
  }
  catch (std::exception& e)
  {
    std::cerr << "Exception: " << e.what() << "\n";
  }

  return 0;
}

I can telnet into this server and everything is echoed.

Now I'd like to wrap up this code in onConnect(), onDisconnect(), onMessageReceived(char* data), etc. Similar to the way things are done in Node.js!

Has anyone got any pointers in this regard?

like image 915
Eamorr Avatar asked Aug 04 '11 13:08

Eamorr


1 Answers

  • onMessageReceived() can be called from handle_read.
  • onConnect() can be called from start.
  • onDisconnect() can be called in the destructor of the session class.

For the bounty questions:

The io_service.run() can be placed in its own thread.

As per the documentation

Certain guarantees are made on when the handler may be invoked, in particular that a handler can only be invoked from a thread that is currently calling run() on the corresponding io_service object.

Asynchronous sending and receiving can be handled by this single thread. This simplifies thread safety because all the callbacks will be running in succession. This is probably the simplest way of using boost asio.

For calls coming from outside of the run() thread, you can schedule a callback (e.g. deadline_timer), from the 'outside thread' for immediate calling to simplify your thread safety handling. e.g.

    boost::asio::deadline_timer timer(io_service);
    timer.expires_from_now(boost::posix_time::seconds(0));
    timer.async_wait(boost::bind(&MyClass::MyCallback, this, boost::asio::placeholders::error);

The io_service object will call the handler for you in a thread-safe fashion as soon as it has a chance. This way, your asio code can behave as if there was only a single thread in the entire system.

If multiple threads are required or preferred (e.g. Take advantage of multi-core) you may call run() on multiple thread. Handlers will have to be re-entrant. You may also want to use a strand for certain operations.

Otherwise, regular thread safety rules applies.

like image 180
ppl Avatar answered Oct 06 '22 08:10

ppl