Boost asio:包括 <arpa/inet.h> 会导致套接字始终输出 0 字节



我正在尝试包含<arpa/inet.h>在一个低级库中,这样我就可以访问库中的hton*和ntoh*函数。低级库由运行Boost asio套接字的高级代码调用。我知道Boost-asio包含hton*和ntoh*函数,但我希望避免将所有Boost-acio链接到库,因为我只需要hton*/ntoh*。

然而,如果我简单地包括<arpa/inet.h>在底层库中,0字节总是从Boost-asio套接字发送。Wireshark确认。

这是我想包括<arpa/inet.h>但不是Boost。如果<arpa/inet.h>包含,则将发送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 bytesn", total_size);
usleep(1000000);
}
}

再次,简单地去除"#包括<arpa/inet.h>quot;将使此代码按预期运行,并为每个数据包发送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(正如您所做的那样(调用未定义的行为。

这很可能是对以下事实的解释:;它停止工作";有一些变化。它从未工作:它可能只是意外地出现工作,但这是未定义的行为。

在获得构造函数。事实上,我认为这是不可能的。简而言之,如果你想把你的结构当作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; }
};

在这里,您可能需要进行endianness转换。

建议

简而言之,如果你想让它发挥作用,我会说保持简单。

与其通过为每个值添加getter/setter来在所有地方创建负责endianness转换的非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 bytesn", 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);
}

在这方面有一些变化,它可以成为模板或PduHeder&(如果您的消息继承而不是聚合(。但最基本的原理是一样的。

Endianness转换

现在您会注意到,我避免直接使用uint32_t/uint16_t(尽管uint8_t很好,因为它不需要字节排序(。相反,您可以将LongShort定义为它们周围的简单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,除了总是完成必要的转换。

如果你想坐在巨人的肩膀上,你可以使用Boost Endian的更好的类型,它也有很多更先进的设施

演示

在Coliru上直播

#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 bytesn", n);
std::this_thread::sleep_for(1s);
}
}
};
int main(){ }

最新更新