简体   繁体   中英

How to determine when a socket receives EOF? (C++20 boost asio coroutines)

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, eg:

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM