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
相关产品推荐
相关产品推荐

