得到 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
#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