【问题标题】:What causes bad file descriptor error with async_write?是什么导致 async_write 出现错误的文件描述符错误?
【发布时间】:2020-09-27 10:27:14
【问题描述】:

我正在将 boost asio 用于客户端服务器应用程序并遇到了这个问题,这个错误信息并不那么丰富(至少对我而言;)),我将结构作为消息来回发送,发送效果很好从客户端,但服务器端几乎类似的尝试导致了这个问题(相当错误):Send failed: Bad file descriptor

这是发送部分的 sn-p(请询问 cmets 中要求的任何其他详细信息):

void read_from_ts(const char*  buf, int len) {  // this is the read callback function
    if (len <= 0) {
        std::cerr << "Error: Connection closed by peer. " << __FILE__ << ":" << __LINE__ << std::endl;
        tcp_client_.close(tcp_connection_);
        tcp_connection_ = nullptr;
        ios_.stop(); // exit
        return;
    }

    const UserLoginRequest *obj = reinterpret_cast<const UserLoginRequest *>(buf);
    int tempId = obj->MessageHeaderIn.TemplateID;
    Responses r;
    switch(tempId)
    {
      case 10018: //login
        const UserLoginRequest *obj = reinterpret_cast<const UserLoginRequest *>(buf);

        //std::cout<<"Login request received"<<"\n";
        boost::asio::ip::tcp::socket sock_(ios_);
        r.login_ack(sock_);

        /*will add more*/
    }

    std::cout << "RX: " << len << " bytes\n";
  }

  class Responses
  {
    public:
      int login_ack(boost::asio::ip::tcp::socket& socket)
      {
        //std::cout<<"here"<<"\n";
        UserLoginResponse info;
        MessageHeaderOutComp mh;
        ResponseHeaderComp rh;

        rh.MsgSeqNum = 0; //no use for now
        rh.RequestTime = 0; //not used at all
        mh.BodyLen = 53; //no use
        mh.TemplateID = 10019; // IMP

        info.MessageHeaderOut = mh;
        info.LastLoginTime  = 0;
        info.DaysLeftForPasswdExpiry = 10; //not used
        info.GraceLoginsLeft = 10; //not used
        rh.SendingTime = 0;
        info.ResponseHeader = rh;
        //Pad6 not used
        async_write(socket, boost::asio::buffer(&info, sizeof(info)), on_send_completed);
      }
      static void on_send_completed(boost::system::error_code ec, size_t bytes_transferred) {
          if (ec)
              std::cout << "Send failed: " << ec.message() << "\n"; //**error shows up here**
          else
              std::cout << "Send succesful (" << bytes_transferred << " bytes)\n";
      }
  };
};

【问题讨论】:

    标签: c++ sockets asynchronous boost boost-asio


    【解决方案1】:

    更新刚刚在阅读您的代码时注意到第三个琐碎的解释,请参阅添加的项目符号

    通常当文件描述符在其他地方关闭时。

    如果您使用的是 Asio,这通常意味着

    • socket¹ 对象已被破坏。当代码在异步操作期间没有延长对象的生命周期时,这可能是一个初学者错误

    • 文件描述符被传递给关闭它的其他代码(例如使用native_handle(https://www.boost.org/doc/libs/1_73_0/doc/html/boost_asio/reference/basic_stream_socket/native_handle.html) 并且其他代码关闭它(例如因为它假定所有权并进行错误处理)。

    • 更新 或者,这可能意味着您的套接字从未初始化开始。在你的代码中我读到:

      //std::cout<<"Login request received"<<"\n";
      boost::asio::ip::tcp::socket sock_(ios_);
      r.login_ack(sock_);
      

      但是,这只是构造一个新的套接字,从不连接或绑定它并尝试对其执行login_ack。这是行不通的,因为login_ack 不绑定也不连接套接字并在其上调用async_write

      您的意思是使用tcp_connection_.sock_ 或类似名称吗?

    一般而言,在第三方代码中关闭文件描述符是多线程代码中的错误,因为它会引发竞争条件,从而导致任意流损坏(参见例如How do you gracefully select() on sockets that could be closed on another thread?

    在大多数情况下,您可以改用shutdown

    未定义的行为

    另外,请注意

    • info 没有足够的生命周期(它在 async_write 完成之前超出范围
    • 你的 login_ack 永远不会返回值

    想象修复

    这就是我想象的周围代码在消除上述问题时的样子。

    事实上,由于响应的静态特性,它可能会简单得多,但我不想假设所有响应都那么简单,所以我选择了共享指针生命周期:

    Live On Coliru

    #include <boost/asio.hpp>
    #include <boost/core/ignore_unused.hpp>
    #include <iostream>
    using boost::asio::ip::tcp;
    
    struct MyProgram {
        boost::asio::io_context ios_;
    
        struct UserLoginRequest {
            struct MessageHeaderInComp {
                int TemplateID = 10018;
            } MessageHeaderIn;
        };
    
        struct Connection {
            tcp::socket sock_;
            template <typename Executor>
            Connection(Executor ex) : sock_{ex} {}
        };
    
        std::unique_ptr<Connection> tcp_connection_ = std::make_unique<Connection>(ios_.get_executor());
    
        struct {
            void close(std::unique_ptr<Connection> const&);
        } tcp_client_;
    
        struct Responses {
            static auto login_ack() {
                struct UserLoginResponse {
                    struct MessageHeaderOutComp {
                        int BodyLen = 53;             // no use
                        int TemplateID = 10019;       // IMP
                    } MessageHeaderOut;
                    int LastLoginTime  = 0;
                    int DaysLeftForPasswdExpiry = 10; // not used
                    int GraceLoginsLeft = 10;         // not used
                    struct ResponseHeaderComp {
                        int MsgSeqNum = 0;            // no use for now
                        int RequestTime = 0;          // not used at all
                        int SendingTime = 0;
                    } ResponseHeader;
                };
                return std::make_shared<UserLoginRequest>();
            }
        };
    
        void read_from_ts(const char*  buf, int len) {  // this is the read callback function
            if (len <= 0) {
                std::cerr << "Error: Connection closed by peer. " << __FILE__ << ":" << __LINE__ << std::endl;
                tcp_client_.close(tcp_connection_);
                tcp_connection_ = nullptr;
                ios_.stop(); // exit
                return;
            }
    
            const UserLoginRequest *obj = reinterpret_cast<const UserLoginRequest *>(buf);
            int tempId = obj->MessageHeaderIn.TemplateID;
    
            switch(tempId) {
                case 10018: //login
                    const UserLoginRequest *obj = reinterpret_cast<const UserLoginRequest *>(buf);
    
                    //std::cout<<"Login request received"<<"\n";
                    boost::asio::ip::tcp::socket sock_(ios_);
                    auto response = Responses::login_ack();
                    async_write(tcp_connection_->sock_, boost::asio::buffer(response.get(), sizeof(*response)),
                        [response](boost::system::error_code ec, size_t bytes_transferred) {
                            if (ec)
                                std::cout << "Send failed: " << ec.message() << "\n"; //**error shows up here**
                            else
                                std::cout << "Send succesful (" << bytes_transferred << " bytes)\n";
                        });
    
                    /*will add more*/
                    boost::ignore_unused(obj);
            }
    
            std::cout << "RX: " << len << " bytes\n";
          }
    
    };
    
    int main() {
        MyProgram p;
    }
    

    ¹(或acceptor/posix::strean_descriptor

    【讨论】:

    • 链接的答案是 C 问题的 C 答案。请注意,C++ 有更好的工具来实现相同的目标。例如。只要至少有一个线程需要,std::shared_ptr&lt;&gt; 是保持boost::socket 活动和打开的一种非常有效的方法。
    • @MSalters 我正在链接这样一场比赛的解释。
    • @DEEP 我刚刚注意到您在代码中使用了sock_ unconnected。这显然也会触发错误。添加了更新
    • 所以很可能不再连接
    • 是的,确实如此,非常感谢@sehe提供的信息丰富的回答
    猜你喜欢
    • 1970-01-01
    • 2021-01-05
    • 2021-03-17
    • 1970-01-01
    • 1970-01-01
    • 2021-03-12
    • 1970-01-01
    • 1970-01-01
    • 2016-08-26
    相关资源
    最近更新 更多