简体   繁体   中英

Boost::asio::async_write, handler called only once

I am quite new in boost::asio and I have a problem. I am writting client that sends in loop some commands to server. I am sending command with boost::asio::async_write and I expect that every time I send commands handler will be called. In fact only during first sending I see that handler is called. My client looks like that:

Client::Client(boost::asio::io_service & p_ioService,
           boost::asio::ip::tcp::endpoint p_endpoint)
 : io_service(p_ioService), endpoint(p_endpoint), socket(p_ioService)
{
   socket.connect(endpoint); 
}
Client::~Client()
{
    socket.close();
}
void Client::sendCommand(const string & p_command)
{
    boost::asio::async_write(socket,boost::asio::buffer(p_command), 
                              boost::bind(&Client::onSendingFinished,this, _1, _2));
    io_service.run();
}
void Client::onSendingFinished(const boost::system::error_code& ec, std::size_t    bytes_transferred)
{
    cout<<"Sent "<<bytes_transferred<<endl;
}

There is no other place in main.cpp where io_service.run is called. I notice that if I call io_service.reset() after io_service.run() it works fine, handler is called every time.

How should I solve this without io_service.reset()

Thanks in advance

I do not understand the aversion to calling io_service::reset() . In this case, it is necessary to invoke prior to any subsequent calls to io_service::run() :

reset() must be called prior to any second or later set of invocations of the run() , run_one() , poll() or poll_one() functions when a previous invocation of these functions returned due to the io_service being stopped or running out of work.

It is possible that a thread returns from run() as a result of an exception being thrown, yet the io_service has neither been stopped nor ran out of work. In this case, the thread can invoke run() without calling reset() .


The current Client::sendCommand() is synchronous. It is an implementation detail that it initiates an asynchronous operation, then blocks in io_service::run() waiting for the operation to complete. Unless there are multiple threads invoking commands on socket , multiple threads running the io_service , or the write operation needs to be cancellable, such as from a timeout, then it would be functionally equivalent and possible easier to implement Client::sendCommand() with a synchronous write() .

void Client::sendCommand(const string & p_command)
{
  boost::system::error_code ec;
  std::size_t bytes_transferred =
      boost::asio::write(socket, boost::asio::buffer(p_command), ec);
  onSendingFinished(ec, bytes_transferred);
}

If Client::sendCommand() needs to be asynchronous, then:

  • The io_service should be ran from outside of Client::sendCommand() . If the io_service does not always have outstanding work, then io_service::work can be used control when run() returns. See this answer for more details as to when io_service::run() blocks and unblocks.
  • The underlying memory provided to async_write() as the buffer ( p_command ) needs to remain valid until the operation's handler, Client::onSendingFinished() , has been called. In this case, it may require making a copy of p_command in Client::sendCommand() , writing the copy to the socket, then deleting the copy from within the handler.

    [...] ownership of the underlying memory blocks is retained by the caller, which must guarantee that they remain valid until the handler is called.

While it is not inherently bad to call reset() every now and then, there are two typical ways to avoid having to do it.

  1. Start a new async operation within the handler of the first one. run() only returns once all handlers have finished and thus a new async operation started in a handler is still in time to keep blocking io_service .

  2. Use io_service::work . If you create an instance of io_service::work constructed with your io_service as parameter, than your subsequent calls to run() will not return as long as the work object remains alive. And thus you will not have to reset anything. Of course this means that either one of your handlers or another thread has to destroy the work object at some time, if you want run() to ever stop blocking.

It's quite unusual to just send messages, it's far more common to have two way communication.

If you implemented a receiver as well, then your receive code would always require a receive handler running in the io_service and you wouldn't have this problem...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM