transfert en UDP

transfert en UDP - C - Programmation

Marsh Posté le 16-04-2010 à 15:10:10    

Salut :)
 
Ce n'est pas vraiment une question C à proprement parler, mais je pense que c'est quand même la meilleure catégorie pour en parler. Si jamais la catégorie n'est pas bonne, pouvez vous m'indiquer la plus appropriée ?
 
 
Voici mon problème :
 
J'ai un système multi-client / multi-serveur qui tourne depuis un moment et sur lequel je veux apporter une modification notamment au niveau des transferts UDP.
 
Chaque serveur envoie sur l'adresse broadcast du réseau un ordre UDP (sur port N) toutes les secondes afin d'indiquer leur présence, ainsi que certains de leurs paramètres (qui peuvent varier. Ex : point de montage, taille restante ou signature MD5 de données sur celui-ci).
Chaque client envoie également sur l'adresse broadcast un ordre UDP (sur port N+1) indiquant également leur présence aux serveurs. Ceci toutes les secondes également.
 
Ainsi, chaque serveur ecoute sur N+1, et chaque client sur N, et ils travaillent en fonction de ce qu'ils recoivent.
 
les données envoyées par le client et par le serveur n'excéderont jamais 1000 octets.
 
Maintenant, étant en train d'écrire les algos pour recevoir les données, je suis pris d'un affreux doute :
 
 
 - Je sais qu'UDP ne garantit par l'ordre des paquets, mais aucun problème car les données sont intègres en un seul paquet, et il n'y a pas de notion temporelle dans ce que je recois. Ainsi, je peux recevoir les paquets de chaque serveur dans le désordre, ce qui ne me dérangerait pas (si c'est ponctuel).
 
 - Par contre, je n'ai pas pensé à la fragmentation. En effet, le réseau peut me fragmenter une trame UDP, mais est-ce que le réseau me garantit que le réassemblage sera fait avant que je recoive mon paquet ? En d'autres terme, si j'envoie un paquet dont je garantis la taille inférieure à la taille max d'un paquet, il arrivera dans le bon ordre ?
 
Par exemple : j'envoie 800 octets avec S1 , et 900 octets avec S2. Est ce que je suis garanti de recevoir S1 puis S2 entier, ou S2 puis S1 entier ou puis je recevoir un truc incohérent du style : 400/900 S2 , 800 S1 , 500/900 S2  (si par exemple sur le réseau, j'ai un équipement qui me fragmente les trames de S2 ) ?
 
 - Maintenant, si j'étends la longueur de mes données à envoyer au delà de 1000 octets, ou plus simplement au delà de la taille d'un paquet UDP, je suppose que mes datas seront fragmentés en plusieurs paquets, mais seront ils réassemblés dans l'ordre à la réception ou pas du tout ?
 
Par exemple:  Depuis mon serveur , je fais un SendTo(data) , avec data = 65536 octets , est ce que le RecvFrom(data) du client va me récupérer data dans l'ordre, ou dans le désordre, ou pire ! mélangé avec les datas d'un autre serveur  (car on travaille sur une adresse de broadcast).
 
Si c'est le cas, est ce que quelqu'un peut me confirmer que UDP n'est pas adapté pour envoyer des paquets trop gros sans mécanisme de réassemblage.
 
 
Merci pour vos réponses  :jap:

Reply

Marsh Posté le 16-04-2010 à 15:10:10   

Reply

Marsh Posté le 16-04-2010 à 15:22:57    

Mouais, j'ai aussi fait joujou avec ce genre de trucs, et avec UDP il n'y aura aucune garantie pour que ton paquet arrive en entier et dans l'ordre si jamais la taille dépasse le MTU du lien physique. Sur de l'ethernet à 100Mbit/s le MTU tourne autour de 1500 octets, sur du Gigabit c'est un peu plus.
 
Avec des paquets plus grand que ça, attends toi à une fragmentation et surtout une récupération dans le désordre quasi certaine.

Reply

Marsh Posté le 19-04-2010 à 09:27:53    

Salut :)
 
Merci de ta réponse.
 
Est-ce que l'on peut dire à l'inverse que si on garantit qu'on aura un paquet à envoyer qui sera toujours inférieur à la taille de la MTU physique ne sera jamais fragmenté ?
 
Merci  :jap:

Reply

Marsh Posté le 19-04-2010 à 10:37:33    

Quand on utilise UDP, il ne faut pas penser que le "paquet" du recvfrom sera de la même longeur que le "paquet" du sendto. Par contre, il y a deux choses que je n'ai jamais vu avec mes programmes UDP :
 
- des inversion de "paquets" (mais peut-être que cela existe dans certains cas particuliers)
- des paquets de moins de 4 octets, sauf en fin de message.
 
En s'appuyant sur ces deux choses, on peut créer un système assez fiable qui consiste à envoyer en premier la longueur du message, puis le message ; et à la réception, à lire la longueur, puis à boucler jusqu'à l'arrivée du message en entier ; avec un timeout, et des réessais eventuels.

Reply

Marsh Posté le 19-04-2010 à 10:44:06    

olivthill a écrit :

Quand on utilise UDP, il ne faut pas penser que le "paquet" du recvfrom sera de la même longeur que le "paquet" du sendto. Par contre, il y a deux choses que je n'ai jamais vu avec mes programmes UDP :
 
- des inversion de "paquets" (mais peut-être que cela existe dans certains cas particuliers)
- des paquets de moins de 4 octets, sauf en fin de message.
 
En s'appuyant sur ces deux choses, on peut créer un système assez fiable qui consiste à envoyer en premier la longueur du message, puis le message ; et à la réception, à lire la longueur, puis à boucler jusqu'à l'arrivée du message en entier ; avec un timeout, et des réessais eventuels.


 
 
Salut :)
 
Merci de ta réponse, c'est exactement ce que je fais, mais j'ai l'impression que c'est insuffisant.
 
Je souhaite envoyer en broadcast UDP des données XML (assez courtes).
 
J'envoie donc des paquets de la forme suivante :
 
MAGIC NUMBER / LONGUEUR / CHECKSUM / DATA
 
MAGIC NUMBER est un entier sur 32 bits arbitraire
LONGUEUR est la longueur de mes datas
CHECKSUM est le checksum de mes datas (CRC32).
DATA correspond à mon buffer xml (ce n'est donc que du texte)
 
Mes 3 premiers entiers sont endianless , car il est possible que les 2 machines qui dialoguent soient différentes de ce coté.
 
 
Mon algo est assez simple :  
 
je lis d'abord 12 octets. Si succès, je lis ensuite LONGUEUR data, et si succès, je teste le checksum. Si tout est ok, je retourne ma chaine à mon parser.
 
Mais j'ai l'impression que ce mécanisme est insuffisant, je me demande s'il ne faut pas que je gère également un numéro de séquenceur (départ, milieu, fin), et que j'envoie des paquets de taille fixe (sauf pour la fin, ou pour départ s'il est inférieur à la taille max du paquet).
 

Reply

Marsh Posté le 20-04-2010 à 09:40:30    

Salut,
 
Bon, apparemment, je n'ai pas du bien comprendre comment fonctionne UDP, je rencontre quelques soucis pour transférer des datas via ce protocole.
 
 
Je souhaite envoyer des paquets XML en broadcast sur le réseau, et ceux-ci sont de taille variable (meme si j'arrive a garantir qu'ils sont plus petit qu'une trame).
 
Je procède donc de la façon suivante  :
 

Code :
  1. int         data = 0;
  2. int   recu = 0;
  3. fd_set      fdin;
  4. timeval     tv;
  5. char         entete[12];
  6. FD_ZERO(&fdin);
  7. FD_SET(m_socket, &fdin);
  8. tv.tv_sec = 1;
  9. tv.tv_usec = 0;
  10. data = select(((int)m_socket)+1, &fdin, NULL, NULL, &tv);
  11. if (data == -1)
  12. {
  13. PAP_ERROR("Erreur select : %s",strerror_socket(errno));
  14. return -1;
  15. }
  16. else if (FD_ISSET(m_socket, &fdin) != 0)
  17. {
  18. socklen_t   nSaiLen = sizeof(struct sockaddr_in);
  19. recu = recvfrom(m_socket, entete, 12, 0, (struct sockaddr*)emetteur, &nSaiLen);
  20. BYTE *pointeur = (BYTE *)entete;
  21. ULONG magic = GET_RAW_DWORD(pointeur);
  22. ULONG len = GET_RAW_DWORD(pointeur);
  23. ULONG chk = GET_RAW_DWORD(pointeur);
  24. if (recu == 12)
  25. {
  26.  PAP_TRACE("recu = 12" );
  27.  if (magic == MAGIC_NUMBER)
  28.  {
  29.   PAP_TRACE("Magic Number OK" );
  30.   if (buffer == NULL)
  31.   {
  32.    buffer = malloc(len + 1);
  33.    recu = recvfrom(m_socket, buffer, len, 0, (struct sockaddr*)emetteur, &nSaiLen);
  34.    PAP_TRACE("retour 2eme recvfrom %d",recu);
  35.    buffer[len] = 0;
  36.    if (recu == len)
  37.    {
  38.     if (chk == getStringCRC(buffer+12,len) )
  39.     {
  40.      return len;
  41.     }
  42.    }
  43.    else if (recu < len && recu > 0)
  44.    {
  45.     free (buffer);
  46.     buffer = NULL;
  47.     PAP_ERROR("Lecture data insuffisante %d",recu);
  48.     return -1;
  49.    }
  50.    else if (recu == 0)
  51.    {
  52.     // lu 0 octets, erreur réseau
  53.     free( buffer);
  54.     buffer = NULL;
  55.     PAP_ERROR("Lu data 0 octets. errno =  : %s ??",strerror_socket(errno));
  56.     return -1;
  57.    }
  58.    else
  59.    {
  60.     free( buffer);
  61.     buffer = NULL;
  62.     PAP_ERROR("Erreur de lecture data. errno =  : %s ??",strerror_socket(errno));
  63.     return -1;
  64.    }
  65.   }
  66.   else
  67.   {
  68.    PAP_ERROR("Buffer non NULL, ne peut pas allouer" );
  69.   }
  70.  }
  71. }
  72. else if (recu <12 && recu > 0 )
  73. {
  74.  PAP_ERROR("Lecture insuffisante %d",recu);
  75.  return -1;
  76. }
  77. else if (recu == 0)
  78. {                   
  79.  // lu 0 octets, erreur réseau  
  80.  PAP_ERROR("Lu 0 octets. errno =  : %s ??",strerror_socket(errno));
  81.  return -1;
  82. }
  83. else // recu < 0
  84. {
  85.  // erreur de lecture
  86.  PAP_ERROR("Erreur de lecture. errno =  : %s ??",strerror_socket(errno));
  87.  return -1;
  88. }
  89. }
  90. else
  91. {
  92.        // aucune donnée recue dans les 1000 ms.
  93. #if (PAP_NIVEAU_TRACE>=3)
  94. PAP_TRACE("Aucune donnée reçue." );
  95. #endif
  96. return 0;
  97. }


 
 
Mon emetteur envoie 160 octets : entete de 12 octets + 148 datas.
 
Si je fais comme ci dessus : select + lecture 12 octets + lecture du restant, le 2eme recvfrom me retourne une erreur : ressource temporary unavailable.
 
Si je mets un select entre les 2, le 2eme select ne voit pas de data , je ne peux donc pas lire les 148 restants.
 
Pire que ca, ma fonction étant rappelée plus tard, elle relit 12 octets, et trouve bien l'entete de ma trame suivante. Cela signifie que mes 148 octets sont tout simplement perdus.
 
Si par contre je lis 160 octets d'un coup (select + recvfrom de 160 octets), pas de pb, je récupère la totalité.
 
Cela signifierait donc que UDP ne permet pas de lire une trame (un DATAGRAM) en plusieurs fois ? Dans ce cas, très difficile d'utiliser UDP avec des paquets de taille variable , et je comprends pourquoi dans la doc il est écrit qu'UDP est plus adapté à un protocole avec des paquets de taille fixe.
 
Quelqu'un peut me confirmer ? Ou m'éclairer sur des points que je n'aurais pas compris ?
 
merci :)

Reply

Marsh Posté le 20-04-2010 à 11:12:39    

Mon programme gère la reception de fichiers PDF.  
Le flux contient un entête "File:%d:%s", puis les octets du fichier PDF.
Voici mon programme pour infos.

Code :
  1. static int udp_main(void)
  2. {
  3.    fd_set fds; struct timeval tv;
  4.    char recv_buf[RECV_BUF_LEN_MAX + 1]; int recv_nb;
  5.    FD_ZERO(&fds);
  6.    FD_SET(recv_socket, &fds);
  7.    tv.tv_sec = 1;
  8.    tv.tv_usec = 0;
  9.    if (select(1, &fds, NULL, NULL, &tv) <= 0)
  10.       return -1;
  11.   //-----------------------------------------------
  12.   // Call the recvfrom function to receive datagrams
  13.   // on the bound socket.
  14.   // printf("Receiving datagrams...\n" );
  15.   send_addr_lg = sizeof(send_addr);
  16.   recv_nb = recvfrom(recv_socket,
  17.                      recv_buf, RECV_BUF_LEN_MAX,
  18.                      0,
  19.                      (SOCKADDR *)&send_addr,
  20.                      &send_addr_lg);
  21.   if (recv_nb >= 0 && recv_nb < RECV_BUF_LEN_MAX) {
  22.         // Get line containing "File:%d:%s", file_lg, filename
  23.         recv_nb = recvfrom(recv_socket,
  24.                            recv_buf, RECV_BUF_LEN_MAX,
  25.                            0,
  26.                            (SOCKADDR *)&send_addr,
  27.                            &send_addr_lg);
  28.         if (recv_nb >= 0 && recv_nb < RECV_BUF_LEN_MAX) {
  29.            recv_buf[recv_nb] = '\0';
  30.            // ErrorMessage("%s", recv_buf);
  31.            if (strncmp(recv_buf, "File:", 5) != 0) {
  32.               return(-2);
  33.            }
  34.            // Get number of characters
  35.            file_lg = 0;
  36.            for (i = 5; i < recv_nb;) {
  37.               c = recv_buf[i++];
  38.               if (c < '0' || c > '9') break;
  39.               file_lg = file_lg * 10 + (int)(c) - '0';
  40.            }
  41.            // ErrorMessage("file_lg=%d", file_lg);
  42.            // Get file's name
  43.            j = 0; k = 0;
  44.            for (; i < recv_nb;) {
  45.               c = recv_buf[i++];
  46.               if (c < ' ') break;
  47.               if (c == '/' || c == '\\') {
  48.                  strcpy(pdf_outfilename, "C:\\WINDOWS\\Temp" );
  49.                  k = strlen(pdf_outfilename);
  50.                  c = '\\';
  51.               }
  52.               pdf_infilename[j++] = c;
  53.               pdf_outfilename[k++] = c;
  54.            }
  55.            pdf_infilename[j] = '\0';
  56.            pdf_outfilename[k] = '\0';
  57.            // ErrorMessage("file=%s", pdf_infilename);
  58.            // ErrorMessage("file=%s", pdf_outfilename);
  59.            // Get content of the file
  60.            file_buf = (char *)malloc(file_lg * sizeof(char) + 1);
  61.            if (file_buf == NULL) {
  62.               return(-9);
  63.            }
  64.            fp = fopen(pdf_outfilename, "wb" );
  65.            for (i = 0; i < file_lg;) {
  66.               recv_nb = recvfrom(recv_socket,
  67.                                  file_buf, file_lg,
  68.                                  0,
  69.                                  (SOCKADDR *)&send_addr,
  70.                                  &send_addr_lg);
  71.               if (recv_nb >= 0 && recv_nb <= file_lg) {
  72.                  fwrite(file_buf, recv_nb, 1, fp);
  73.                  i += recv_nb;
  74.               }
  75.            }
  76.            fclose(fp);
  77.            free(file_buf);
  78.      }
  79.   }

Reply

Marsh Posté le 20-05-2011 à 15:27:22    

J'ai trouvé la réponse à une de mes questions :
 

Citation :


 - Je sais qu'UDP ne garantit par l'ordre des paquets, mais aucun problème car les données sont intègres en un seul paquet, et il n'y a pas de notion temporelle dans ce que je recois. Ainsi, je peux recevoir les paquets de chaque serveur dans le désordre, ce qui ne me dérangerait pas (si c'est ponctuel).
 
 - Par contre, je n'ai pas pensé à la fragmentation. En effet, le réseau peut me fragmenter une trame UDP, mais est-ce que le réseau me garantit que le réassemblage sera fait avant que je recoive mon paquet ? En d'autres terme, si j'envoie un paquet dont je garantis la taille inférieure à la taille max d'un paquet, il arrivera dans le bon ordre ?
 
Par exemple : j'envoie 800 octets avec S1 , et 900 octets avec S2. Est ce que je suis garanti de recevoir S1 puis S2 entier, ou S2 puis S1 entier ou puis je recevoir un truc incohérent du style : 400/900 S2 , 800 S1 , 500/900 S2  (si par exemple sur le réseau, j'ai un équipement qui me fragmente les trames de S2 ) ?
 
 - Maintenant, si j'étends la longueur de mes données à envoyer au delà de 1000 octets, ou plus simplement au delà de la taille d'un paquet UDP, je suppose que mes datas seront fragmentés en plusieurs paquets, mais seront ils réassemblés dans l'ordre à la réception ou pas du tout ?
 
Par exemple:  Depuis mon serveur , je fais un SendTo(data) , avec data = 65536 octets , est ce que le RecvFrom(data) du client va me récupérer data dans l'ordre, ou dans le désordre, ou pire ! mélangé avec les datas d'un autre serveur  (car on travaille sur une adresse de broadcast).
 
Si c'est le cas, est ce que quelqu'un peut me confirmer que UDP n'est pas adapté pour envoyer des paquets trop gros sans mécanisme de réassemblage.  


 
->  http://tangentsoft.net/wskfaq/general.html
 

Citation :


The stack will fragment a UDP datagram when it’s larger than the network’s MTU. The remote peer’s stack will reassemble the complete datagram from the fragments before it delivers it to the receiving application. If a fragment is missing or corrupted, the whole datagram is thrown away. This makes large datagrams impractical: an 8 KB UDP datagram will be broken into 6 fragments when sent over Ethernet, for example, because it has a 1500 byte MTU. If any of those 6 fragments is lost or corrupted, the stack throws away the entire 8 KB datagram.


 
Conclusion : un datagramme UDP est fragmenté ( en fragment de taille max = MTU ) puis ré assemblé dans l'ordre, si tous les fragments sont bien reçus.

Reply

Marsh Posté le 20-05-2011 à 15:45:38    

Par expérience: on ne peut pas faire de lecture fragmentée d'un datagramme. Donc la solution de lire d'abord l'entête, puis le reste du message n'est possible qu'à une condition : utiliser l'option MSG_PEEK, qui ne retire pas les données du tampon de réception après lecture.
 
Je me pose des questions sur la performance du truc, parce que ça oblige à faire au moins deux lectures (une avec MSG_PEEK pour connaitre la taille du message à lire, et une autre pour sortir l'intégralité du message).
 
Il y a aussi l'option, avant la lecture, d'appeler ioctl avec l'option FIONREAD. Cela te retourne la taille du prochain datagramme dans le tampon.

Reply

Marsh Posté le 20-05-2011 à 16:22:18    

shaoyin a écrit :

Par expérience: on ne peut pas faire de lecture fragmentée d'un datagramme. Donc la solution de lire d'abord l'entête, puis le reste du message n'est possible qu'à une condition : utiliser l'option MSG_PEEK, qui ne retire pas les données du tampon de réception après lecture.
 
Je me pose des questions sur la performance du truc, parce que ça oblige à faire au moins deux lectures (une avec MSG_PEEK pour connaitre la taille du message à lire, et une autre pour sortir l'intégralité du message).
 
Il y a aussi l'option, avant la lecture, d'appeler ioctl avec l'option FIONREAD. Cela te retourne la taille du prochain datagramme dans le tampon.


 
 
Je te remercie de cette info, cela répond à ma 2ème interrogation. Comme tu as pu le remarquer, mon message initial date de plus d'un an , donc j'ai trouvé une solution intermédiaire, je postais juste  une des réponses que j'avais trouvé au cas où cela pouvait interesser quelqu'un.
 
Ma solution est : j'ai une taille variable mais elle est bornée. L'algo est donc :
 

Code :
  1. je lis MAX octets sur ma socket connectée en UDP
  2. SI taille supérieure > 12  ( sinon erreur à traiter )
  3.      j'extrais les données de l'entete
  4.      SI magic number ok
  5.          SI ( longueur contenue dans l entete + 12  == valeur de retour de lecture udp )
  6.                 je récupère les datas
  7.                 SI ( cheksum data de l entete == checksum data calculé sur les datas )
  8.                       je retourne succes avec les datas


 
cela fonctionne très bien et sans problème  :jap:

Reply

Marsh Posté le 20-05-2011 à 16:22:18   

Reply

Marsh Posté le 20-05-2011 à 16:42:25    

Heu... non, j'avais pas fait gaffe à la date ! ;)

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed