Boost asio:包括 <arpa/inet.h> 导致套接字始终输出 0 字节
Boost asio: including <arpa/inet.h> causes socket to always output 0 bytes
我正在尝试将 包含在低级库中,以便我可以访问库中的 hton* 和 ntoh* 函数。低级库被高级代码调用 运行 一个 Boost asio 套接字。我知道 Boost asio 包含 hton* 和 ntoh* 函数,但我想避免将所有 Boost asio 链接到库,因为 hton*/ntoh* 是我所需要的。
但是,如果我简单地将 包含在低级库中,则始终会从 Boost asio 套接字发送 0 字节。通过 Wireshark 确认。
这是 class 我想包括 但不包括 Boost 的地方。如果包含 ,将发送 0 个字节。
#pragma pack(push, 1)
#include "PduHeader.h"
#include <arpa/inet.h>
class ClientInfoPdu
{
public:
ClientInfoPdu(const uint16_t _client_receiver_port)
{
set_client_receiver_port(_client_receiver_port);
}
PduHeader pdu_header{CLIENT_INFO_PDU, sizeof(client_receiver_port)};
inline void set_client_receiver_port(const uint16_t _client_receiver_port)
{
//client_receiver_port = htons(_client_receiver_port);
client_receiver_port = _client_receiver_port;
}
inline uint16_t get_client_receiver_port()
{
return client_receiver_port;
}
inline size_t get_total_size()
{
return sizeof(PduHeader) + pdu_header.get_pdu_payload_size();
}
private:
uint16_t client_receiver_port;
};
#pragma pack(pop)
这是更高级别的代码,其中包含 Boost 并尝试通过套接字发送数据。打印输出表明发送了 5 个字节,但实际发送了 0 个字节。
#include "ServerConnectionThread.h"
#include "config/ClientConfig.h"
#include "protocol_common/ClientInfoPdu.h"
#include <boost/asio.hpp>
#include <unistd.h>
using boost::asio::ip::udp;
void ServerConnectionThread::execute()
{
boost::asio::io_service io_service;
udp::endpoint remote_endpoint =
udp::endpoint(boost::asio::ip::address::from_string(SERVER_IP), SERVER_PORT);
udp::socket socket(io_service);
socket.open(udp::v4());
ClientInfoPdu client_info_pdu = ClientInfoPdu(RECEIVE_PORT);
while (true)
{
uint16_t total_size = client_info_pdu.get_total_size();
socket.send_to(boost::asio::buffer(&client_info_pdu, total_size), remote_endpoint);
printf("sent %u bytes\n", total_size);
usleep(1000000);
}
}
同样,简单地删除“#include ”将使此代码按预期运行并每个数据包发送 5 个字节。
ClientInfoPdu
是怎么定义的?这看起来很可能是 UB:
boost::asio::buffer(&client_info_pdu, total_size)
总大小是 sizeof(PduHeader) + pdu_header.get_pdu_payload_size()
(所以 sizeof(PduHeader) + 2
);
第一个问题是您在混合使用访问修饰符,从而破坏了类型的 POD/standard_layout 属性。
#include <type_traits>
static_assert(std::is_standard_layout_v<PduHeader> && std::is_trivial_v<PduHeader>);
static_assert(std::is_standard_layout_v<ClientInfoPdu> && std::is_trivial_v<ClientInfoPdu>);
这将无法编译。将类型视为 POD(如您所做的那样)调用
Undefined Behaviour.
This is likely the explanation for the fact that "it stops working" with some changes. It never worked: it might just accidentally have appeared to work, but it was undefined behaviour.
在获得 POD 的便利性的同时实现 POD 性并不容易
构造函数。事实上,我认为这是不可能的。简而言之,如果你想
将您的结构视为 C 风格的 POD 类型,使它们... C 风格的 POD 类型。
另一件事:`PduHeader I 的可能实现
可以看到为你工作看起来有点像这样:
enum MsgId{CLIENT_INFO_PDU=0x123};
struct PduHeader {
MsgId id;
size_t payload_size;
size_t get_pdu_payload_size() const { return payload_size; }
};
在这里,您可能会再次 have/need 字节顺序转换。
建议
简而言之,如果你想让它起作用,我会说保持简单。
与其通过为每个值添加 getters/setters 来在所有负责字节顺序转换的地方创建非 POD 类型,为什么不创建一个简单的用户定义类型来始终执行此操作,并使用他们呢?
struct PduHeader {
Short id; // or e.g. uint8_t
Long payload_size;
};
struct ClientInfoPdu {
PduHeader pdu_header; // or inheritance, same effect
Short client_receiver_port;
};
然后将其用作 POD 结构:
while (true) {
ClientInfoPdu client_info_pdu;
init_pdu(client_info_pdu);
auto n = socket.send_to(boost::asio::buffer(&client_info_pdu, sizeof(client_info_pdu)), remote_endpoint);
printf("sent %lu bytes\n", n);
std::this_thread::sleep_for(1s);
}
函数 init_pdu
可以通过重载每个子消息来实现:
void init_pdu(ClientInfoPdu& msg) {
msg.pdu_header.id = CLIENT_INFO_PDU;
msg.pdu_header.payload_size = sizeof(msg);
}
There are variations on this where it can become a template or take a
PduHeder&
(if your message inherits instead of aggregates). But the basic
principle is the same.
字节顺序转换
现在您会注意到我避免直接使用 uint32_t
/uint16_t
(尽管 uint8_t
很好,因为它不需要字节顺序)。相反,您可以将 Long
和 Short
定义为围绕它们的简单 POD 包装器:
struct Short {
operator uint16_t() const { return ntohs(value); }
Short& operator=(uint16_t v) { value = htons(v); return *this; }
private:
uint16_t value;
};
struct Long {
operator uint32_t() const { return ntohl(value); }
Long& operator=(uint32_t v) { value = htonl(v); return *this; }
private:
uint32_t value;
};
赋值和转换意味着您可以将其用作另一个
int32_t/int16_t 除了总是完成必要的转换。
If you want to satnd on the shoulder of giants instead, you can use the better types from Boost Endian, which also has lots more advanced facilities
演示
#include <type_traits>
#include <cstdint>
#include <thread>
#include <arpa/inet.h>
using namespace std::chrono_literals;
#pragma pack(push, 1)
enum MsgId{CLIENT_INFO_PDU=0x123};
struct Short {
operator uint16_t() const { return ntohs(value); }
Short& operator=(uint16_t v) { value = htons(v); return *this; }
private:
uint16_t value;
};
struct Long {
operator uint32_t() const { return ntohl(value); }
Long& operator=(uint32_t v) { value = htonl(v); return *this; }
private:
uint32_t value;
};
static_assert(std::is_standard_layout_v<Short>);
static_assert(std::is_trivial_v<Short>);
static_assert(std::is_standard_layout_v<Long>);
static_assert(std::is_trivial_v<Long>);
struct PduHeader {
Short id; // or e.g. uint8_t
Long payload_size;
};
struct ClientInfoPdu {
PduHeader pdu_header; // or inheritance, same effect
Short client_receiver_port;
};
void init_pdu(ClientInfoPdu& msg) {
msg.pdu_header.id = CLIENT_INFO_PDU;
msg.pdu_header.payload_size = sizeof(msg);
}
static_assert(std::is_standard_layout_v<PduHeader> && std::is_trivial_v<PduHeader>);
static_assert(std::is_standard_layout_v<ClientInfoPdu> && std::is_trivial_v<ClientInfoPdu>);
#pragma pack(pop)
#include <boost/asio.hpp>
//#include <unistd.h>
using boost::asio::ip::udp;
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 6767
#define RECEIVE_PORT 6868
struct ServerConnectionThread {
void execute() {
boost::asio::io_service io_service;
udp::endpoint const remote_endpoint =
udp::endpoint(boost::asio::ip::address::from_string(SERVER_IP), SERVER_PORT);
udp::socket socket(io_service);
socket.open(udp::v4());
while (true) {
ClientInfoPdu client_info_pdu;
init_pdu(client_info_pdu);
auto n = socket.send_to(boost::asio::buffer(&client_info_pdu, sizeof(client_info_pdu)), remote_endpoint);
printf("sent %lu bytes\n", n);
std::this_thread::sleep_for(1s);
}
}
};
int main(){ }
我正在尝试将
但是,如果我简单地将
这是 class 我想包括
#pragma pack(push, 1)
#include "PduHeader.h"
#include <arpa/inet.h>
class ClientInfoPdu
{
public:
ClientInfoPdu(const uint16_t _client_receiver_port)
{
set_client_receiver_port(_client_receiver_port);
}
PduHeader pdu_header{CLIENT_INFO_PDU, sizeof(client_receiver_port)};
inline void set_client_receiver_port(const uint16_t _client_receiver_port)
{
//client_receiver_port = htons(_client_receiver_port);
client_receiver_port = _client_receiver_port;
}
inline uint16_t get_client_receiver_port()
{
return client_receiver_port;
}
inline size_t get_total_size()
{
return sizeof(PduHeader) + pdu_header.get_pdu_payload_size();
}
private:
uint16_t client_receiver_port;
};
#pragma pack(pop)
这是更高级别的代码,其中包含 Boost 并尝试通过套接字发送数据。打印输出表明发送了 5 个字节,但实际发送了 0 个字节。
#include "ServerConnectionThread.h"
#include "config/ClientConfig.h"
#include "protocol_common/ClientInfoPdu.h"
#include <boost/asio.hpp>
#include <unistd.h>
using boost::asio::ip::udp;
void ServerConnectionThread::execute()
{
boost::asio::io_service io_service;
udp::endpoint remote_endpoint =
udp::endpoint(boost::asio::ip::address::from_string(SERVER_IP), SERVER_PORT);
udp::socket socket(io_service);
socket.open(udp::v4());
ClientInfoPdu client_info_pdu = ClientInfoPdu(RECEIVE_PORT);
while (true)
{
uint16_t total_size = client_info_pdu.get_total_size();
socket.send_to(boost::asio::buffer(&client_info_pdu, total_size), remote_endpoint);
printf("sent %u bytes\n", total_size);
usleep(1000000);
}
}
同样,简单地删除“#include
ClientInfoPdu
是怎么定义的?这看起来很可能是 UB:
boost::asio::buffer(&client_info_pdu, total_size)
总大小是 sizeof(PduHeader) + pdu_header.get_pdu_payload_size()
(所以 sizeof(PduHeader) + 2
);
第一个问题是您在混合使用访问修饰符,从而破坏了类型的 POD/standard_layout 属性。
#include <type_traits>
static_assert(std::is_standard_layout_v<PduHeader> && std::is_trivial_v<PduHeader>);
static_assert(std::is_standard_layout_v<ClientInfoPdu> && std::is_trivial_v<ClientInfoPdu>);
这将无法编译。将类型视为 POD(如您所做的那样)调用 Undefined Behaviour.
This is likely the explanation for the fact that "it stops working" with some changes. It never worked: it might just accidentally have appeared to work, but it was undefined behaviour.
在获得 POD 的便利性的同时实现 POD 性并不容易 构造函数。事实上,我认为这是不可能的。简而言之,如果你想 将您的结构视为 C 风格的 POD 类型,使它们... C 风格的 POD 类型。
另一件事:`PduHeader I 的可能实现 可以看到为你工作看起来有点像这样:
enum MsgId{CLIENT_INFO_PDU=0x123};
struct PduHeader {
MsgId id;
size_t payload_size;
size_t get_pdu_payload_size() const { return payload_size; }
};
在这里,您可能会再次 have/need 字节顺序转换。
建议
简而言之,如果你想让它起作用,我会说保持简单。
与其通过为每个值添加 getters/setters 来在所有负责字节顺序转换的地方创建非 POD 类型,为什么不创建一个简单的用户定义类型来始终执行此操作,并使用他们呢?
struct PduHeader {
Short id; // or e.g. uint8_t
Long payload_size;
};
struct ClientInfoPdu {
PduHeader pdu_header; // or inheritance, same effect
Short client_receiver_port;
};
然后将其用作 POD 结构:
while (true) {
ClientInfoPdu client_info_pdu;
init_pdu(client_info_pdu);
auto n = socket.send_to(boost::asio::buffer(&client_info_pdu, sizeof(client_info_pdu)), remote_endpoint);
printf("sent %lu bytes\n", n);
std::this_thread::sleep_for(1s);
}
函数 init_pdu
可以通过重载每个子消息来实现:
void init_pdu(ClientInfoPdu& msg) {
msg.pdu_header.id = CLIENT_INFO_PDU;
msg.pdu_header.payload_size = sizeof(msg);
}
There are variations on this where it can become a template or take a
PduHeder&
(if your message inherits instead of aggregates). But the basic principle is the same.
字节顺序转换
现在您会注意到我避免直接使用 uint32_t
/uint16_t
(尽管 uint8_t
很好,因为它不需要字节顺序)。相反,您可以将 Long
和 Short
定义为围绕它们的简单 POD 包装器:
struct Short {
operator uint16_t() const { return ntohs(value); }
Short& operator=(uint16_t v) { value = htons(v); return *this; }
private:
uint16_t value;
};
struct Long {
operator uint32_t() const { return ntohl(value); }
Long& operator=(uint32_t v) { value = htonl(v); return *this; }
private:
uint32_t value;
};
赋值和转换意味着您可以将其用作另一个 int32_t/int16_t 除了总是完成必要的转换。
If you want to satnd on the shoulder of giants instead, you can use the better types from Boost Endian, which also has lots more advanced facilities
演示
#include <type_traits>
#include <cstdint>
#include <thread>
#include <arpa/inet.h>
using namespace std::chrono_literals;
#pragma pack(push, 1)
enum MsgId{CLIENT_INFO_PDU=0x123};
struct Short {
operator uint16_t() const { return ntohs(value); }
Short& operator=(uint16_t v) { value = htons(v); return *this; }
private:
uint16_t value;
};
struct Long {
operator uint32_t() const { return ntohl(value); }
Long& operator=(uint32_t v) { value = htonl(v); return *this; }
private:
uint32_t value;
};
static_assert(std::is_standard_layout_v<Short>);
static_assert(std::is_trivial_v<Short>);
static_assert(std::is_standard_layout_v<Long>);
static_assert(std::is_trivial_v<Long>);
struct PduHeader {
Short id; // or e.g. uint8_t
Long payload_size;
};
struct ClientInfoPdu {
PduHeader pdu_header; // or inheritance, same effect
Short client_receiver_port;
};
void init_pdu(ClientInfoPdu& msg) {
msg.pdu_header.id = CLIENT_INFO_PDU;
msg.pdu_header.payload_size = sizeof(msg);
}
static_assert(std::is_standard_layout_v<PduHeader> && std::is_trivial_v<PduHeader>);
static_assert(std::is_standard_layout_v<ClientInfoPdu> && std::is_trivial_v<ClientInfoPdu>);
#pragma pack(pop)
#include <boost/asio.hpp>
//#include <unistd.h>
using boost::asio::ip::udp;
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 6767
#define RECEIVE_PORT 6868
struct ServerConnectionThread {
void execute() {
boost::asio::io_service io_service;
udp::endpoint const remote_endpoint =
udp::endpoint(boost::asio::ip::address::from_string(SERVER_IP), SERVER_PORT);
udp::socket socket(io_service);
socket.open(udp::v4());
while (true) {
ClientInfoPdu client_info_pdu;
init_pdu(client_info_pdu);
auto n = socket.send_to(boost::asio::buffer(&client_info_pdu, sizeof(client_info_pdu)), remote_endpoint);
printf("sent %lu bytes\n", n);
std::this_thread::sleep_for(1s);
}
}
};
int main(){ }