最近我一直在深入研究网络编程,我在构建具有可变“数据”属性的数据包时遇到了一些困难。以前的几个问题有很大帮助,但我仍然缺乏一些实施细节。我试图避免使用可变大小的数组,只使用向量。但我无法正确传输它,我相信它在序列化过程中的某个地方。
现在有些代码。
数据包标头
class Packet {
public:
void* Serialize();
bool Deserialize(void *message);
unsigned int sender_id;
unsigned int sequence_number;
std::vector<char> data;
};
Packet ImpL
typedef struct {
unsigned int sender_id;
unsigned int sequence_number;
std::vector<char> data;
} Packet;
void* Packet::Serialize(int size) {
Packet* p = (Packet *) malloc(8 + 30);
p->sender_id = htonl(this->sender_id);
p->sequence_number = htonl(this->sequence_number);
p->data.assign(size,'&'); //just for testing purposes
}
bool Packet::Deserialize(void *message) {
Packet *s = (Packet*)message;
this->sender_id = ntohl(s->sender_id);
this->sequence_number = ntohl(s->sequence_number);
this->data = s->data;
}
在执行期间,我只需创建一个数据包,分配它的成员,然后相应地发送/接收。以上方法仅负责序列化。不幸的是,数据永远不会被转移。
这里要指出一些事情。我猜测 malloc 是错误的,但我不确定如何计算它(即它会是什么其他值)。除此之外,我不确定以这种方式使用矢量的正确方法,并希望有人向我展示如何(代码示例请!):)
编辑:我已经将问题授予了关于使用矢量数据属性实现的最全面的答案。感谢所有回复!
答案 0 :(得分:3)
这个技巧适用于结构末尾的C风格数组,但不适用于C ++向量。无法保证C ++向量类(并且很可能不会)将其包含的数据放入Packet结构中存在的“头对象”中。相反,该对象将包含指向其他地方的指针,存储实际数据。
答案 1 :(得分:3)
我想你可能想这样做: `
struct PacketHeader
{
unsigned int senderId;
unsigned int sequenceNum;
};
class Packet
{
protected:
PacketHeader header;
std::vector<char> data;
public:
char* serialize(int& packetSize);
void deserialize(const char* data,int dataSize);
}
char* Packet::serialize(int& packetSize)
{
packetSize = this->data.size()+sizeof(PacketHeader);
char* packetData = new char[packetSize];
PacketHeader* packetHeader = (PacketHeader*)packetData;
packetHeader->senderId = htonl(this->header.senderId);
packetHeader->sequenceNum = htonl(this->header.sequenceNum);
char* packetBody = (packetData + sizeof(packetHeader));
for(size_t i=0 ; i<this->data.size() ; i++)
{
packetBody[i] = this->data.at(i);
}
return packetData;
}
void deserialize(const char* data,int dataSize)
{
PacketHeader* packetHeader = (PacketHeader*)data;
this->header.senderId = ntohl(packetHeader->senderId);
this->header.sequenceNum = ntohl(packetHeader->sequenceNum);
this->data.clear();
for(int i=sizeof(PacketHeader) ; i<dataSize ; i++)
{
this->data.push_back(data[i]);
}
}
`
这些代码不包括绑定检查和自由分配的数据,不要忘记从serialize()函数中删除返回的缓冲区,也可以使用memcpy而不是使用循环将每个字节的字节复制到std中或从std中复制: :矢量
大多数编译器有时会在结构中添加填充,如果在不禁用填充的情况下完整地发送这些数据会导致问题,如果使用visual studio,则可以使用#pragma pack(1)来执行此操作
免责声明:我实际上并没有编译这些代码,您可能需要重新检查它
答案 2 :(得分:2)
我认为问题的中心是你尝试'序列化'那样的向量,你可能假设向量的状态信息被传输了。正如您所发现的那样,当您试图通过网络移动对象时,这并不会真正起作用,而指针之类的东西在其他计算机上并不意味着什么。
我认为最简单的方法是将Packet更改为以下结构:
struct Packet {
unsigned int sender_id;
unsigned int sequence_number;
unsigned int vector_size;
char data[1];
};
data[1]
位是可变长度数组的旧C技巧 - 它必须是结构中的最后一个元素,因为您实际上是在编写结构的大小。您拥有来为此获取数据结构的分配,否则您将处于受伤的世界。
您的序列化功能看起来像这样:
void* Packet::Serialize(std::vector<char> &data) {
Packet* p = (Packet *) malloc(sizeof(Packet) + data.size());
p->sender_id = htonl(this->sender_id);
p->sequence_number = htonl(this->sequence_number);
p->vector_size = htonl(data.size());
::memcpy(p->data, data[0], size);
}
正如您所看到的,我们将传输数据大小和向量的内容,复制到一个容易传输的普通C数组中。你必须记住,在你的网络发送例程中,你必须正确计算结构的大小,因为你必须发送sizeof(Packet) + sizeof(data)
,否则你将得到矢量切断并重新变好缓冲区溢出区域。
免责声明 - 我没有测试上面的代码,它只是从内存中编写的,因此您可能需要修复奇怪的编译错误。
答案 3 :(得分:2)
我认为您需要直接使用套接字函数返回的字节数组。
出于这些目的,在协议中有两个不同的消息部分是很好的。第一部分是固定大小的“标题”。这将包括后面跟随的大小,“有效负载”或示例中的data
。
所以,借用你的一些片段并扩展它们,也许你会有这样的东西:
typedef struct { unsigned int sender_id; unsigned int sequence_number; unsigned int data_length; // this is new } PacketHeader;
那么当你得到一个缓冲区时,你会把它当作PacketHeader*
,并检查data_length
以了解后面的字节向量中会出现多少字节。
我还想补充几点......
制作这些字段unsigned int
并不明智。 C和C ++的标准没有指定int
有多大,而且你想要在所有编译器上都可以预测的东西。我建议在uint32_t
<stdint.h>
请注意,当您从套接字获取字节时...绝不能保证与另一端写入send()
或write()
的大小相同。您可能会收到不完整的消息(术语中的“数据包”),或者您可能会在一次read()
或recv()
调用中获得多个消息。如果缺少单个请求,您有责任缓冲这些请求,或者如果您在同一个通道中收到多个请求,则循环使用它们。
答案 4 :(得分:1)
由于您已经分配了一些原始内存,然后将其视为非POD类类型的初始化对象,因此这种强制转换非常危险。这可能会在某些时候导致崩溃。
Packet* p = (Packet *) malloc(8 + 30);
查看代码,我假设你想要从调用seralize函数的Packet
对象写出一个字节序列。在这种情况下,您不需要第二个数据包对象。您可以创建适当大小的字节向量,然后跨数据复制。
e.g。
void* Packet::Serialize(int size)
{
char* raw_data = new char[sizeof sender_id + sizeof sequence_number + data.size()];
char* p = raw_data;
unsigned int tmp;
tmp = htonl(sender_id);
std::memcpy(p, &tmp, sizeof tmp);
p += sizeof tmp;
tmp = htonl(sequence_number);
std::memcpy(p, &tmp, sizeof tmp);
p += sizeof tmp;
std::copy(data.begin(), data.end(), p);
return raw_data;
}
这可能不完全是你想要的,因为我不确定你的size
参数的最终对象是什么,并且你的接口可能不安全,因为你返回一个我认为应该是原始数据的指针动态分配。使用管理动态分配内存生命周期的对象会更安全,然后调用者不必猜测是否以及如何释放内存。
此外,调用者无法知道分配了多少内存。这可能与解除分配无关,但可能是要复制或流式传输此缓冲区,则需要此信息。
最好返回std::vector<char>
或者通过引用获取一个,或者甚至使该函数成为模板并使用输出迭代器。