Triggering written with Boost :: asio

advertisements

I have some software that I would like to make a TCP client. I don't know if this is the best architecture, but in my software I spawn a thread that will be used for the Network I/O. If there is a better architecture, I'd appreciate some pointers and advice.

Both threads have a refernce to the boost::asio::io_service object and a Session object that encapsulates the socket object. The sesson object is roughly as follows:

  class Session
  {
  public:

  Session(
    boost::asio::io_service & io_service,
    std::string const & ip_address,
    std::string const & port)
  : io_service_(io_service),
    resolver_(io_service),
    socket_(io_service),
    ip_address_(ip_address),
    port_(port),
  {}

  virtual void start();
  virtual ~Session();
  virtual void stop();
  void write(std::string const & msg);

  void handle_resolve(
    const boost::system::error_code & error,
    boost::asio::ip::tcp::resolver::iterator endpoint_itr);

  void handle_connect(
    const boost::system::error_code & error,
    boost::asio::ip::tcp::resolver::iterator endpoint_itr);

  void handle_close();
  void handle_write(const boost::system::error_code & error);

private:
  boost::asio::io_service & io_service_;
  boost::asio::ip::tcp::resolver resolver_;
  boost::asio::ip::tcp::socket socket_;
  std::string ip_address_;
  std::string port_;
};

In the I/O thread run-loop, the start() method of the session object is called which connects to the server. (This works, btw). Then, the thread sits in a loop calling the run() method on the I/O service object [io_service_.run()] to trigger events.

The main thread calls the write() method of the session when it wants to send data, and the session object calls boost::async_write with the data to write and then a callback method that is a member of the session object (handle_write).

While I have the I/O thread connecting to the server, I cannot get the handle_write method to be triggered. I have verified that the main thread is calling into the session object and executing async_write() on the socket. It is just that the callback is never triggered. I also don't see any data on the server side or over the wire with tcpdump.

Any idea where my problem might be? Is there a better way to organize the architecture? Most of all, I don't want to block the main thread doing I/O.

Here is the code that spawns the io thread from the main thread (apologies for the spacing):

    boost::asio::io_service io_service;
    boost::shared_ptr<Session> session_ptr;
    boost::thread io_thread;
    ....
    session_ptr.reset(
      new Session::Session(
                io_service,
                std::string("127.0.0.1"),
                std::string("17001")));

    // spawn new thread for the network I/O endpoint
    io_thread = boost::thread(
      boost::bind(
        &Session::start,
        session_ptr_.get()));

The code for the start() method is as follows:

    void Session::start()
    {
      typedef boost::asio::ip::tcp tcp;

      tcp::resolver::query query(
          tcp::v4(),
          ip_address_,
          port_);  

      resolver_.async_resolve(
          query,
          boost::bind(
              &Session::handle_resolve,
              this,
              boost::asio::placeholders::error,
              boost::asio::placeholders::iterator));

      while(1){ // improve this later
        io_service_.run();
      }
    }

The callback for the resolver:

    void Session::handle_resolve(
        const boost::system::error_code & error,
        boost::asio::ip::tcp::resolver::iterator endpoint_itr)
    {
      if (!error)
      {
        boost::asio::ip::tcp::endpoint endpoint = *endpoint_itr;
        socket_.async_connect(
            endpoint,
            boost::bind(
              &Session::handle_connect,
              this,
              boost::asio::placeholders::error,
              ++endpoint_itr));
      }
      else
      {
        std::cerr << "Failed to resolve\n";
        std::cerr << "Error: " << error.message() << std::endl;
      }
    }

The callback for connect:

    void Session::handle_connect(
        const boost::system::error_code & error,
        boost::asio::ip::tcp::resolver::iterator endpoint_itr)
    {
      typedef boost::asio::ip::tcp tcp;

      if (!error)
      {
        std::cerr << "Connected to the server!\n";
      }
      else if (endpoint_itr != tcp::resolver::iterator())
      {
        socket_.close();
        socket_.async_connect(
            *endpoint_itr,
            boost::bind(
              &Session::handle_connect,
              this,
              boost::asio::placeholders::error,
              ++endpoint_itr));
      }
      else
      {
        std::cerr << "Failed to connect\n";
      }

    }

The write() method that the main thread can call to send post an asychronous write.

    void Session::write(
        std::string const & msg)
    {
      std::cout << "Write: " << msg << std::endl;
      boost::asio::async_write(
          socket_,
          boost::asio::buffer(
              msg.c_str(),
              msg.length()),
          boost::bind(
              &Session::handle_write,
              this,
              boost::asio::placeholders::error));
    }

And finally, the write completion callback:

    void Session::handle_write(
        const boost::system::error_code & error)
    {
      if (error)
      {
         std::cout << "Write complete with errors !!!\n";
      }
      else
      {
         std::cout << "Write complete with no errors\n";
      }
    }


Looks like your io service will run out of work after connect, after which you just call io_service::run again? It looks like run is being called in the while loop, however I can't see a call to reset anywhere. You need to call io::service::reset before you call run on the same io_service again.

Structurally, it would be better to add work to the io_service, then you don't need to call it in the loop and the run will exit once you call io_service::stop.