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

asio可否在accept handler外调用async_write 如何解决Bad file descriptor错误

核心结论

async_write 不需要必须在异步操作的handler内部调用,你遇到的Bad file descriptor错误通常是两类原因导致:

  • 调用async_write时,socket还未完成accept流程,尚未和远端建立有效连接,处于未打开状态
  • 跨线程直接操作socket,未做线程安全调度,asio的socket本身不支持多线程并发操作,直接跨线程调用会触发状态异常

实现方案与代码示例

核心思路是:等accept完成后标记连接就绪,外部调用写操作时,通过post将写逻辑投递到io_context的运行线程执行,同时用队列处理异步写重叠问题,保证线程安全。

1. 连接类实现

#include <boost/asio.hpp>
#include <memory>
#include <queue>
#include <mutex>

using boost::asio::ip::tcp;
class TCPConnection : public std::enable_shared_from_this<TCPConnection> {
public:
    using Ptr = std::shared_ptr<TCPConnection>;
    static Ptr Create(boost::asio::io_context& io) {
        return Ptr(new TCPConnection(io));
    }

    tcp::socket& Socket() {
        return m_socket;
    }

    // 对外暴露的线程安全写接口,可在任意位置调用
    void AsyncWrite(const std::string& send_data) {
        // 将写操作投递到io_context所在线程执行
        boost::asio::post(m_socket.get_executor(),
            [self = shared_from_this(), send_data]() {
                // 先校验连接有效性
                if (!self->m_is_connected || !self->m_socket.is_open()) {
                    return;
                }
                // 写入队列,避免多个async_write重叠
                bool write_in_progress = !self->m_write_queue.empty();
                self->m_write_queue.push(send_data);
                if (!write_in_progress) {
                    self->DoWrite();
                }
            });
    }

    bool IsConnected() const {
        return m_is_connected;
    }

    // 仅在accept handler中调用,标记连接就绪
    void MarkConnected() {
        m_is_connected = true;
    }

private:
    TCPConnection(boost::asio::io_context& io) : m_socket(io) {}

    void DoWrite() {
        boost::asio::async_write(m_socket,
            boost::asio::buffer(m_write_queue.front()),
            [self = shared_from_this()](const boost::system::error_code& err, size_t write_len) {
                if (!err) {
                    self->m_write_queue.pop();
                    if (!self->m_write_queue.empty()) {
                        self->DoWrite();
                    }
                } else {
                    // 写失败,关闭连接
                    self->m_is_connected = false;
                    self->m_socket.close();
                    // 清空待发送队列
                    std::queue<std::string> empty;
                    std::swap(self->m_write_queue, empty);
                }
            });
    }

    tcp::socket m_socket;
    bool m_is_connected = false;
    std::queue<std::string> m_write_queue;
};

2. 监听类实现

class TCPAcceptor {
public:
    TCPAcceptor(boost::asio::io_context& io, uint16_t listen_port)
        : m_io(io), m_acceptor(io, tcp::endpoint(tcp::v4(), listen_port)) {
        StartAccept();
    }

    // 对外提供获取就绪连接的接口
    TCPConnection::Ptr GetReadyConnection() {
        std::lock_guard<std::mutex> lock(m_conn_mutex);
        return m_current_conn;
    }

private:
    void StartAccept() {
        m_current_conn = TCPConnection::Create(m_io);
        m_acceptor.async_accept(m_current_conn->Socket(),
            [this](const boost::system::error_code& err) {
                if (!err) {
                    // 仅标记连接就绪,无需在handler内调用写操作
                    m_current_conn->MarkConnected();
                }
                // 继续监听下一个连接
                StartAccept();
            });
    }

    boost::asio::io_context& m_io;
    tcp::acceptor m_acceptor;
    TCPConnection::Ptr m_current_conn;
    std::mutex m_conn_mutex;
};

3. 外部调用示例

int main() {
    boost::asio::io_context io;
    TCPAcceptor acceptor(io, 8888);

    // 启动io_context运行线程
    std::thread io_thread([&io]() { io.run(); });

    // 模拟任意外部线程调用写操作
    std::thread external_write_thread([&acceptor]() {
        while (true) {
            auto conn = acceptor.GetReadyConnection();
            if (conn && conn->IsConnected()) {
                conn->AsyncWrite("test data from outside accept handler\n");
            }
            std::this_thread::sleep_for(std::chrono::seconds(2));
        }
    });

    io_thread.join();
    external_write_thread.join();
    return 0;
}

注意事项
  • 所有对socket的操作都要投递到io_context的运行线程执行,避免跨线程并发操作socket
  • 调用写操作前必须校验连接状态,确保socket已完成accept且处于打开状态
  • 异步写操作不能重叠提交,需要用队列缓存待发送数据,等上一次写操作回调完成后再提交下一次

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 06:24:06