2009-10-31 5 views
4

J'ai un structsérialisation/désérialisation d'un struct à un char * en C

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]; 
    } 

}; 

Je dois convertir à un char *, longueur maximale MaxMailSize> MaxDataSize pour l'envoi sur le réseau et désérialiser puis à l'autre extrémité

Je ne peux pas utiliser tpl ou une autre bibliothèque.

Y a-t-il un moyen de rendre cela meilleur? Je ne suis pas très à l'aise avec cela, ou est-ce le meilleur que nous pouvons faire.

Répondre

6

puisque ce doit être envoyé sur un réseau, je vous conseille fortement de convertir ces données en ordre des octets du réseau avant de transmettre et de nouveau dans l'ordre des octets hôte lors de la réception. En effet, l'ordre des octets n'est pas le même partout, et une fois que vos octets ne sont pas dans le bon ordre, il peut être très difficile de les inverser (selon le langage de programmation utilisé du côté réception). Les fonctions de classement des octets sont définies avec les sockets et sont nommées htons(), htonl(), ntohs() et ntohl(). (dans ce nom: h signifie 'hôte' ou votre ordinateur, n signifie 'réseau', s signifie 'court' ou 16 bits, l signifie 'long' ou 32 bits). Alors vous êtes seul avec la sérialisation, C et C++ n'ont aucun moyen automatique de l'exécuter. Certains logiciels peuvent générer du code pour le faire pour vous, comme l'implémentation ASN.1 asn1c, mais ils sont difficiles à utiliser car ils impliquent bien plus que la simple copie de données sur le réseau.

+0

Merci pour l'aide! Je vais essayer ça. –

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

Vous remplacez les valeurs ici. senderId et sequenceNumber sont à la fois ints et occuperont plus de sizeof (char) octets sur la plupart des architectures. Essayez quelque chose comme ceci:

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); 

EDIT: code fixe écrit dans un état de stupeur. En outre, comme indiqué dans le commentaire, un tel paquet n'est pas portable en raison de différences endian.

+0

Le second argument de memcpy prend const void *, mais vous faisant passer l'int. Ce devrait être: 'memcpy (message + offset, & senderId, sizeof (senderId))'. De plus, vous n'avez pas besoin du dernier memcpy, car 'data' n'a pas besoin d'être sérialisé car c'est simplement le tampon utilisé pour la désérialisation. –

+1

Il n'écrase rien quand il attribue un 'int' à un' char'. Il est (peut-être) tronquer leurs valeurs, cependant. –

+0

-1 L'utilisation de 'memcpy' pour' senderId' et 'sequenceNumber' n'est pas portable, car vous ne pouvez pas garantir l'ordre des octets pour ces valeurs. Mieux vaut le faire avec changement et masquage. –

0

Pour répondre à votre question en général, C++ ne possède pas de mécanisme de réflexion, et donc les fonctions de sérialisation manuelle et de désérialisation définies par classe sont ce que vous pouvez faire de mieux. Cela étant dit, la fonction de sérialisation que vous avez écrite altèrera vos données. Voici une mise en œuvre correcte:

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)); 
3

Vous pouvez avoir une classe représentant l'objet que vous utilisez dans votre logiciel avec toutes les subtilités et fonctions de membre et tout ce dont vous avez besoin. Ensuite, vous avez une structure «sérialisée» qui est plus une description de ce qui finira sur le réseau. Pour que le compilateur fasse ce que vous lui dites de faire, vous devez lui ordonner d'emballer la structure. La directive que j'ai utilisée ici est pour gcc, consultez votre document de compilation si vous n'utilisez pas gcc.

Ensuite, la routine de sérialisation et de désérialisation se convertit simplement entre les deux, en assurant l'ordre des octets et des détails comme ça.

#include <arpa/inet.h> /* ntohl htonl */ 
#include <string.h>  /* 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); 
} 
+1

est-il sûr de libérer le void * retourné par serialize avec un simple 'delete ptr'? Cela fait un moment que je travaillais en C++, mais puisque vous allouez de la mémoire de tas, vous devrez la désallouer quelque part. – Jherico

+0

Mmmm, non probablement pas (au moins pas garanti). Il vaut probablement mieux utiliser malloc() et utiliser free() alors. – 246tNt

+0

Ou mieux encore, oubliez d'allouer de la mémoire sur le tas; utilisez simplement le même tampon de pile interne pour la sérialisation et la désérialisation –

0

Comme mentionné dans les autres postes, et senderIdsequenceNumber sont tous deux de type int, qui est susceptible d'être plus grande que char, de sorte que ces valeurs seront tronqués.

Si cela est acceptable, le code est OK.Si non, alors vous devez les diviser en leurs octets constitutifs. Étant donné que le protocole que vous utilisez spécifiera l'ordre des octets des champs multi-octets, la façon la plus portable et la moins ambiguë de le faire est le décalage.

Par exemple, disons que senderId et sequenceNumber sont tous deux 2 octets, et le protocole exige que l'octet supérieur va d'abord:

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; 
} 

Je vous recommande également de remplacer la boucle for avec memcpy (si disponible), car il est peu probable qu'il soit moins efficace, et cela raccourcit le code. Finalement, tout ceci suppose que char est long d'un octet. Dans le cas contraire, toutes les données doivent être masquées, par exemple:

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

Selon si vous avez assez de place ou non ... vous pouvez simplement utiliser les flux :)

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 '|' 
} 

J'admets volontiers qu'il faut plus de place ... ou que cela pourrait.

En fait, sur une architecture 32 bits, un int couvre habituellement 4 octets (4 caractères). Les sérialiser en utilisant des flux ne prennent que plus de 4 'char' si la valeur est supérieure à 9999, ce qui donne habituellement de la place.

Notez également que vous devriez probablement inclure des gardes dans votre flux, juste pour vérifier quand vous l'avez récupéré que tout va bien. La versionnage est probablement une bonne idée, elle ne coûte pas cher et permet un développement ultérieur non planifié.

0

Vous pouvez utiliser des tampons de protocole pour définir et sérialiser des structures et des classes. C'est ce que google utilise en interne, et dispose d'un très petit mécanisme de transfert.

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