如何确定套接字何时收到 EOF? (C++20 提升 asio 协程)

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

考虑一个简单的回显服务器:

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

(使用 g++ -std=c++20clang++ -stdlib=libc++ -fcoroutines-ts 构建)

当远程端关闭连接时,co_await async_read_some从不returns

似乎 boost io_service 在关闭连接时简单地破坏了一切

如果我将一个对象插入 serve_coroutine 并跟踪它的构造函数和析构函数,我会发现它在关闭连接时被破坏了

那么处理连接关闭事件的正确方法是什么?如果你在开发游戏,你需要清除玩家的数据,当你确定他的连接关闭时,告诉所有人他已经下线了

你可以捕获异常:

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

哪个会打印

boost system error: End of file [asio.misc:2]

或者您可以使用其他方法接收 error_code。您可以在您的清单中看到一个示例:

auto [e, client] = co_await acceptor.async_accept(
    boost::asio::experimental::as_tuple(use_awaitable));

所以,例如:

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

将显示如下内容:

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)