-1

I am trying to learn boost asio socket. there is one interesting example in boost web page which set a deadline time to monitor the timeout and change async io to sync io fashion. but when I remove the deadline timer the program stuck in while loop in write_line, I don't understand why the connection can be setup but the socket write is stuck. it seems that the writing never finished ,the handler never called so the "ec" never changed. Thank you in advance!!

#include <boost/asio/connect.hpp>
#include <boost/asio/deadline_timer.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/read_until.hpp>
#include <boost/asio/streambuf.hpp>
#include <boost/system/system_error.hpp>
#include <boost/asio/write.hpp>
#include <cstdlib>
#include <iostream>
#include <string>
#include <boost/bind.hpp>
using boost::asio::deadline_timer;
using boost::asio::ip::tcp;

class client
{
public:
  client()
    : socket_(io_service_),
      deadline_(io_service_)
  {
   deadline_.expires_from_now(boost::posix_time::seconds(10)); 
   check_deadline(); // without this line which means without the this timer 
                     //async_wait, the code stuck in write_line io_service_.run_one() loop .
  }
  void connect_handler(const boost::system::error_code& error,boost::system::error_code *er)
  {
      std::cerr<<"connect handler"<<std::endl;
      *er = error;
      std::cerr<<error<<std::endl;
  }

  void connect(const std::string& host, const std::string& service)
  {
    tcp::resolver::query query(host, service);
    tcp::resolver::iterator iter = tcp::resolver(io_service_).resolve(query);
    std::cerr<<"connect start"<<std::endl;

    boost::system::error_code ec = boost::asio::error::would_block;

    boost::asio::async_connect(socket_, iter, bind(&client::connect_handler,this,_1,&ec));

    do
    {io_service_.run_one();
    }while (ec == boost::asio::error::would_block);

    std::cerr<<"connect done"<<std::endl; // always works fine!
    if (ec || !socket_.is_open())
      throw boost::system::system_error(
          ec ? ec : boost::asio::error::operation_aborted);
  }

  void write_handler(const boost::system::error_code& error, std::size_t size,boost::system::error_code* er )
  {
      std::cerr<<"write handler "<<std::endl;
      *er=error;
      std::cerr<<error<<std::endl;
  }
  void write_line(const std::string& line)
  {
    std::cerr<<"write start"<<std::endl;
    std::string data = line + "\n";
    boost::system::error_code ec = boost::asio::error::would_block;

    boost::asio::async_write(socket_, boost::asio::buffer(data), bind(&client::write_handler,this,_1,_2,&ec));
    do
    {
        io_service_.run_one();     //stuck here without "check_deadline();" in constructor.
    }while (ec == boost::asio::error::would_block);
    std::cerr<<"write done";
    if (ec)
      throw boost::system::system_error(ec);
  }

private:
  void check_deadline()
  {
    if (deadline_.expires_at() <= deadline_timer::traits_type::now())
    {
      boost::system::error_code ignored_ec;
      socket_.close(ignored_ec);
      deadline_.expires_at(boost::posix_time::pos_infin);
       throw boost::system::system_error(ignored_ec);
    }

    deadline_.async_wait(std::bind(&client::check_deadline, this));
  }

  boost::asio::io_service io_service_;
  tcp::socket socket_;
  deadline_timer deadline_;

};

int main()
{
  try
  {
    client c,d;
    c.connect("216.58.194.164", "80");// google IP.
    c.write_line("test");
  }
  catch (std::exception& e)
  {
    std::cerr << "Exception: " << e.what() << "\n";
  }

  return 0;
}

Without comment out line "check_deadline();" in constructor the output is :

connect start
connect handler
system:0
connect done
write start
write handler 
system:0
write done

when the line "check_deadline();" comment out in constructor, the output is :

connect start
connect handler
system:0
connect done
write start

and stuck there forever.

Daniel
  • 53
  • 5

1 Answers1

0

The whole point of that particular example is to have timeouts on blocking operations. This is why they use the async_* flavour of functions.

If you don't need that, don't use the async_* flavour at all:

#include <boost/asio.hpp>
#include <iostream>
#include <string>

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

class client {
  public:
    client() : socket_(io_service_) { }

    void connect(const std::string &host, const std::string &service) {
        tcp::resolver::query query(host, service);
        socket_.close();
        boost::asio::connect(socket_, tcp::resolver(io_service_).resolve(query));
    }

    std::string read_line() {
        boost::system::error_code ec;
        boost::asio::read_until(socket_, input_buffer_, '\n', ec);

        if (ec == boost::asio::error::eof)
            socket_.close();
        else if (ec)
            throw boost::system::system_error(ec);

        std::string line;
        std::getline(std::istream(&input_buffer_), line);
        return line;
    }

    void write_line(const std::string &line) {
        std::string data = line + "\n";
        boost::asio::write(socket_, boost::asio::buffer(data));
    }

  private:
    boost::asio::io_service io_service_;
    tcp::socket socket_;
    boost::asio::streambuf input_buffer_;
};

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

        client c;
        c.connect(argv[1], argv[2]);

        boost::posix_time::ptime time_sent = boost::posix_time::microsec_clock::universal_time();

        c.write_line(argv[3]);

        for (;;) {
            std::string line = c.read_line();

            std::cout << "Received '" << line << "'\n";

            if (line == argv[3])
                break;
        }

        boost::posix_time::ptime time_received = boost::posix_time::microsec_clock::universal_time();

        std::cout << "Round trip time: ";
        std::cout << (time_received - time_sent).total_microseconds();
        std::cout << " microseconds\n";
    } catch (std::exception &e) {
        std::cerr << "Exception: " << e.what() << "\n";
    }
}

That's a lot simpler. In fact, it's too simple in case the packets arriving contain more than 1 line at a time.

Instead of "fixing" the sample by re-complicating it, it can be ENORMOUSLY simpler:

#include <boost/asio.hpp>
#include <iostream>
#include <string>

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

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

        tcp::iostream c(argv[1], argv[2]);

        boost::posix_time::ptime time_sent = boost::posix_time::microsec_clock::universal_time();

        c << argv[3] << "\n";

        std::string line;
        while (getline(c, line)) {
            std::cout << "Received '" << line << "'\n";

            if (line == argv[3])
                break;
        }

        boost::posix_time::ptime time_received = boost::posix_time::microsec_clock::universal_time();

        std::cout << "Round trip time: ";
        std::cout << (time_received - time_sent).total_microseconds();
        std::cout << " microseconds\n";
    } catch (std::exception &e) {
        std::cerr << "Exception: " << e.what() << "\n";
    }
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Sehe, Thank you so much for your reply. I understand the usage of async API, but my understanding is that even we remove the deadline timer the socket async API should work. I will check my code and questions simpler to help you understand my problem. – Daniel Feb 02 '18 at 15:44
  • Sehe, the intend to keep the async_* is I don't want to block the whole process with socket operation in a single thread application. – Daniel Feb 02 '18 at 16:28
  • Well. Then don't use that example. Because they LITERALLY block every operation (see the comments and the `run_one` loops) – sehe Feb 03 '18 at 02:52
  • I got it , but still don’t understand why the connect can be done but the write can not without deadline wait . Do you see my updated post and new code, do you have any comments? Thanks! – Daniel Feb 03 '18 at 04:31
  • The only problem in that code is that you [should `io_service_.reset()`](http://coliru.stacked-crooked.com/a/09e0549091ad0455). BUT that makes absolutely no sense if you wanted to use asynchronous code. So don't. Instead, you probably want something like this: [Live With Two Concurrent Clients](http://coliru.stacked-crooked.com/a/168e2dfda5f3c90a) – sehe Feb 03 '18 at 13:52
  • Hi Sehe, thanks for your reply, you are correct the io_service.reset() should be called after "connect" function. I don't think I understand the io_service totally, I will start a new post about my question. Thanks again! – Daniel Feb 06 '18 at 16:25
  • I'll repeat that it makes absolutely no sense if you wanted asynchronous operations. – sehe Feb 06 '18 at 16:26
  • Mmm @Daniel what happened? I notice you unaccepted this post. – sehe Mar 06 '18 at 18:26
  • I have no idea, I think I accepted this post before. let me do it again. – Daniel Mar 07 '18 at 12:33