J'ai du mal à comprendre ce que recv()/recvfrom() renvoie d'un socket UDP non bloquant.

Un peu plus précis et comparé à TCP (corrigez-moi si je me trompe) :

  • Un socket bloquant (TCP ou UDP) ne reviendra pas d'un recv() tant qu'il n'y aura pas de données dans le tampon. Il peut s'agir d'un certain nombre d'octets (TCP) ou d'un datagramme complet (UDP).

  • Une socket TCP non bloquante renvoie soit EWOULDBLOCK (linux) / WSAEWOULDBLOCK (windows) soit les octets qui sont actuellement dans le tampon. Comme les données TCP sont un flux, le nombre d'octets renvoyés n'a pas d'importance.

Maintenant la question:

  • Un socket UDP non bloquant renvoie également WOULDBLOCK (linux) / WSAEWOULDBLOCK (windows) s'il n'y a pas de données disponibles. Mais s'il y a des données disponibles, est-ce qu'un socket UDP non bloquant ne renvoie que quelques octets, ce qui pourrait signifier que vous n'obtenez que la moitié d'un datagramme OU est-ce qu'un socket UDP renvoie toujours des datagrammes complets ??

Éditer:

Ce que je veux dire par "la moitié d'un datagramme", c'est : que se passe-t-il si j'appelle recv() juste au moment où la socket reçoit actuellement un datagramme. À ce moment-là, il y a quelques octets dans le tampon mais le datagramme n'est pas encore terminé.

Vos explications et commentaires sont appréciés. Merci!

10
Uwe 10 févr. 2010 à 23:09

3 réponses

Meilleure réponse

Enfin, une excuse pour sortir mes livres Stevens de mes vieilles boîtes de bureau.

À condition que le tampon soit suffisamment grand, les fonctions standard des sockets Berkeley recv() et recvfrom() ne retourneront jamais un datagramme partiel. Le datagramme n'est pas disponible pour l'application tant que le noyau n'a pas complètement reçu et réassemblé le datagramme.

Fait intéressant, et ce n'est pas vraiment (aucun ?) problème aujourd'hui, les autres interfaces de programmation réseau ne s'entendent pas sur le comportement lorsque le tampon fourni est trop petit :

La version traditionnelle de Berkeley de l'API des sockets tronque le datagramme, en supprimant toutes les données en excès. La notification de l'application dépend de la version. (4.3BSD Reno et versions ultérieures peuvent informer l'application que le datagramme a été tronqué.)

L'API des sockets sous SVR4 (y compris Solaris 2.x) ne tronque pas le datagramme. Toutes les données en excès sont renvoyées dans les lectures suivantes. L'application n'est pas informée que plusieurs lectures sont effectuées à partir d'un seul datagramme UDP.

L'API TLI ne supprime pas les données. Au lieu de cela, un indicateur est renvoyé indiquant que davantage de données sont disponibles, et les lectures suivantes par l'application renvoient le reste du datagramme.

(Stevens, TCP/IP illustré, volume 1, p. 160)

10
Community 20 juin 2020 à 12:12
Il semble qu'il soit possible de transmettre et de recevoir un indicateur MSG_TRUNC à recvmsg sous Linux. Documenté dans la page de manuel recv(2). Sur une autre note, peut-être que je lis mal, mais je ne peux trouver que le comportement de suppression documenté dans la page de manuel pour socket(2), qui ne le mentionne que pour les sockets SOCK_SEQPACKET. Je ne les ai jamais utilisés personnellement.
 – 
Stéphan Kochen
11 févr. 2010 à 12:10
MSG_TRUNC comme argument de recv(2) n'est pas standard. Il n'est disponible ni sur FreeBSD ni sur Mac OS X (les systèmes auxquels j'ai accès pour le moment ; probablement vrai pour les autres). MSG_TRUNC est disponible sur Linux, FreeBSD et Mac OS X dans le membre flags du struct msghdr passé à recvmsg(2). Dans tous les cas, le datagramme sera tronqué si le tampon passé n'est pas assez grand, même avec recv(2) sous Linux. L'appelant doit vérifier la valeur de retour et la comparer à la taille du tampon si MSG_TRUNC y est utilisé. Il saura que les données sont perdues, mais elles sont toujours perdues.
 – 
Steve Madsen
11 févr. 2010 à 19:07
Soyez prudent avec votre terminologie. UDP est orienté datagramme. Les paquets sont la "chose sur le fil" et chaque fois que vos datagrammes UDP sont plus gros que le MTU, ils seront fragmentés au niveau de la couche IP, ce qui entraînera plusieurs paquets IP. Le remontage a lieu au niveau du récepteur avant que quoi que ce soit ne soit disponible pour l'application.
 – 
Steve Madsen
11 févr. 2010 à 21:11
1
Oui vous avez raison. En résumé : - Si aucune donnée n'est disponible, recv() renvoie EWOULDBLOCK. - Étant donné un tampon trop petit pour un datagramme complet, le datagramme est tronqué et tout ce qui ne rentre pas dans le tampon est perdu. - Si le tampon est suffisamment grand, recv() renvoie exactement un datagramme.
 – 
Uwe
11 févr. 2010 à 22:46

Oui, UDP renvoie simplement les données transmises dans ce datagramme. UDP n'est pas orienté flux comme TCP. Les datagrammes sont des transmissions discrètes et ne sont en aucun cas liés à d'autres datagrammes. C'est la raison pour laquelle l'option socket pour TCP est SOCK_STREAM.

Le bon côté de ceci est que vous pouvez avoir une idée des transmissions séparées, ce qui n'est pas vraiment facile à faire avec TCP.

2
T.E.D. 10 févr. 2010 à 23:23
Merci à vous deux. Je connais la différence entre TCP orienté flux (SOCK_STREAM) et UDP orienté paquet (SOCK_DGRAM). Je n'étais tout simplement pas sûr qu'un UDP recv() non bloquant soit également orienté paquet. Pour citer la page de manuel recv() : ... Si aucun message n'est disponible sur la socket, les appels de réception attendent qu'un message arrive, à moins que la socket ne soit non bloquante (voir fcntl(2)), auquel cas la valeur -1 est renvoyé et la variable externe errno est définie sur EAGAIN ou EWOULDBLOCK. Les appels reçus renvoient normalement toutes les données disponibles, ...
 – 
Uwe
10 févr. 2010 à 23:38

Je crois que vous obtenez précisément un ou zéro datagrammes. Mais je ne peux pas sauvegarder cela pour le moment. Peut-être que quelqu'un d'autre pourrait fournir une bonne référence?

Edit : je suis presque sûr que vous ne pouvez pas recevoir la moitié d'un datagramme. Soit le datagramme est arrivé dans le tampon in, soit il ne l'est pas.

0
Hans W 11 févr. 2010 à 00:54
Je crois que c'est correct. Vous pouvez également obtenir une erreur si elle ne rentre pas dans le tampon que vous avez fourni, ou si vos mbufs ne sont pas assez gros (cela arrive parfois avec de gros datagrammes fragmentés).
 – 
T.E.D.
11 févr. 2010 à 01:03
Mbufs est une structure de données du noyau BSD. Ils ne sont pas exposés à l'espace utilisateur.
 – 
Steve Madsen
11 févr. 2010 à 19:08