Operações Assíncronas E/S

Diferentemente da API sockets do UNIX, o Asio possui habilidades de leitura & gravação(read/write) assíncronas inclusas. Ainda pode usar basic_stream_socket como exemplo, e um par de implementações assim:

	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)
	{
		.......
	}

Como as funções async_send e async_receive retornam imediatamente, e não bloqueiam a thread atual, você deve passar uma função de retorno de chamada como o parâmetro que recebe o resultado das operações de leitura & gravação:

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

Há um exemplo simples de cliente/servidor. Abaixo está o código do cliente:

	#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;
	}

Vamos analisar o código:

(1) Como o objeto sockets é non-copyable (sockets), sockets é criado como um ponteiro inteligente de memória compartilhada (shared_pointer):

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

(2) Como o callback possui apenas dois parâmetros, ele precisa usar std::bind para passar parâmetros adicionais:

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

(3) async_send não garante que todos os bytes sejam enviados (boost::asio::async_write retorna todos os bytes enviados com sucesso ou ocorre um erro), portanto, é necessário reemitir async_send no callback:

	......
	if (error)
	{
	    ......
	}
	else if (bytes_transferred == str.length())
	{
	    ......
	}
	else
	{
	    socket->async_send(......);
	}

(4) A função io_context.run será bloqueada até que todo o trabalho termine e não haja mais handlers(manipuladores) a serem despachados, ou até que o io_context seja interrompido:

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

Se não houver a função io_context.run, o programa será encerrado imediatamente.

Verifique o código do servidor que usa 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;
	}

Há duas advertências às quais você precisa prestar atenção:

(1) Apenas para fins de demonstração: para cada cliente, o callback é chamado apenas uma vez;
(2) O io_context.restart deve ser chamado para chamar outro io_context.run.

Da mesma forma, você também pode verificar como usar o boost::asio::async_read.

Compile e execute os programas.

O cliente exibirá o seguinte:

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

Servidor emitirar o seguinte:

$ ./server
Hello world!