async_write 导致错误文件描述符错误的原因是什么?

What causes bad file descriptor error with async_write?

我正在将 boost asio 用于客户端服务器应用程序,并且 运行 解决了这个问题,这个信息量不大的错误消息(至少对我来说是这样;)),我将结构作为消息来回发送,从客户端发送工作非常好,但是从服务器端几乎类似的尝试导致了这个问题(相当错误):Send failed: Bad file descriptor

这是发送部分的片段(请在评论中询问所需的任何其他详细信息):

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";
      }
  };
};

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

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

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

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

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

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

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

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

    Did you mean to use tcp_connection_.sock_ or similar?

In general closing file-descriptors in third-party code is an error in multi-threaded code because it invites race conditions which will lead to arbitrary stream corruption (see e.g. How do you gracefully select() on sockets that could be closed on another thread?)

You can use shutdown instead in the majority of cases

未定义的行为

另外,请注意

  • info 没有足够的生命周期(它在 async_write 完成之前超出范围
  • 你的login_ack从来没有returns一个值

想象修复

这是我想象的周围代码在解决上述问题后的样子。

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

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