Que se passe-t-il avec les données auxiliaires de flux Unix sur les lectures partielles?

18

J'ai donc lu beaucoup d'informations sur les données auxiliaires d'Unix-stream, mais une chose qui manque dans toute la documentation est ce qui est censé se passer quand il y a une lecture partielle?

Supposons que je reçois les messages suivants dans un tampon de 24 octets

msg1 [20 byes]   (no ancillary data)
msg2 [7 bytes]   (2 file descriptors)
msg3 [7 bytes]   (1 file descriptor)
msg4 [10 bytes]  (no ancillary data)
msg5 [7 bytes]   (5 file descriptors)

Le premier appel à recvmsg, j'obtiens l'intégralité de msg1 (et une partie de msg2? Est-ce que le système d'exploitation le fera?) quand je sais ce que le message me disait réellement de faire avec les données? Si je libère les 20 octets de msg1 et que j'appelle à nouveau recvmsg, est-ce qu'il livrera jamais msg3 et msg4 en même temps? Les données auxiliaires de msg3 et msg4 sont-elles concaténées dans la structure du message de contrôle?

Bien que je puisse écrire des programmes de test pour le découvrir expérimentalement, je cherche de la documentation sur le comportement des données auxiliaires dans un contexte de streaming. Il semble étrange que je ne trouve rien de officiel là-dessus.


Je vais ajouter mes résultats expérimentaux ici, que j'ai obtenus de ce programme de test:

https://github.com/nrdvana/daemonproxy/blob/master/src/ancillary_test.c

Linux 3.2.59, 3.17.6

Il semble que Linux ajoute des parties de messages auxiliaires à la fin des autres messages tant qu'aucune charge utile auxiliaire préalable n'a dû être remise lors de cet appel à recvmsg. Une fois que les données auxiliaires d'un message sont livrées, il retournera une courte lecture plutôt que de démarrer le prochain message de données auxiliaires. Ainsi, dans l'exemple ci-dessus, les lectures que j'obtiens sont:

recv1: [24 bytes] (msg1 + partial msg2 with msg2's 2 file descriptors)
recv2: [10 bytes] (remainder of msg2 + msg3 with msg3's 1 file descriptor)
recv3: [17 bytes] (msg4 + msg5 with msg5's 5 file descriptors)
recv4: [0 bytes]

BSD 4.4, 10.0

BSD fournit plus d'alignement que Linux et donne une courte lecture immédiatement avant le début d'un message avec des données auxiliaires. Mais, il ajoutera volontiers un message non auxiliaire à la fin d'un message auxiliaire. Donc, pour BSD, il semble que si votre tampon est plus grand que le message auxiliaire, vous obtenez un comportement presque semblable à un paquet. Les lectures que je reçois sont:

recv1: [20 bytes] (msg1)
recv2: [7 bytes]  (msg2, with msg2's 2 file descriptors)
recv3: [17 bytes] (msg3, and msg4, with msg3's 1 file descriptor)
recv4: [7 bytes]  (msg5 with 5 file descriptors)
recv5: [0 bytes]

FAIRE:

J'aimerais toujours savoir comment cela se produit sur les anciens Linux, iOS, Solaris, etc., et comment cela pourrait se produire à l'avenir.

M Conrad
la source
Ne confondez pas les flux et les paquets, dans un flux, il n'y a aucune garantie que les données seront livrées dans les mêmes morceaux qu'ils ont été envoyés, pour cela, vous auriez besoin d'un protocole basé sur des paquets, pas sur un flux.
ctrl-alt-delor
c'est précisément pourquoi je pose cette question
M Conrad
L'ordre doit être conservé. C'est ce que font les streams. Si une lecture bloquante renvoie 0, alors c'est la fin du flux. S'il renvoie un autre numéro, il peut y en avoir plus, vous devez faire au moins une autre lecture pour le savoir. Il n'y a rien de tel que message1, message2 etc. Aucun délimiteur de message n'est transmis. Vous devez l'ajouter à votre protocole, si vous en avez besoin.
ctrl-alt-delor
1
Plus précisément, j'ai un protocole de flux de texte et j'ajoute une commande qui transmet un descripteur de fichier avec une ligne de texte. J'ai besoin de savoir dans quel ordre ces données auxiliaires sont reçues par rapport au texte du message afin d'écrire correctement le code.
M Conrad
1
@MConrad: J'essaierais d'obtenir une copie de la spécification POSIX.1g. S'il n'y est pas explicitement écrit, vous pouvez vous attendre à un comportement spécifique à l'implémentation.
Laszlo Valko du

Réponses:

1

Les données auxiliaires sont reçues comme si elles étaient mises en file d'attente avec le premier octet de données normal du segment (le cas échéant).

- POSIX.1-2017

Pour le reste de votre question, les choses deviennent un peu velues.

... Aux fins de la présente section, un datagramme est considéré comme un segment de données qui termine un enregistrement et qui inclut une adresse source en tant que type spécial de données auxiliaires.

Les segments de données sont placés dans la file d'attente lorsque les données sont livrées au socket par le protocole. Les segments de données normaux sont placés à la fin de la file d'attente lors de leur livraison. Si un nouveau segment contient le même type de données que le segment précédent et ne contient aucune donnée auxiliaire, et si le segment précédent ne termine pas un enregistrement, les segments sont logiquement fusionnés en un seul segment ...

Une opération de réception ne doit jamais retourner des données ou des données auxiliaires provenant de plus d'un segment.

Les sockets BSD modernes correspondent donc exactement à cet extrait. Ce n'est pas surprenant :-).

Rappelez-vous que la norme POSIX a été écrite après UNIX et après des divisions comme BSD vs System V. L'un des principaux objectifs était d'aider à comprendre la gamme de comportements existante et d'éviter encore plus de divisions dans les fonctionnalités existantes.

Linux a été implémenté sans référence au code BSD. Il semble se comporter différemment ici.

  1. Si je vous comprends bien, il semble que Linux est la fusion de plus « segments » lorsqu'un nouveau segment ne comprend des données auxiliaires, mais le segment précédent ne fonctionne pas.

  2. Votre argument selon lequel «Linux ajoutera des parties de messages auxiliaires à la fin des autres messages tant qu'aucune charge utile auxiliaire préalable n'a dû être livrée lors de cet appel à recvmsg», ne semble pas entièrement expliqué par la norme. Une explication possible impliquerait une condition de concurrence. Si vous lisez une partie d'un "segment", vous recevrez les données auxiliaires. Peut-être que Linux a interprété cela comme signifiant que le reste du segment ne comptait plus comme incluant des données auxiliaires! Ainsi, lorsqu'un nouveau segment est reçu, il est fusionné - soit selon la norme, soit selon la différence 1 ci-dessus.

Si vous voulez écrire un programme portable au maximum, vous devez éviter complètement cette zone. Lors de l'utilisation de données auxiliaires, il est beaucoup plus courant d'utiliser des sockets datagrammes . Si vous voulez travailler sur toutes les plates-formes étranges qui aspirent techniquement à fournir quelque chose comme POSIX, votre question semble s'aventurer dans un coin sombre et non testé.


On pourrait dire que Linux suit toujours plusieurs principes importants:

  1. "Les données auxiliaires sont reçues comme si elles étaient mises en file d'attente avec le premier octet de données normal du segment".
  2. Les données auxiliaires ne sont jamais "concaténées", comme vous dites.

Cependant, je ne suis pas convaincu que le comportement de Linux soit particulièrement utile lorsque vous le comparez au comportement BSD. Il semble que le programme que vous décrivez aurait besoin d'ajouter une solution de contournement spécifique à Linux. Et je ne connais pas la raison pour laquelle Linux s'attendrait à ce que vous fassiez cela.

Cela aurait pu paraître raisonnable lors de l'écriture du code du noyau Linux, mais sans jamais avoir été testé ou exercé par aucun programme.

Ou il pourrait être exercé par un code de programme qui fonctionne principalement sous ce sous-ensemble, mais pourrait en principe avoir des "bogues" ou des conditions de concurrence.

Si vous ne pouvez pas comprendre le comportement de Linux et son utilisation prévue, je pense que cela plaide pour le traiter comme un "coin sombre et non testé" sur Linux.

sourcejedi
la source
Merci pour l'examen approfondi! Je pense que le point à retenir ici est que je peux gérer cela en toute sécurité avec deux tampons (chacun avec une partie de données et une partie auxiliaire); Si je reçois des descripteurs de fichiers lors de la première lecture et qu'ils n'appartiennent pas au message, mais qu'un autre message commence, alors si la lecture suivante contient également des données auxiliaires, cela signifie que je trouverai certainement la fin de mon message de données possédant la première charge utile auxiliaire dans cette deuxième lecture. Alternant dans les deux sens, je devrais toujours être en mesure de faire correspondre le message avec la charge utile en fonction de l'emplacement du premier octet.
M Conrad