boost-asio-1-timer usage

Article directory

    • Preface
    • timer
      • Synchronous use of timers
      • Using timers asynchronously
      • Bind parameters to completion handler
      • Synchronous completion handlers in multi-threaded programs

Foreword

There is no network library included in the C++ standard library. I need (familiarity with) a networking library that can serve as a “Swiss Army Knife” in my day-to-day network programming.

Here, I choose to learn to use boost_asio.

I chose this library for two reasons: First, the order in which I chose the libraries was, C++ standard library > boost library > well-known C++ library for specific functions == QT library. Functions not found in the C++ standard library are first looked for in boost. The second is that socket programming is different on windows and linux. In order to ensure the portability of code between platforms, socket is not a good choice.

For the asio library, I can currently only “learn to use” but not “learn”. Because it is a bit difficult to look at the source code of asio.

Timer

The following content comes from: boost-asio-Tutorial-Basic Skills

In this section, we learn about the use of timers in asio. (The most basic function of the timer is to execute the corresponding action after the specified time. Note that this is an asynchronous process. The principle of asynchronous is a bit difficult, I don’t know it. Here is a related video, you can watch it if you are interested: CppCon 2016: Michael Caisse “Asynchronous IO with Boost.Asio”)

Synchronize using timers

Create a timer and wait for it to expire. code show as below.

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

int main(int argc, char *argv[]) {
  boost::asio::io_context io;
  boost::asio::steady_timer t(io, boost::asio::chrono::seconds(5));
  t.wait();
  std::cout << "hello world" << std::endl;
  return 0;
}
  1. All asio classes can be introduced using the “asio.hpp” header file.
  2. All programs that use asio need to have at least one I/O execution context, such as an io_context or thread_pool object. Provides access to I/O.
  3. Next, we declare an object of type boost::asio::steady_timer. It takes a reference to io_context as the first parameter. The second parameter of the constructor sets the timer to expire 5 seconds from now.
  4. steady_timer::wait() function, waits for the timer to expire.

Using timers asynchronously

The following program demonstrates the asynchronous use of timers. After the timer expires, the specified function (completion handler) is called.

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

void print(const boost::system::error_code & amp; /*e*/) {
  std::cout << "Hello, world!" << std::endl;
}

int main(int argc, char *argv[]) {
  boost::asio::io_context io;
  boost::asio::steady_timer t(io, boost::asio::chrono::seconds(5));
  t.async_wait( & amp;print);
  io.run();
  return 0;
}
  1. We call the steady_Timer::async_wait() function to perform asynchronous waiting. The print function is called when the asynchronous wait ends.
  2. Finally, we must call the io_context::run() member function on the io_context object. The asio library guarantees that the completion handler will only be called from the thread currently calling io_context::run(). Therefore, the completion handler of the async await completion will never be called unless the io_context::run() function is called.
  3. Remember to give io_context some work before calling io_context::run(). For example, if we omit the call to steady_timer::async_wait() above, io_context will have no work to do, so io_context::run() will return immediately.

Bind parameters to completion handler

On the basis of the above, when we want to pass parameters to the completion handler, but the handler does not have additional parameter locations. Naturally, we need to use bind.

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

void print(const boost::system::error_code & amp; /*e*/,
           boost::asio::steady_timer *t, int *count) {
  if (*count < 5) {
    std::cout << *count << std::endl;
     + + (*count);
    t->expires_at(t->expiry() + boost::asio::chrono::seconds(1));
    t->async_wait(
        boost::bind(print, boost::asio::placeholders::error, t, count));
  }
}

int main() {
  boost::asio::io_context io;
  int count = 0;
  boost::asio::steady_timer t(io, boost::asio::chrono::seconds(1));
  t.async_wait(
      boost::bind(print, boost::asio::placeholders::error, & amp;t, & amp;count));
  io.run();
  std::cout << "Final count is " << count << std::endl;
  return 0;
}

The above is the official sample code. The pointer of boost::asio::steady_timer is used in the print function. Don’t use raw pointers in C++, you can encapsulate them with smart pointers.

So, why can’t we use references as parameters? An error will be reported after the code is modified as a reference. The error is reported as follows: use of deleted function boost::asio::basic_waitable_timer.

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

void print(const boost::system::error_code & amp; /*e*/,
           boost::asio::steady_timer & amp;t, int & amp;count) {
  if (count < 5) {
    std::cout << count + + << std::endl;
    t.expires_at(t.expiry() + boost::asio::chrono::seconds(1));
    t.async_wait(
        boost::bind(print, boost::asio::placeholders::error, t, count));
    // t.async_wait(boost::bind(print, boost::asio::placeholders::error,
    // boost::ref(t), count));
  }
}

int main(int argc, char *argv[]) {
  int count = 0;
  boost::asio::io_context io;
  boost::asio::steady_timer t(io, boost::asio::chrono::seconds(1));
  t.async_wait(boost::bind(print, boost::asio::placeholders::error, t, count));
  // t.async_wait(boost::bind(print, boost::asio::placeholders::error,
  // boost::ref(t), count));
  io.run();
  return 0;
}

A little strange. steady_timer’s assignment construction and copy construction are prohibited, and these two functions are private. But this does not affect the use of references,

private:
// Disallow copying and assignment.
  basic_waitable_timer(const basic_waitable_timer &) BOOST_ASIO_DELETED;
  basic_waitable_timer & amp; operator=(
      const basic_waitable_timer & amp;) BOOST_ASIO_DELETED;

After dinner, I looked at this code again. Boost.Ref comes to mind. For boost::bind, its parameters are copied by value. Even if the bound function is declared as a reference, it will not affect the actual parameters. At this time, for the referenced parameters, we need to use boost:: refs. The problem is solved.

Synchronous completion handlers in multi-threaded programs

As we know, the asio library provides a guarantee that the completion handler will only be called from the thread that is currently calling io_context::run(). Completion handlers can be executed concurrently by calling io_context::run() in different threads. When handlers may access shared, thread-unsafe resources, we need a synchronization method.

The strand class template is an executor adapter that guarantees that for handlers scheduled through it, one executing handler will be allowed to complete before the next one starts. This is guaranteed regardless of the number of threads calling io_context::run(). By binding handlers to the same chain, we ensure that they cannot execute concurrently.

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

class printer {
public:
  printer(boost::asio::io_context & amp;io)
      : strand_(boost::asio::make_strand(io)),
        timer1_(io, boost::asio::chrono::seconds(1)),
        timer2_(io, boost::asio::chrono::seconds(2)), count_(0) {
    timer1_.async_wait(boost::asio::bind_executor(
        strand_, boost::bind( & amp;printer::print1, this)));

    timer2_.async_wait(boost::asio::bind_executor(
        strand_, boost::bind( & amp;printer::print2, this)));
  }

  ~printer() { std::cout << "Final count is " << count_ << std::endl; }

private:
  void print1() {
    if (count_ < 10) {
      std::cout << "Timer 1: " << count_ << std::endl;
       + + count_;
      timer1_.expires_at(timer1_.expiry() + boost::asio::chrono::seconds(1));
      timer1_.async_wait(boost::asio::bind_executor(
          strand_, boost::bind( & amp;printer::print1, this)));
    }
  }

  void print2() {
    if (count_ < 10) {
      std::cout << "Timer 2: " << count_ << std::endl;
       + + count_;
      timer2_.expires_at(timer2_.expiry() + boost::asio::chrono::seconds(1));
      timer2_.async_wait(boost::asio::bind_executor(
          strand_, boost::bind( & amp;printer::print2, this)));
    }
  }

private:
  boost::asio::strand<boost::asio::io_context::executor_type> strand_;
  boost::asio::steady_timer timer1_;
  boost::asio::steady_timer timer2_;
  int count_;
};

int main() {
  boost::asio::io_context io;
  printer p(io);
  boost::thread t(boost::bind( & amp;boost::asio::io_context::run, & amp;io));
  io.run();
  t.join();

  return 0;
}

Because the compiler does not implicitly convert the object’s member functions into function pointers. Therefore, in the bind function parameters, & amp; must be added before the member method.

When compiling, you need to link the boost_thread library. The output is as follows:

Timer 1: 0
Timer 1: 1
Timer 2: 2
Timer 1: 3
Timer 2: 4
Timer 1: 5
Timer 2: 6
Timer 1: 7
Timer 2: 8
Timer 1: 9
Final count is 10