Envoie une structure sur un socket avec un remplissage et une finalité corrects en C

J’ai plusieurs structures définies pour envoyer sur différents systèmes d’exploitation (réseaux TCP). Les structures définies sont:

struct Struct1 { uint32_t num; char str[10]; char str2[10];} struct Struct2 { uint16_t num; char str[10];} typedef Struct1 a; typedef Struct2 b; 

Les données sont stockées dans un fichier texte. Le format de données est en tant que tel:

  • 123
  • Tarte
  • Croûte

Struct1 a est stocké en tant que 3 parameters distincts. Cependant, struct2 est deux parameters distincts avec les 2ème et 3ème lignes stockées dans char str []. Le problème est que lorsque j’écris sur un serveur via plusieurs réseaux, les données ne sont pas reçues correctement. De nombreux espaces séparent les différents parameters des structures. Comment puis-je assurer un envoi et un remplissage correct lorsque j’écris sur le serveur? Comment stocker les données correctement (tampon dynamic ou tampon fixe)?

Exemple d’écriture: write (fd, & a, sizeof (typedef struct a)); Est-ce correct?

Problème de sortie côté réception pour struct2:

  • 123 (,)
  • 0 (, tarte)
  • 0 (croûte)

Sortie correcte

123 (Tarte, Croûte)

write(fd,&a, sizeof(a)); n’est pas correcte; du moins pas de manière portable, car le compilateur C peut introduire un remplissage entre les éléments pour assurer un alignement correct. sizeof(typedef struct a) n’a même pas de sens.

La manière dont vous devriez envoyer les données dépend des spécifications de votre protocole. En particulier, les protocoles définissent des méthodes d’envoi de chaînes très diverses. Il est généralement plus sûr d’envoyer les membres de la struct séparément; soit par plusieurs appels à écrire, soit par writev(2) . Par exemple, envoyer

 struct { uint32_t a; uint16_t b; } foo; 

sur le réseau, où foo.a et foo.b ont déjà le bon endianness, vous feriez quelque chose comme:

 struct iovec v[2]; v[0].iov_base = &foo.a; v[0].iov_len = sizeof(uint32_t); v[1].iov_base = &foo.b; v[1].iov_len = sizeof(uint16_t); writev(fp, v, 2); 

L’envoi de structures sur le réseau est délicat. Les problèmes suivants que vous pourriez avoir

  1. Byte endiannes questions avec des entiers.
  2. Rembourrage introduit par votre compilateur.
  3. Analyse de chaîne (c’est-à-dire détection des limites de chaîne).

Si les performances ne sont pas votre objective, je suggère de créer des encodeurs et des décodeurs pour chaque structure à envoyer et recevoir (ASN.1, XML ou personnalisée). Si les performances sont vraiment nécessaires, vous pouvez toujours utiliser des structures et résoudre (1), en fixant une finalité (ordre des octets du réseau) et vous assurer que vos entiers sont stockés tels quels dans ces structures, et (2) en réparant un compilateur et en utilisant les pragmas. ou des atsortingbuts pour appliquer une structure “compactée”.

Gcc, par exemple, utilise l’ atsortingbut (( compacté )) en tant que tel:

 struct mystruct { uint32_t a; uint16_t b; unsigned char text[24]; } __atsortingbute__((__packed__)); 

(3) n’est pas facile à résoudre. L’utilisation de chaînes à terminaison nulle sur un protocole réseau et en fonction de leur présence rendrait votre code vulnérable à plusieurs attaques. Si des chaînes doivent être impliquées, j’utiliserais une méthode d’encodage appropriée telle que celles suggérées ci-dessus.

Le moyen le plus simple serait d’écrire deux fonctions pour chaque structure: une pour convertir une représentation textuelle en structure et l’autre pour convertir une structure en texte. Ensuite, il vous suffit d’envoyer le texte sur le réseau et de le convertir du côté destinataire à vos structures. De cette façon, l’endianité n’a pas d’importance.

Il existe des fonctions de conversion pour assurer la portabilité des entiers binarys sur un réseau. Utilisez htons, htonl, ntohs et ntohl pour convertir les entiers 16 et 32 ​​bits d’un hôte à l’ordre des octets du réseau et inversement.