IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Réseau C Discussion :

Ecrire un double sur une socket


Sujet :

Réseau C

  1. #1
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juillet 2014
    Messages
    14
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Juillet 2014
    Messages : 14
    Points : 17
    Points
    17
    Par défaut Ecrire un double sur une socket
    Salut,

    j'suis actuellement sur un projet de client/serveur en C/C++ et je dois donc communiquer des informations entre mon serveur et mon client.

    Pour se faire je n'ai pas voulu envoyer des commandes textes au serveur car je trouve pas ça très agréable à manipuler. Je préfère directement inscrire les données comme elles sont. Je tiens à préciser que j'écrit ces données en big endian.
    Pour faire ça j'ai donc fait deux classe, DataInput et DataOutput. Leur structure est assez simple, j'ai pour chaque type une méthode qui écrit dans un buffer la donnée, et une méthode qui lit cette donnée depuis le buffer.
    Par exemple :

    pour écrire un int :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    void                                    DataOutput::writeInt(int i)
    {
      _buf += (i >> 24);
      _buf += (i >> 16) & 0xff;
      _buf += (i >> 8) & 0xff;
      _buf += i & 0xff;
    }
    pour lire un int :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    int                             DataInput::readInt()
    {
      int                           ret;
     
      ret = _buf[_pos] << 24;
      ret |= ((_buf[_pos + 1] & 0xff) << 16);
      ret |= ((_buf[_pos + 2] & 0xff) << 8);
      ret |= _buf[_pos + 3] & 0xff;
      _pos += 4;
      return (ret);
    }
    Tout ça fonctionne à merveille et je pense d'ailleurs que c'est un procédé classique.
    Mais je me heurte à un problème. Je ne sais pas comment écrire un double ou encore un float. Est ce que vous auriez une idée de comment faire ceci ?
    Merci beaucoup !

  2. #2
    Membre éclairé
    Inscrit en
    Décembre 2010
    Messages
    290
    Détails du profil
    Informations forums :
    Inscription : Décembre 2010
    Messages : 290
    Points : 719
    Points
    719
    Par défaut
    En effet les opérateurs bit à bit ne fonctionnent pas sur les types float & double, du coup écrire une fonction de swap c'est pas forcément le plus simple.
    Y a longtemps, dans le source de Quake (https://github.com/id-Software/Quake...Quake/common.c), j'étais tombé là dessus :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     
    float FloatSwap (float f)
    {
    	union
    	{
    		float   f;
    		byte    b[4];
    	} dat1, dat2;
     
     
    	dat1.f = f;
    	dat2.b[0] = dat1.b[3];
    	dat2.b[1] = dat1.b[2];
    	dat2.b[2] = dat1.b[1];
    	dat2.b[3] = dat1.b[0];
    	return dat2.f;
    }
    Tu dois pouvoir adapter ce code pour les doubles sans trop de problèmes, mais je n'ai jamais essayé.

    Un peu hors-sujet, le même code contient une petite perle pour détecter de façon portable si la machine sur laquelle le code s'exécute fonctionne en big-endian ou en little-endian:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    	byte    swaptest[2] = {1,0};
     
    // set the byte swapping variables in a portable manner 
    	if ( *(short *)swaptest == 1)
    	{
    		bigendien = false;
    	}
    	else
    	{
    		bigendien = true;
    	}

  3. #3
    Membre éprouvé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2014
    Messages
    345
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Finance

    Informations forums :
    Inscription : Juin 2014
    Messages : 345
    Points : 1 211
    Points
    1 211
    Par défaut
    Je suppose que _buf est un char* ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    // Ecriture
    double d = /* ... */;
    *(double*)_buf = d;
    Mais ça ne fonctionnera bien sûr que si les deux machines ont la même endianness et la même implémentation de double.

    Au passage, je ne comprends pas le premier extrait de code que tu postes ... Le _buf déclaré est le même que dans le deuxième extrait ? Pourquoi "+=" ?

  4. #4
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juillet 2014
    Messages
    14
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Juillet 2014
    Messages : 14
    Points : 17
    Points
    17
    Par défaut
    Oui j'ai pas été super clair sur mes bouts de code.

    Dans le premier, qui correspond à DataOutput, _buf est une string c'est pour ça que je peux faire += (et sur ma socket j'écrit _buf.c_str()). Manipuler une string est plus simple qu'un char * c'est pour ça que j'fais ça.
    Dans le second c'est bien un char *, celui que je reçoit du serveur du coup !

    Je vais essayer vos propositions, je reviens pour vous dire ce qu'il en est.

    Merci en tous cas !

  5. #5
    Membre éprouvé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2014
    Messages
    345
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Finance

    Informations forums :
    Inscription : Juin 2014
    Messages : 345
    Points : 1 211
    Points
    1 211
    Par défaut
    D'accord. Je précise que le code que je t'ai fourni comme exemple considère que _buf est un char*

  6. #6
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juillet 2014
    Messages
    14
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Juillet 2014
    Messages : 14
    Points : 17
    Points
    17
    Par défaut
    Je viens de faire le test qui n'est pas concluant, mais peut être que j'my prend mal ? Voici ce que j'ai fait :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    void                                    DataOutput::writeDouble(double d)
    {
      // A la base je manipule une string donc je doit la repasser en char* avec ces deux lignes
      char                                  *buftmp = new char [_buf.length() + 1];
      std::strcpy(buftmp, _buf.c_str());
     
      // j'applique la méthode de the Hund
      *(double *)buftmp = d;
     
      // je remet mon char* dans ma string.
      _buf = buftmp;
    }
    Je me fait un petit main de test :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     
    int main()
    {
      DataOutput o;
     
      // j'écrit une chaine en 1er pour ensuite essayer de la lire
      o.writeUTF("salut");
      // j'écrit mon double
      o.writeDouble(3.14);
     
      // je passe mon buffer à ma classe input
      DataInput     in(o.getBuf());
     
      // j'essaye de lire la chaine
      std::cout << in.readUTF() << std::endl;
    }
    La sortie attendu est "salut" mais j'obtient que des caractères bizzares . Est ce que c'est parce que j'utilise une string à la base, que je transforme en char* puis repasse en string ?
    Sinon pour la lecture comment vais-je devoir faire ? Mon systeme actuel me permet d'appeler mes fonctions de read et grâce à une variable dans ma classe je sauvegarde ma position ce qui fait que plus j'appelle de méthode pour lire et plus j'avance dans mon char*.

    Merci !

  7. #7
    Membre éprouvé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2014
    Messages
    345
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Finance

    Informations forums :
    Inscription : Juin 2014
    Messages : 345
    Points : 1 211
    Points
    1 211
    Par défaut
    Alors plusieurs remarques :

    - new char[_buf.length() + 1] : length() renvoie la taille du buffer, qui non seulement peut être inférieure à la taille d'un double (si par exemple la chaîne _buf est vide) mais en plus n'a rien à voir avec le buftmp que tu déclares. En effet il te faut un buffer uniquement pour stocker ton double, donc la taille de ce buffer sera sizeof(double). Ensuite, le "+ 1" est utilisé pour les chaînes de caractères qui sont considérées en tant que telles (pour mettre le '\0' à la fin), or ton buftmp sert de buffer et rien d'autre (on s'en fout du '\0').
    Ce qui amène à la chose suivante : std::string est une classe qui encapsule la "chaîne de caractère", et lorsque tu cast un char* en std::string, ce char* doit sémantiquement être une "chaîne de caractère" et doit donc contenir le '\0' de fin (que tu ne mets pas, au passage).
    Or, encore une fois, buftmp est sémantiquement un buffer et non une string ; il n'est donc pas sémantiquement valide de rajouter un buffer à une string. Donc je ne pense pas qu'un buffer implémenté sous forme de string soit une bonne idée.

    - Les caractères bizarres qui s'affichent, c'est normal, c'est ton double qui est affiché comme une chaîne. Cela n'a pas grand chose à voir avec le fait que tu aies fait des casts char* -> std::string ou l'inverse ; c'est simplement que ton double est traité en tant que string et donc chacun des octets qui le constituent est affiché comme un caractère. Maintenant si le "salut" ne s'affiche pas, c'est à cause d'une erreur triviale : tu fais un _buf = buftmp, qui écrase le contenu de la string, au lieu d'un _buf += buftmp.

    - Pour ce qui est de la lecture, je t'invite à te renseigner sur les buffers circulaires. Je ne sais pas exactement dans quel type de projet tu comptes utiliser ces classes, mais elles ont l'air d'être faites pour l'entrée/sortie de manière générale ; tu peux trouver sur le net différentes implémentations de buffers d'entrée/sortie, dont les buffers circulaires font partie.

    En espérant t'avoir aidé.

  8. #8
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juillet 2014
    Messages
    14
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Juillet 2014
    Messages : 14
    Points : 17
    Points
    17
    Par défaut
    Ok merci,
    j'ai changer un peu et ça a l'air de fonctionner. Après je sais toujours pas faire la lecture.

    Pour le buffer circulaire j'sais à peut près ce que c'est mais ça me semble compliqué comme système à mettre en place. Est ce que vous avez un exemple simple à me montrer ?
    Merci bcp en tout cas !

    PS/ Pour la lecture du double je peux faire comment du coup ?

  9. #9
    Membre éprouvé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2014
    Messages
    345
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Finance

    Informations forums :
    Inscription : Juin 2014
    Messages : 345
    Points : 1 211
    Points
    1 211
    Par défaut
    La page Wikipedia "Circular buffer" a l'air plutôt verbeuse sur le sujet (lu en diagonale, donc à vérifier).

    Si j'étais toi, je dissocierais les classes d'entrée/sortie (DataInput et DataOutput) de la gestion du buffer, et affecterais celle-ci à une classe dédiée. De cette manière tu peux garder une implémentation simple, sans buffer circulaire, puis changer si besoin est (et conceptuellement c'est plus propre aussi).

    Pour la lecture du double c'est (presque) la même chose :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    double d;
    d = *(double*)_buf; // _buf est de type char*
    et là tu vas me dire, mais si je veux lire une string puis un double, comment je fais pour différencier la string, de taille variable, et le double, de taille fixe, alors qu'ils sont dans le même buffer ?
    C'est là que le problème de sémantique revient : ton buffer sert à la fois de stockage de texte (string) et de binaire (double).
    Soit tu passes tout en binaire, et au lieu d'envoyer des chaînes de caractères tu vas envoyer du binaire. Tu affectes à chacune des commandes du serveur un code (genre un entier) que tu envoies à la place d'une string représentant la commande (genre au lieu d'envoyer "GET", tu envoies 01, sachant que 01 correspond à la commande GET). L'avantage c'est que tu envoies des paquets de données de taille fixe, donc tu sais exactement combien d'octets tu vas lire. IP et ARP par exemple fonctionnent comme ça.
    Soit tu passes tout en texte, comme les protocoles styles HTTP et autres. L'avantage c'est que tu peux directement communiquer avec le serveur (avec un telnet ou une connerie du genre), ça facilite le débogage. L'inconvénient c'est le parsing.
    Soit tu fais un mix des deux, genre avant d'envoyer une string tu envoies un int qui contient la taille de la string.
    Ou alors tu peux envoyer une string, suivie d'un caractère de séparation (un '\n' par exemple) puis les données binaires, mais là ça devient tellement crade que j'ai envie de m'auto-flageller pour avoir pensé à ça.

    Bref, vu que tu veux éviter les protocoles à base de texte, je te conseillerais de te pencher sur la première option, même si encore une fois, tout dépend de ce pour quoi les classes seront utilisées.

  10. #10
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juillet 2014
    Messages
    14
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Juillet 2014
    Messages : 14
    Points : 17
    Points
    17
    Par défaut
    En fait j'ai pas le choix du protocol . Je dois pouvoir envoyé une socket avec dessus une string + un double + un int + une string + etc .... Du côté client j'ai un sérialiseur pour chaque type de paquet et côté serveur, le désérialiseur qui sait comment lire ce paquet.

    Pour ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    double d;
    d = *(double*)_buf; // _buf est de type char*
    Si je fait un buf temporaire de la taille sizeof(double) et que je copie la partie intéréssante de mon buf original, je peux appliquer ta méthode ?
    Je vais quand même regarder pour faire une classe buffer en dehors de DataIn/Output, mais je vois pas, pour l'instant, l'interet .

    Merci pour le temps que tu m'accorde !

  11. #11
    Membre éprouvé
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2014
    Messages
    345
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Finance

    Informations forums :
    Inscription : Juin 2014
    Messages : 345
    Points : 1 211
    Points
    1 211
    Par défaut
    Moi ce que j'en comprends, c'est que tu dois pouvoir envoyer des données de tel type, mais qu'on se fiche de savoir comment elles sont envoyées, tant qu'elles sont bien reçues c'est ça ? Dans ce cas c'est toi qui décide du protocole (tu décides de comment sont formés les paquets, quelles infos complémentaires tu envoies etc).
    Pour faire simple dans un premier temps je te conseillerais donc la troisième option (où tu write la taille de la string avant de write la string), qui est la plus légère à mes yeux.

    Oui, tu peux copier ton buffer original vers un buffer temporaire, m'enfin si ton buffer original est un char*, tu peux aussi faire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    double d;
    d = *(double*)(_buf + i); // i est la position de lecture courante
    mais il faut faire gaffe avec ça, avec un buffer circulaire ça peut planter dans le cas où tu sors de la zone de mémoire allouée à _buf.

    L'intérêt d'une classe buffer, c'est que non seulement tu respectes un peu plus les principes de la POO (par exemple ta classe DataInput doit conceptuellement lire des données et rien d'autre, elle n'est pas censée s'"occuper" du buffer), mais en plus tu t'offres un niveau de flexibilité supplémentaire : non seulement maintenir la classe buffer sera plus simple, mais en plus tu pourras la réutiliser pour d'autres classes, utiliser une autre classe buffer dans ton DataInput, etc.
    Ce qu'il faut bien voir ici, c'est que DataInput/Output ne sont pas censées "savoir" comment le buffer fonctionne (l'implémentation), elle doivent juste "savoir" qu'on peut faire telle ou telle chose avec (on peut read/write de N octets dessus, on peut avancer le curseur de X octets, etc.) (l'interface). Tout comme tu te fiches de la manière dont la poste délivre ton courrier, les classes DataInput/Output se fichent de la manière dont les données sont "bufferisées".

  12. #12
    Membre à l'essai
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juillet 2014
    Messages
    14
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Juillet 2014
    Messages : 14
    Points : 17
    Points
    17
    Par défaut
    Ok j'vais tenter de faire un buffer circulaire mais pour le protocol en gros j'ai un serveur de test et je dois le reproduire. Par ex pour le paquet de "connexion" il est de la forme :
    ID paquet (int) + login (string) + mdp (string) + ID channel (int)

    Du coup tu vois j'suis obligé de faire ce protocol ^^. Et ouai pour les string je note la taille suivit de la chaine.

    Merci de ton aide en tout cas !

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Ecrire du binaire sur une Socket
    Par _LittleFlea_ dans le forum Entrée/Sortie
    Réponses: 4
    Dernier message: 04/03/2011, 11h39
  2. Ecrire du texte sur une fenêtre de Jeu vidéo (OSD)
    Par zenway dans le forum DirectX
    Réponses: 7
    Dernier message: 07/03/2009, 14h06
  3. Ecrire du texte sur une Overlay Surface
    Par deakuk dans le forum DirectX
    Réponses: 1
    Dernier message: 17/02/2006, 13h52
  4. [visual c++] connaitre le debit sur une socket
    Par khayyam90 dans le forum MFC
    Réponses: 4
    Dernier message: 25/10/2005, 16h12
  5. [VMR9][D3D9]ecrire un texte sur une surface
    Par drizztfr dans le forum DirectX
    Réponses: 2
    Dernier message: 13/11/2003, 15h06

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo