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:
template <typename ConstBufferSequence, typename WriteHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler,
void (boost::system::error_code, std::size_t))
async_send(const ConstBufferSequence& buffers,
BOOST_ASIO_MOVE_ARG(WriteHandler) handler)
{
.......
}
template <typename MutableBufferSequence, typename ReadHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler,
void (boost::system::error_code, std::size_t))
async_receive(const MutableBufferSequence& buffers,
BOOST_ASIO_MOVE_ARG(ReadHandler) handler)
{
.......
}
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:
void handler(
const boost::system::error_code& error, // Result of operation.
std::size_t bytes_transferred // Number of bytes processed.
)
There is a simple client/server example. Below is client code:
#include <boost/asio.hpp>
#include <functional>
#include <iostream>
#include <memory>
void callback(
const boost::system::error_code& error,
std::size_t bytes_transferred,
std::shared_ptr<boost::asio::ip::tcp::socket> socket,
std::string str)
{
if (error)
{
std::cout << error.message() << '\n';
}
else if (bytes_transferred == str.length())
{
std::cout << "Message is sent successfully!" << '\n';
}
else
{
socket->async_send(
boost::asio::buffer(str.c_str() + bytes_transferred, str.length() - bytes_transferred),
std::bind(callback, std::placeholders::_1, std::placeholders::_2, socket, str));
}
}
int main()
{
try
{
boost::asio::io_context io_context;
boost::asio::ip::tcp::endpoint endpoint{
boost::asio::ip::make_address("192.168.35.145"),
3303};
std::shared_ptr<boost::asio::ip::tcp::socket> socket{new boost::asio::ip::tcp::socket{io_context}};
socket->connect(endpoint);
std::cout << "Connect to " << endpoint << " successfully!\n";
std::string str{"Hello world!"};
socket->async_send(
boost::asio::buffer(str),
std::bind(callback, std::placeholders::_1, std::placeholders::_2, socket, str));
socket->get_executor().context().run();
}
catch (std::exception& e)
{
std::cerr << e.what() << '\n';
return -1;
}
return 0;
}
Let’s go through the code:
(1) Since socket object is non-copyable (please refer socket), socket is created as an shared pointer:
......
std::shared_ptr<boost::asio::ip::tcp::socket> socket{new boost::asio::ip::tcp::socket{io_context}};
......
(2) Because the callback only has two parameters, it needs to use std::bind
to pass additional parameters:
......
std::bind(callback, std::placeholders::_1, std::placeholders::_2, socket, str)
......
(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:
......
if (error)
{
......
}
else if (bytes_transferred == str.length())
{
......
}
else
{
socket->async_send(......);
}
(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:
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
:
#include <ctime>
#include <functional>
#include <iostream>
#include <string>
#include <boost/asio.hpp>
void callback(
const boost::system::error_code& error,
std::size_t,
char recv_str[]) {
if (error)
{
std::cout << error.message() << '\n';
}
else
{
std::cout << recv_str << '\n';
}
}
int main()
{
try
{
boost::asio::io_context io_context;
boost::asio::ip::tcp::acceptor acceptor(
io_context,
boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 3303));
for (;;)
{
boost::asio::ip::tcp::socket socket(io_context);
acceptor.accept(socket);
char recv_str[1024] = {};
socket.async_receive(
boost::asio::buffer(recv_str),
std::bind(callback, std::placeholders::_1, std::placeholders::_2, recv_str));
socket.get_executor().context().run();
socket.get_executor().context().restart();
}
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
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:
$ ./client
Connect to 192.168.35.145:3303 successfully!
Message is sent successfully!
Server outputs following:
$ ./server
Hello world!