You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

本地C/S环境下1万并发TCP连接死锁问题排查

问题场景与现象

在这个简单实现中,服务器通过asio::io_context启动listener协程接受入站连接,每建立一个连接就启动一个session协程。同时启动10000个并发客户端与服务器建立连接,服务器会循环向每个客户端写入1KB的数据块。主线程等待所有连接建立完成后,休眠一段时间让服务器与客户端完成数据交互,随后将标志位stop设为true,预期session协程会退出并销毁底层套接字以终止客户端连接,但实际始终有部分连接无法关闭。

运行后卡在3047个客户端连接无法关闭,推测问题出在session协程挂起在async_write调用中从未恢复。按逻辑客户端应该持续读取数据释放缓冲区空间,让服务器完成写入后退出session、终止连接,为何会出现这种死锁场景?


代码实现

全局定义与客户端逻辑

using asio::ip::tcp;
using Executor = asio::io_context::executor_type;
std::atomic_bool stop = false;
std::atomic_int32_t connected = 0;
int numConnections = 10'000;

namespace Client {
    asio::awaitable<void, Executor> client() try {
        const auto ex = co_await asio::this_coro::executor;
        tcp::socket socket{ex};
        tcp::resolver res{ex};
        co_await async_connect(socket, res.resolve("localhost", "12345"), asio::deferred);
        ++connected;

        for (std::vector<char> buf(1024);;)
            co_await async_read(socket, asio::mutable_buffer(buf.data(), buf.size()), asio::deferred);

    } catch (asio::system_error const &se) {
        --connected;
    }
}

服务器逻辑

namespace Server {
    asio::awaitable<void, Executor> session(tcp::socket socket) try {
        for (static const std::vector payload(1024, 'A'); !stop;) {
            co_await async_write(socket, asio::buffer(payload),
                                 asio::as_tuple(asio::deferred));
        }
    } catch (asio::system_error &err) {
        log() << err.code().message() << std::endl;
    }

    asio::awaitable<void, Executor> listener() try {
        const auto executor = co_await asio::this_coro::executor;

        for (tcp::acceptor acceptor{executor, {{}, 12345}};;) {
            co_spawn(executor, session(co_await acceptor.async_accept(asio::deferred)), asio::detached);
        }
    } catch (asio::system_error const &se) {
        log() << se.code().message() << std::endl;
    }
}

主函数

using namespace std::chrono_literals;
using std::this_thread::sleep_for;

#include <ranges>
int main() {
    asio::io_context ctx;
    asio::cancellation_signal signal;

    co_spawn(ctx, Server::listener(), asio::bind_cancellation_slot(signal.slot(), asio::detached));
    std::ranges::for_each(std::views::iota(0, numConnections), [&](int) {
        co_spawn(ctx, Client::client(), asio::detached);
    });

    std::jthread th([&ctx] { ctx.run(); });

    while (connected < numConnections) {
        sleep_for(1s);
        log() << "Clients connected: " << connected << std::endl;
    }

    sleep_for(5s);
    stop = true;
    signal.emit(asio::cancellation_type::terminal);

    while (connected > 0) {
        sleep_for(1s);
        log() << "Clients still connected: " << connected << std::endl;
    }
}

运行输出

main:74 Clients connected: 4097
main:74 Clients connected: 8107
main:74 Clients connected: 8107
main:74 Clients connected: 9174
main:74 Clients connected: 9174
client:25 All clients connected
main:74 Clients connected: 10000
listener:53 Operation aborted.
main:83 Clients still connected: 3047
main:83 Clients still connected: 3047
main:83 Clients still connected: 3047
main:83 Clients still connected: 3047
main:83 Clients still connected: 3047
main:83 Clients still connected: 3047
main:83 Clients still connected: 3047

问题分析与原因

核心死锁点

当stop被设为true时,部分session协程正挂起在async_write操作上,无法被唤醒检查stop标志,导致这些协程无法退出,套接字也不会被销毁,客户端的async_read会一直阻塞,connected计数无法递减。

具体原因

  1. TCP流控与缓冲区积压
    服务器持续向客户端写入数据,当客户端的TCP接收缓冲区被填满后,操作系统会停止向客户端发送数据,服务器的async_write会因为TCP窗口关闭而挂起。此时如果stop被设置,这些挂起的async_write不会主动取消,协程会一直等待TCP窗口重新打开,而客户端因为还在等待async_read完成,不会主动关闭连接,形成循环等待。

  2. 缺乏取消机制
    代码中仅通过stop标志让session协程在async_write完成后退出,但对于已经挂起的async_write操作,没有绑定取消信号。当stop设置后,这些挂起的写操作不会被中断,协程永远无法回到循环条件检查stop的逻辑。

  3. 客户端的阻塞逻辑
    客户端的async_read会一直等待数据,直到套接字被关闭或出错。如果服务器的session协程没有销毁套接字,客户端不会触发异常,connected计数不会减少。

解决方案建议

  • 给session协程的async_write绑定取消槽,当stop触发时主动取消所有挂起的写操作(需要将cancellation_signal传递到session协程中):
    // 修改session协程中的async_write调用
    auto cancel_slot = asio::bind_cancellation_slot(signal.slot(), asio::deferred);
    co_await async_write(socket, asio::buffer(payload), asio::as_tuple(cancel_slot));
    
  • 当stop设置后,主动遍历并关闭所有服务器端套接字,强制终止连接。
  • 客户端增加超时逻辑,长时间未收到数据时主动关闭连接。

内容的提问来源于stack exchange,提问作者Yannik Schrödder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 07:39:55