问题描述
#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
。
我假设io_context
中的class A
属性以某种方式被破坏了。基本上,我要实现的是在单独的类中侦听某些UDP端口。我该如何解决?
解决方法
问题在于您在aClass
中声明了B
成员:
class B {
public:
A aClass;
explicit B(const A& a) : aClass(a) {}
...
aClass
成员的类型为A
,因此当您执行aClass(a)
时,将执行副本。您已经为A
定义了一个副本构造函数,但是它不会复制已经打开的套接字,而在副本中留下了一个默认初始化的(因此是未打开的)套接字。
请考虑以下示例类:
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
时,它不会从bar
中取值c
,而是将foo_
成员默认初始化为foo
。同样的问题也适用于A
类的套接字成员。
解决方案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>
来代替B
A
,进而是A
持有的套接字。
解决方案2
如果您要将A
传递给A
但又希望B
拥有持有的套接字的所有权,则另一种解决方案是移动构造B
由A
实例完成,而无需使用(智能)指针的间接调用。
这样,您可以利用udp::socket
的move构造函数。但是,这需要您将io_context
从A
中移出,因为io_context
没有移动构造器。
#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