在C中将结构序列化/反序列化为char *

我有一个结构

struct Packet { int senderId; int sequenceNumber; char data[MaxDataSize]; char* Serialize() { char *message = new char[MaxMailSize]; message[0] = senderId; message[1] = sequenceNumber; for (unsigned i=0;i<MaxDataSize;i++) message[i+2] = data[i]; return message; } void Deserialize(char *message) { senderId = message[0]; sequenceNumber = message[1]; for (unsigned i=0;i<MaxDataSize;i++) data[i] = message[i+2]; } }; 

我需要将其转换为char *,最大长度MaxMailSize> MaxDataSize用于通过网络发送,然后在另一端反序列化

我不能使用tpl或任何其他库。

有没有办法让这更好我对此不太满意,或者这是我们能做的最好的事情。

由于这是通过网络发送的,我强烈建议您在发送之前将这些数据转换为网络字节顺序,并在接收时返回主机字节顺序。 这是因为字节顺序在各处都不一样,一旦你的字节顺序不正确,就可能很难将它们反转(取决于接收端使用的编程语言)。 字节排序函数与套接字一起定义,并命名为htons()htonl()ntohs()ntohl() 。 (在这些名称中:h表示’主机’或您的计算机,n表示’网络’,s表示’短’或16位值,l表示’长’或32位值)。

然后你自己进行序列化,C和C ++没有自动执行它的方法。 一些软件可以生成代码来为您完成,例如ASN.1实现asn1c,但它们很难使用,因为它们不仅仅涉及通过网络复制数据。

您可以让一个类代表您在软件中使用的对象,包括所有细节和成员函数以及您需要的任何内容。 然后你有一个’序列化’结构,它更多地描述了最终会在网络上发生什么。

为了确保编译器能够执行您告诉他的任何操作,您需要指示它“打包”结构。 我在这里使用的指令是gcc,如果你没有使用gcc,请参阅你的编译器文档。

然后序列化和反序列化例程只是在两者之间进行转换,确保字节顺序和类似的细节。

 #include  /* ntohl htonl */ #include  /* memcpy */ class Packet { int senderId; int sequenceNumber; char data[MaxDataSize]; public: char* Serialize(); void Deserialize(char *message); }; struct SerializedPacket { int senderId; int sequenceNumber; char data[MaxDataSize]; } __attribute__((packed)); void* Packet::Serialize() { struct SerializedPacket *s = new SerializedPacket(); s->senderId = htonl(this->senderId); s->sequenceNumber = htonl(this->sequenceNumber); memcpy(s->data, this->data, MaxDataSize); return s; } void Packet::Deserialize(void *message) { struct SerializedPacket *s = (struct SerializedPacket*)message; this->senderId = ntohl(s->senderId); this->sequenceNumber = ntohl(s->sequenceNumber); memcpy(this->data, s->data, MaxDataSize); } 

取决于你是否有足够的地方……你可能只是使用流:)

 std::string Serialize() { std::ostringstream out; char version = '1'; out << version << senderId << '|' << sequenceNumber << '|' << data; return out.str(); } void Deserialize(const std::string& iString) { std::istringstream in(iString); char version = 0, check1 = 0, check2 = 0; in >> version; switch(version) { case '1': senderId >> check1 >> sequenceNumber >> check2 >> data; break; default: // Handle } // You can check here than 'check1' and 'check2' both equal to '|' } 

我欣然承认它需要更多的地方……或者它可能。

实际上,在32位架构上, int通常覆盖4个字节(4个字符)。 如果值优于9999,则使用流序列化它们只需要4个’char’,这通常会提供一些空间。

另请注意,您可能应该在您的信息流中包含一些警卫,只是为了检查您什么时候回来它没关系。

版本控制可能是一个好主意,它不会花费太多,并允许计划外的后期开发。

 int senderId; int sequenceNumber; ... char *message = new char[MaxMailSize]; message[0] = senderId; message[1] = sequenceNumber; 

你在这里覆盖了值。 senderId和sequenceNumber都是整数,并且在大多数体系结构上占用的字符数大于(char)字节。 尝试更像这样的东西:

 char * message = new char[MaxMailSize]; int offset = 0; memcpy(message + offset, &senderId, sizeof(senderId)); offset += sizeof(senderId); memcpy(message + offset, &sequenceNumber, sizeof(sequenceNumber)); offset += sizeof(sequenceNumber); memcpy(message + offset, data, MaxDataSize); 

编辑:用昏迷写的固定代码。 此外,如评论中所述,由于字节差异,任何此类数据包都不可移植。

要回答一般问题,C ++没有reflection机制,因此手动序列化和反序列化基于每个类定义的函数是您可以做的最好的。 话虽这么说,你编写的序列化函数会破坏你的数据。 这是一个正确的实现:

 char * message = new char[MaxMailSize]; int net_senderId = htonl(senderId); int net_sequenceNumber = htonl(sequenceNumber); memcpy(message, &net_senderId, sizeof(net_senderId)); memcpy(message + sizeof(net_senderId), &net_sequenceNumber, sizeof(net_sequenceNumber)); 

正如其他post中所提到的, senderIdsequenceNumber都是int类型,它可能比char大,所以这些值将被截断。

如果这是可以接受的,那么代码就可以了。 如果没有,那么你需要将它们分成它们的组成字节。 鉴于您使用的协议将指定多字节字段的字节顺序,最容易实现和最不模糊的方法是通过移位。

例如,假设senderIdsequenceNumber都是2个字节长,并且协议要求高字节首先出现:

 char* Serialize() { char *message = new char[MaxMailSize]; message[0] = senderId >> 8; message[1] = senderId; message[2] = sequenceNumber >> 8; message[3] = sequenceNumber; memcpy(&message[4], data, MaxDataSize); return message; } 

我还建议用memcpy (如果可用)替换for循环,因为它不太可能降低效率,并且它会缩短代码。

最后,这一切都假设char是一个字节长。 如果不是,则需要屏蔽所有数据,例如:

  message[0] = (senderId >> 8) & 0xFF; 

您可以使用Protocol Buffers定义和序列化结构和类。 这是谷歌内部使用的,并且具有非常小的传输机制。

http://code.google.com/apis/protocolbuffers/