得到 receive_from:使用 boost::asio 时文件描述符错误

got receive_from: Bad file descriptor while using boost::asio

#include "string"
#include <boost/asio.hpp>
#include <boost/asio/io_context.hpp>
using namespace boost::asio::ip;
 
class A {
    public:
    std::string address;
    std::string port;
    boost::asio::io_context io_context;
    udp::socket socketInstance = udp::socket(io_context);
    udp::endpoint endpointSetup;
 
    explicit A(std::string addrs, std::string port) {
        this->address = addrs;
        this->port = port;
    }
 
    A(const A& a) {
        this->address = a.address;
        this->port = a.port;
    }
 
    void attachListener() {
        endpointSetup = udp::endpoint(make_address(address), std::stoi(port));
        socketInstance = udp::socket(io_context, endpointSetup);
    }
};
 
class B {
    public:
    A aClass;
 
    explicit B(const A& a) : aClass(a) {}
 
    void getData() {
        std::array<char, 1024> recv_buffer{};
 
        aClass.socketInstance.receive_from(boost::asio::buffer(recv_buffer), aClass.endpointSetup);
    }
};
 
int main() {
    A a("192.168.1.49", "5080");
    a.attachListener();
    B b(a);
    b.getData();
}

aClass.socketInstance.receive_from(boost::asio::buffer(recv_buffer), aClass.endpointSetup); 部分产生错误 Bad file descriptor

我推测 class A 中的 io_context 属性 不知何故被破坏了。基本上,我想要实现的是在单独的 类 中监听一些 UDP 端口。我该如何解决?

问题在于您在 B 中声明 aClass 成员:

class B {
    public:
    A aClass;
 
    explicit B(const A& a) : aClass(a) {}
...

aClass 成员的类型为 A,因此当您执行 aClass(a) 时,将执行复制。您已经为 A 定义了一个复制构造函数,但它不会复制已经打开的套接字,在副本中留下一个 default-initialized(因此未打开)套接字。

考虑以下示例 class:

class C {
public:
  std::string foo_ = "foo";
  C(const std::string &foo) : foo_{foo} {}
  C(const C &c) {}
};

如果你会运行下面的代码:

  C c{"bar"};
  C c2{c};
  std::cout << c2.foo_ << std::endl;

你会得到输出 foo。这是因为 C 的复制构造函数不复制 foo_ 成员。因此,当 c2 被初始化时,它不会从 c 中取值 bar,而是 default-initialize 将 foo_ 成员foo。同样的问题适用于 A class.

的套接字成员

解决方案 1

通过使 B 持有指向 A:

的指针来避免复制
class B {
public:
  A* aClass;

  explicit B(A* a) : aClass(a) {}

  void getData() {
    std::array<char, 1024> recv_buffer{};

    aClass->socketInstance.receive_from(boost::asio::buffer(recv_buffer),
                                       aClass->endpointSetup);
  }
};

然后在 main 中执行:

B b(&a);

这样,当您创建 B 时,它仍然可以访问您为原始 A 初始化的套接字。

除了使用 A*,您还可以使用智能指针,即 std::unique_ptr<A>std::shared_ptr<A>,如果您想在 [=18= 的意义上表达所有权] 拥有 A,因此拥有 A.

的套接字

解决方案 2

另一种解决方案是 move-construct A,如果您想将 A 传递给 B 但还希望 B 获得所有权A 实例持有的套接字,不使用(智能)指针的间接寻址。

这样您就可以利用 move-construct 或 udp::socket。但是,这需要您将 io_context 移出 A,因为 io_context 没有 move-constructor.

#include <boost/asio.hpp>
#include <boost/asio/io_context.hpp>

using namespace boost::asio::ip;

class A {
public:
  std::string address;
  uint16_t port;

  boost::asio::io_context &io_context;

  udp::socket socketInstance;
  udp::endpoint endpointSetup;

  explicit A(boost::asio::io_context &io_context, std::string addrs,
             uint16_t port)
      : io_context{io_context}, address{addrs}, port{port}, socketInstance{
                                                                io_context} {}

  void attachListener() {
    endpointSetup = udp::endpoint{make_address(address), port};
    socketInstance = udp::socket{io_context, endpointSetup};
  }
};

class B {
public:
  A aClass;

  explicit B(A &&a) : aClass{std::move(a)} {}

  void getData() {
    std::array<char, 1024> recv_buffer{};

    aClass.socketInstance.receive_from(boost::asio::buffer(recv_buffer),
                                       aClass.endpointSetup);
  }
};

int main() {
  boost::asio::io_context io_context{};

  A a{io_context, "127.0.0.1", 5080};
  a.attachListener();
  B b{std::move(a)};
  b.getData();
}

两种解决方案都可以测试,例如与

netcat --udp localhost 5080