Consider a simple echo server:
#include <boost/asio.hpp>
#include <boost/asio/experimental/as_tuple.hpp>
#include <iostream>
#include <vector>
using boost::asio::awaitable;
using boost::asio::buffer;
using boost::asio::co_spawn;
using boost::asio::io_context;
using boost::asio::detached;
namespace ip = boost::asio::ip;
using boost::asio::use_awaitable;
// echo server
awaitable<void> serve_coroutine(ip::tcp::socket s) {
std::vector<unsigned char> data_buf;
data_buf.resize(4096);
for (;;) {
auto n = co_await s.async_read_some(buffer(data_buf, data_buf.size()),
use_awaitable);
auto ep = s.remote_endpoint();
std::cout << "R from " << ep.address().to_string()
<< " " << ep.port() << " L=" << n << std::endl;
while (n) {
size_t written = co_await s.async_write_some(buffer(data_buf, n), use_awaitable);
n -= written;
}
}
}
awaitable<void> listen_coroutine(ip::tcp::acceptor& acceptor) {
for (;;) {
auto [e, client] = co_await acceptor.async_accept(
boost::asio::experimental::as_tuple(use_awaitable));
if (!e) {
auto ex = client.get_executor();
// create working coroutine for data-transmission
co_spawn(ex, serve_coroutine(std::move(client)), detached);
} else {
std::cerr << "accept failed: " << e.message() << std::endl;
}
}
}
int main(int argc, char** argv) {
if (argc < 3) {
std::cout << "Usage: " << argv[0]
<< " <bind_address> <bind_port>" << std::endl;
return 1;
}
try {
io_context ctx;
// bind address
ip::tcp::endpoint listen_endpoint{ip::make_address(argv[1]),
static_cast<ip::port_type>(std::stoi(argv[2]))};
// create acceptor
ip::tcp::acceptor acceptor{ctx, listen_endpoint};
// add coroutine to execution queue
co_spawn(ctx, listen_coroutine(acceptor), detached);
// start executing coroutines
ctx.run();
} catch (boost::system::system_error& e) {
std::cerr << "boost system error: " << e.what() << std::endl;
} catch (std::exception& e) {
std::cerr << "E: " << e.what() << std::endl;
}
return 0;
}
(Build with g++ -std=c++20
or clang++ -stdlib=libc++ -fcoroutines-ts
)
When the remote peer closes connection, co_await async_read_some
never returns
It seems that boost io_service simply destroyed everything when closing connection
If I insert an object into serve_coroutine
and track its constructor and destructor, I could find it destructed when closing connection
Then what's the proper way to handle connection-closing events? If you are developing a game, you need to clear the player's data and tell everyone that he's gone offline when you determine his connection is closed
You can catch the exception:
for (;;) {
try {
auto n = co_await s.async_read_some(
buffer(data_buf, data_buf.size()), use_awaitable);
auto ep = s.remote_endpoint();
std::cout << "R from " << ep.address().to_string() << " "
<< ep.port() << " L=" << n << std::endl;
while (n) {
size_t written = co_await s.async_write_some(
buffer(data_buf, n), use_awaitable);
n -= written;
}
} catch (std::exception& e) {
std::cerr << "boost system error: " << e.what() << std::endl;
}
}
Which will print
boost system error: End of file [asio.misc:2]
Or you can use an alternative method to receive the error_code. You can see an example in your very listing:
auto [e, client] = co_await acceptor.async_accept(
boost::asio::experimental::as_tuple(use_awaitable));
So, e.g.:
for (;;) {
auto [ec, n] = co_await s.async_read_some(
buffer(data_buf, data_buf.size()),
boost::asio::experimental::as_tuple(use_awaitable));
auto ep = s.remote_endpoint();
std::cout << "R from " << ep.address().to_string() << " " << ep.port()
<< " L=" << n << " (" << ec.message() << ")" << std::endl;
if (!ec)
break;
while (n) {
size_t written =
co_await s.async_write_some(buffer(data_buf, n), use_awaitable);
n -= written;
}
}
Will display things like:
R from 127.0.0.1 51586 L=4 (Success)
R from 127.0.0.1 51586 L=1 (Success)
R from 127.0.0.1 51586 L=1 (Success)
R from 127.0.0.1 51586 L=1 (Success)
R from 127.0.0.1 51586 L=0 (End of file)
R from 127.0.0.1 51590 L=1 (Success)
R from 127.0.0.1 51590 L=1 (Success)
R from 127.0.0.1 51590 L=0 (End of file)