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

非Boost版Asio如何正确存储并复用TCP socket连接?

问题根因

你的socket存储思路本身没有问题,群发逻辑失效是三个明确的实现错误:

  • 遍历发送时未对shared_ptr解引用:asio::write第一个参数要求传入socket流的引用,你直接传入了shared_ptr<asio::ip::tcp::socket>对象,接口不匹配,要么直接编译报错,要么触发未定义行为。
  • 全程吞掉了IO错误码:你定义的ignored_error没有做任何检查,客户端断开、连接重置、发送失败等错误完全无感知,失效的socket一直残存在vector中,后续遍历会持续在无效句柄上操作失败。
  • 群发逻辑绑定在同步阻塞的accept循环中:acceptor.accept()是阻塞调用,只有新客户端连入时才会走到群发步骤,平时不会触发发送;同时同步asio::write也是阻塞的,只要有一个客户端网络卡顿、接收缓存占满,整个循环就会卡在该socket的发送操作上,既无法接收新连接,也无法给其他客户端发消息。
正确存储与复用Socket的实现规则
  • asio::ip::tcp::socket是仅可移动(move-only)类型,不支持拷贝操作,不能直接复制实例存入容器。你用std::move转移所有权、或者用std::shared_ptr托管实例的写法是可行的。
  • 所有IO操作必须检查返回的错误码,一旦检测到连接失效(对端断开、连接重置、超时),立刻把对应socket从容器中移除,避免操作无效句柄。
  • 多线程场景下访问存储socket的容器必须加锁,或者使用asio的strand串行化所有访问操作,避免数据竞争。
可运行修正代码
#include <iostream>
#include <string>
#include <vector>
#include <asio.hpp>
#include <memory>
#include <algorithm>

std::vector<std::shared_ptr<asio::ip::tcp::socket>> clients;

int main() {
    std::cout << "Starting server on 0.0.0.0:6666..." << "\n";

    try {
        asio::io_context io_context;
        asio::ip::tcp::acceptor acceptor(io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 6666));

        for (;;) {
            // 阻塞等待新客户端连接
            asio::ip::tcp::socket socket(io_context);
            acceptor.accept(socket);
   
            asio::error_code ec;
            // 给新连接发送客户端序号
            std::string welcome_msg = "Your client ID: " + std::to_string(clients.size()) + "\n";
            asio::write(socket, asio::buffer(welcome_msg), ec);
            // 新连接刚建立就断开,直接跳过不存入容器
            if (ec) {
                continue;
            }

            // 移动构造shared_ptr存入vector,转移socket所有权
            auto newconn = std::make_shared<asio::ip::tcp::socket>(std::move(socket)); 
            clients.push_back(newconn);

            // 群发逻辑:先清理失效连接,再逐个发送消息
            const std::string broadcast_msg = "Server broadcast: hello\n";
            // 移除所有无效连接
            clients.erase(
                std::remove_if(clients.begin(), clients.end(), [&broadcast_msg, &ec](const std::shared_ptr<asio::ip::tcp::socket>& sock) {
                    if (!sock->is_open()) {
                        return true;
                    }
                    asio::write(*sock, asio::buffer(broadcast_msg), ec);
                    // 发送失败标记为待移除
                    return static_cast<bool>(ec);
                }),
                clients.end()
            );

            std::cout << "Current online client count: " << clients.size() << "\n";
        }
        
    } catch (const std::exception &e) {
        std::cerr << "Server fatal error: " << e.what() << std::endl;
    }

    return EXIT_SUCCESS;
}
补充说明

你注释中提到的1024以下端口权限问题是正确的:类Unix系统(Linux/macOS)、Windows系统都限制普通用户绑定1024以下的知名端口,测试时使用1024以上端口即可。

  • 上述示例是同步阻塞模型,仅用于功能验证。生产环境建议使用异步接口:通过async_accept接收连接,async_write发送数据,将io_context运行在多线程上,避免单个慢连接阻塞整个服务。
  • 如果不想使用shared_ptr,也可以直接定义std::vector<asio::ip::tcp::socket> clients,存入时调用clients.push_back(std::move(socket))转移socket所有权即可,功能完全一致;shared_ptr的优势是异步场景下更容易管理socket生命周期,避免异步操作执行时socket已经被提前销毁。

内容的提问来源于stack exchange,提问作者darclander

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 13:15:36