Asynchronous read/write operations

Unlike classical UNIX socket programming, boost.asio has battery-included asynchronous read/write abilities. Still use basic_stream_socket as an example, and one pair of the implementations is like this:

  1. template <typename ConstBufferSequence, typename WriteHandler>
  2. BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler,
  3. void (boost::system::error_code, std::size_t))
  4. async_send(const ConstBufferSequence& buffers,
  5. BOOST_ASIO_MOVE_ARG(WriteHandler) handler)
  6. {
  7. .......
  8. }
  9. template <typename MutableBufferSequence, typename ReadHandler>
  10. BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler,
  11. void (boost::system::error_code, std::size_t))
  12. async_receive(const MutableBufferSequence& buffers,
  13. BOOST_ASIO_MOVE_ARG(ReadHandler) handler)
  14. {
  15. .......
  16. }

Since async_send and async_receive functions will return immediately, and not block current thread, you should pass a callback function as the parameter which receives the result of read/write operations:

  1. void handler(
  2. const boost::system::error_code& error, // Result of operation.
  3. std::size_t bytes_transferred // Number of bytes processed.
  4. )

There is a simple client/server example. Below is client code:

  1. #include <boost/asio.hpp>
  2. #include <functional>
  3. #include <iostream>
  4. #include <memory>
  5. void callback(
  6. const boost::system::error_code& error,
  7. std::size_t bytes_transferred,
  8. std::shared_ptr<boost::asio::ip::tcp::socket> socket,
  9. std::string str)
  10. {
  11. if (error)
  12. {
  13. std::cout << error.message() << '\n';
  14. }
  15. else if (bytes_transferred == str.length())
  16. {
  17. std::cout << "Message is sent successfully!" << '\n';
  18. }
  19. else
  20. {
  21. socket->async_send(
  22. boost::asio::buffer(str.c_str() + bytes_transferred, str.length() - bytes_transferred),
  23. std::bind(callback, std::placeholders::_1, std::placeholders::_2, socket, str));
  24. }
  25. }
  26. int main()
  27. {
  28. try
  29. {
  30. boost::asio::io_context io_context;
  31. boost::asio::ip::tcp::endpoint endpoint{
  32. boost::asio::ip::make_address("192.168.35.145"),
  33. 3303};
  34. std::shared_ptr<boost::asio::ip::tcp::socket> socket{new boost::asio::ip::tcp::socket{io_context}};
  35. socket->connect(endpoint);
  36. std::cout << "Connect to " << endpoint << " successfully!\n";
  37. std::string str{"Hello world!"};
  38. socket->async_send(
  39. boost::asio::buffer(str),
  40. std::bind(callback, std::placeholders::_1, std::placeholders::_2, socket, str));
  41. socket->get_executor().context().run();
  42. }
  43. catch (std::exception& e)
  44. {
  45. std::cerr << e.what() << '\n';
  46. return -1;
  47. }
  48. return 0;
  49. }

Let’s go through the code:

(1) Since socket object is non-copyable (please refer socket), socket is created as an shared pointer:

  1. ......
  2. std::shared_ptr<boost::asio::ip::tcp::socket> socket{new boost::asio::ip::tcp::socket{io_context}};
  3. ......

(2) Because the callback only has two parameters, it needs to use std::bind to pass additional parameters:

  1. ......
  2. std::bind(callback, std::placeholders::_1, std::placeholders::_2, socket, str)
  3. ......

(3) async_send does not guarantee all the bytes are sent (boost::asio::async_write returns either all bytes are sent successfully or an error occurs), so needs to reissue async_send in callback:

  1. ......
  2. if (error)
  3. {
  4. ......
  5. }
  6. else if (bytes_transferred == str.length())
  7. {
  8. ......
  9. }
  10. else
  11. {
  12. socket->async_send(......);
  13. }

(4) io_context.run function will block until all work has finished and there are no
more handlers to be dispatched, or until the io_context has been stopped:

  1. socket->get_executor().context().run();

If there is no io_context.run function, the program will exit immediately.

Check the server code who uses async_receive:

  1. #include <ctime>
  2. #include <functional>
  3. #include <iostream>
  4. #include <string>
  5. #include <boost/asio.hpp>
  6. void callback(
  7. const boost::system::error_code& error,
  8. std::size_t,
  9. char recv_str[]) {
  10. if (error)
  11. {
  12. std::cout << error.message() << '\n';
  13. }
  14. else
  15. {
  16. std::cout << recv_str << '\n';
  17. }
  18. }
  19. int main()
  20. {
  21. try
  22. {
  23. boost::asio::io_context io_context;
  24. boost::asio::ip::tcp::acceptor acceptor(
  25. io_context,
  26. boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 3303));
  27. for (;;)
  28. {
  29. boost::asio::ip::tcp::socket socket(io_context);
  30. acceptor.accept(socket);
  31. char recv_str[1024] = {};
  32. socket.async_receive(
  33. boost::asio::buffer(recv_str),
  34. std::bind(callback, std::placeholders::_1, std::placeholders::_2, recv_str));
  35. socket.get_executor().context().run();
  36. socket.get_executor().context().restart();
  37. }
  38. }
  39. catch (std::exception& e)
  40. {
  41. std::cerr << e.what() << std::endl;
  42. }
  43. return 0;
  44. }

There are two caveats you need to pay attention to:

(1) Just for demo purpose: for every client, the callback is called only once;
(2) io_context.restart must be called to invoke another io_context.run.

Correspondingly, you can also check how to use boost::asio::async_read.

Build and run programs. Client outputs following:

  1. $ ./client
  2. Connect to 192.168.35.145:3303 successfully!
  3. Message is sent successfully!

Server outputs following:

  1. $ ./server
  2. Hello world!