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

Viewed 200
#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();
}

The part aClass.socketInstance.receive_from(boost::asio::buffer(recv_buffer), aClass.endpointSetup); creates the error Bad file descriptor.

I presume the io_context property in class A is destroyed somehow. Basically, what I want to achieve is to listen to some UDP port in a separate classes. How do I solve this?

1 Answers

The problem is with your declaration of the aClass member in B:

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

The aClass member is of type A, so when you do aClass(a), a copy is performed. You have defined a copy constructor for A, but it does not copy the already opened socket, leaving you with a default-initialized (and therefore unopened) socket in the copy.

Consider the following example class:

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

If you would run the following code:

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

You would get the output foo. This is because the copy constructor for C does not copy the foo_ member. Therefore, when c2 is initialized, it will not take the value bar from c, but instead default-initialize the foo_ member to foo. The same problem applies to the socket member of your A class.

Solution 1

Avoid a copy by making B hold a pointer to to an 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);
  }
};

And then in main do:

B b(&a);

This way, when you create a B, it will still have access to the socket you initialized for the original A.

Instead of using A*, you could also use a smart pointer, i.e. std::unique_ptr<A> or std::shared_ptr<A>, if you want to express ownership in the sense that the B owns the A, and thereby the socket held by A.

Solution 2

An alternative solution would be to move-construct A, if you want to pass an A to B but also want B to take ownership of the socket held by the A instance, without using the indirection of a (smart) pointer.

This way you could take advantage of the move-constructor for the udp::socket. However, this would require you to move the io_context out of A, because the io_context does not have 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();
}

Both solutions can be tested e.g. with

netcat --udp localhost 5080
Related