我正在与其他服务器(相同或类似的系统)交换一个名为struct update_packet
的结构,使用UDP
和sendto(..)
通过recvfrom()
套接字运行相同的程序。
update_packet
需要采用通用消息格式,这意味着其字段具有预定的固定大小,结构的大小是字段的总和。
struct node {
uint32_t IP;
uint16_t port;
int16_t nil;
uint16_t server_id;
uint16_t cost;
};
struct update_packet {
uint16_t num_update_fields;
uint16_t port;
uint32_t IP;
struct node * nodes;
update_packet() :
num_update_fields(num_nodes), IP(myIP), port(myport)
{//fill in nodes array};
};
(update_packet
包含指针数组struct node
)
我使用reinterpret_cast
通过UDP发送update packet
的实例,然后编译并发送到正确的目的地。
int update_packet_size = sizeof(up);
sendto(s, reinterpret_cast<const char*>(&up), update_packet_size, 0,
(struct sockaddr *)&dest_addr, sizeof(dest_addr));
然而,当我收到它并尝试通过
解码时struct update_packet update_msg =
reinterpret_cast<struct update_packet>(recved_msg);
我收到错误
In function ‘int main(int, char**)’:
error: invalid cast from type ‘char*’ to type ‘update_packet’
struct update_packet update_msg =
reinterpret_cast<struct update_packet>(recved_msg);
为什么会出现此错误,我该如何解决?
此外,这是通过套接字在struct
实例中交换数据的正确方法吗?如果没有,我该怎么办?我是否需要http://beej.us/guide/bgnet/examples/pack2.c中的pack()
功能?
答案 0 :(得分:5)
<强>普通强>
在其他问题中已正确回答演员问题。
但是,您不应该依赖指针强制转换来通过网络发送/接收结构,原因很多,包括:
这会导致代码可能会运行一段时间,但几年之后会导致很多问题,如果有人更改编译器,平台等等......因为这是一个教育项目你应该尝试以正确的方式做到这一点......
出于这个原因,将结构中的数据转换为char数组以便通过网络发送或写入文件应该仔细地进行,逐个变量,如果可能的话,考虑到endianness。此过程称为&#34;序列化&#34;。
详细序列化
序列化意味着您将数据结构转换为可以通过网络发送的字节数组。
序列化格式不一定是二进制格式:text或xml是可能的选项。如果数据量很小,文本可能是最好的解决方案,并且您只能使用字符串流来依赖STL(std :: istringstream和std :: ostringstream)
有几个好的库用于序列化为二进制,例如Qt中的Boost :: serialization或QDataStream。 你也可以自己做,看看&#34; C ++序列化&#34;
使用STL简单地序列化为文本
在您的情况下,您可能只使用以下内容序列化为文本字符串:
std::ostringstream oss;
oss << up.port;
oss << up.IP;
oss << up.num_update_fields;
for(unsigned int i=0;i<up.num_update_fields;i++)
{
oss << up.nodes[i].IP;
oss << up.nodes[i].port;
oss << up.nodes[i].nil;
oss << up.nodes[i].server_id;
oss << up.nodes[i].cost;
}
std::string str = oss.str();
char * data_to_send = str.data();
unsigned int num_bytes_to_send = str.size();
用于反序列化收到的数据:
std::string str(data_received, num_bytes_received);
std::istringstream(str);
update_packet up;
iss >> up.port;
iss >> up.IP;
iss >> up.num_update_fields;
//maximum number of nodes should be checked here before doing memory allocation!
up.nodes = (nodes*)malloc(sizeof(node)*up.num_update_fields);
for(unsigned int i=0;i<up.num_update_fields;i++)
{
iss >> up.nodes[i].IP;
iss >> up.nodes[i].port;
iss >> up.nodes[i].nil;
iss >> up.nodes[i].server_id;
iss >> up.nodes[i].cost;
}
这将是100%便携和安全。您可以通过检查iss错误标志来验证数据有效性。
你也可以为了安全起见:
iss >> up.num_update_fields;
之后的节点数量,如果它太大则只需中断解码,然后再分配一个巨大的缓冲区,这会导致程序崩溃,也可能导致系统崩溃。网络攻击基于&#34;漏洞&#34;像这样:如果没有进行这种检查,你可能会让他分配比RAM大100倍的缓冲区,从而导致服务器崩溃。简单二进制序列化(不支持字节顺序/字节顺序):
替换:
oss.write << up.port;
人:
oss.write((const char *)&up.port, sizeof(up.port));
<强>字节序强>
但是在你的项目中,Big-Endian是必需的。如果您在PC(x86)上运行,则需要在每个字段中反转字节。
1)第一个选项:手工
const char * ptr = &up.port;
unsigned int s = sizeof(up.port);
for(unsigned int i=0; i<s; i++)
oss.put(ptr[s-1-i]);
终极代码:检测字节顺序(这不难做到 - 在SO上查找)并调整序列化代码。
2)第二个选项:使用像boost或Qt
这样的库这些库允许您选择输出数据的字节顺序。然后他们自动检测平台字节序并自动完成工作。
答案 1 :(得分:2)
您不能转换指向结构的指针,但您可以转换指向结构的指针。
更改
struct update_packet update_msg =
reinterpret_cast<struct update_packet>(recved_msg);
到
update_packet * update_msg =
reinterpret_cast<update_packet *>(recved_msg);
是的,你至少需要pack()
,因为发送端的编译器可能会以不同的方式添加填充。但它不是100%安全。您还考虑到发送和接收机器的字节顺序不同。我建议您研究正确的序列化机制。
答案 2 :(得分:2)
您也可以使用:
struct update_packet update_msg;
memcpy(&update_msg, recved_msg, size-of-message);
但您必须确保size-of-message
正是您所寻找的。 p>
答案 3 :(得分:1)
仅就解码(您的计算机-您的规则)而言,可以在GCC和Clang中使用这样的组合(使用Boost.Endian库)同时考虑字节序和打包:
#include <boost/endian/arithmetic.hpp>
using boost::endian::big_uint16_t;
using boost::endian::big_uint32_t;
using boost::endian::big_uint64_t;
#pragma pack(push, 1)
enum class e_message_type: uint8_t {
hello = 'H',
goodbye = 'G'
};
struct message_header {
big_uint16_t size;
e_message_type message_type;
std::byte reserved;
};
static_assert(sizeof(header) == 4);
struct price_quote {
big_uint64_t price;
big_uint32_t size;
big_uint32_t timestamp;
};
static_assert(sizeof(header) == 16);
template<class T> struct envelope {
message_header header;
T payload;
};
static_assert(sizeof(envelope<price_quote>) == 20);
#pragma pack(pop)
// and then
auto& x = *static_cast<envelope const*>(buffer.data());