Premier, un peu de contexte pour expliquer pourquoi je suis sur la route "UDP Sampling":
Je voudrais goûter des données produites à une vitesse rapide pour une période de temps inconnue. Les données que je veux échantillonner sont sur une autre machine que celle qui consommait les données. J'ai une connexion Ethernet dédiée entre les deux alors la bande passante n'est pas un problème. Le problème que j'ai est que la machine consommant les données est beaucoup plus lente que celle de la produisant. Une contrainte supplémentaire est que, bien que ce soit bien que je n'obtiens pas tous les échantillons (ce ne sont que des échantillons), il est obligatoire que je reçois le dernier> dernier fort>. P>
Ma 1ère solution consistait à rendre le producteur de données Envoyer un datagramme UDP pour chaque échantillon produit et laissez le consommateur de données essayer d'obtenir les échantillons qu'il pourrait et laissez les autres être mis au rebut par la couche de socket lorsque la prise UDP est pleine. Le problème avec cette solution est que lorsque les nouveaux datagrammes UDP arrivent et que le socket est plein, il s'agit des Ma question est la suivante: Y a-t-il un moyen de créer une prise UDP remplacer d'anciens datagrammes lorsque de nouveaux arrivent? P>
Le récepteur est actuellement une machine Linux, mais cela pourrait changer en faveur d'un autre système d'exploitation de type UNIX à l'avenir (Windows peut être possible car elle implémente les sockets BSD, mais moins probable) PS: J'ai pensé à d'autres solutions, mais ils sont plus complexes (impliquent une modification intense de l'expéditeur), je voudrais donc d'abord avoir un statut précis sur la faisabilité de ce que je demande! :) p>
mises à jour: strong>
- Je sais que le système d'exploitation sur la machine destinataire peut gérer la charge réseau + remontage du trafic généré par l'expéditeur. C'est juste que son comportement par défaut est de supprimer les nouveaux datagrammes lorsque le tampon de la prise est plein. Et en raison des délais de traitement dans le processus de réception, je sais que cela deviendra complet tout ce que je fais (la moitié de la mémoire de la mémoire sur un tampon de prise n'est pas une option :)). S'il n'y a vraiment aucun moyen de changer le comportement de la réception UDP dans une prise BSD, alors dites-moi simplement, je suis prêt à accepter cette terrible vérité et commencera à travailler sur la solution "Processus d'assistance" quand je revenir à elle :) p>
La solution idéale utiliserait des mécanismes généralisés (comme setsockopt () s) au travail. P>
- J'aimerais vraiment éviter d'avoir un processus d'assistance à faire ce que le système d'exploitation aurait pu faire lors de l'expressation de paquets et de la ressource de déchets qui copier des messages dans un SHM.
- le problème que je vois avec la modification de l'expéditeur est que le code que j'ai accès est juste une fonction PleaseWisData (), il n'a aucune connaissance que cela peut être la dernière fois qu'elle est appelée avant longtemps, alors je ne le fais pas Voir toutes les astuces faisables à cette fin ... mais je suis ouvert aux suggestions! :) p>
6 Réponses :
Il suffit de définir la prise sur le non-blocage et de la boucle sur RECV () code> jusqu'à ce qu'il renvoie <0 avec
errno == eagain code>. Puis traiter le dernier paquet que vous avez obtenu, rincer et répéter. P>
Le problème est que la machine de réception est beaucoup plus lente que l'envoi de la machine et lorsqu'elle traite le dernier datagramme dans la mémoire tampon de prise, le tampon de prise peut devenir à nouveau à nouveau et perdre le dernier datagramme de la "transmission".
@Nawak: Pour contourner cela, vous pouvez définir un tampon de réception suffisamment grand sur la prise, comme explique Valdo.
Je fais cela avec UDP (RECVFROM) dans un look avec une prise non bloquante et rien n'a jamais été trouvé (toujours 0). S'il bloque, quelque chose est lu. Pourquoi cela arrive-t-il?
@darkgaze: 0 est un retour réussi, alors il a lu un paquet. Ma suggestion dans cette réponse est de boucler jusqu'à ce que les paquets non zéro - c.-à-d. Jetez des paquets jusqu'à ce qu'il n'y ait plus de choses dans la file d'attente - puis traitez le dernier reçu.
Je suis d'accord avec "CAF". Définissez la prise sur un mode non bloquant.
Chaque fois que vous recevez quelque chose sur la prise - lisez une boucle jusqu'à ce que rien ne reste plus. Puis gérez le dernier datagramme de lecture. P>
une seule note: vous devez définir un grand tampon de réception du système pour la prise P>
int nRcvBufSize = 5*1024*1024; // or whatever you think is ok setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*) &nRcvBufSize, sizeof(nRcvBufSize));
Une autre idée est d'avoir un processus de lecteur dédié qui ne fait que des boucles sur la prise et lit les paquets entrants dans une mémoire tampon circulaire dans la mémoire partagée (vous devrez vous inquiéter de la commande de rédaction appropriée). Quelque chose comme Peut être trop compliqué pour un simple lecteur à sens unique, juste une option. P> kfifo code>
. Le non-blocage va bien ici aussi. Les nouvelles données remplacent les anciennes données. Ensuite, d'autres processus (ES) auraient toujours accès au dernier bloc à la tête de la file d'attente et em> tous les morceaux précédents non encore écrasés. P>
Oui, ma peur initiale était que ce serait la seule solution. Je n'aime pas cela car il ne perverse pas un peu des ressources déjà basses sur le récepteur ... J'espérais avoir une astuce dans la prise BSD qui permettrait d'écraser de vieux messages au lieu de jeter les nouveaux. De cette façon, le système d'exploitation ferait presque la même quantité de travail et il resterait suffisamment de ressources pour la partie de traitement.
Non, les sockets ne fonctionnent pas comme ça. Une autre option consiste à regrouper les mises à jour sur la machine plus rapide et à publier des données composées sur une minuterie plus grossier.
Ce sera difficile de se déplacer complètement juste juste sur le côté de l'auditeur, car il pourrait manquer le dernier paquet de la puce d'interface réseau, qui gardera votre programme de toujours avoir eu une chance de la voir.
L'opération Le code UDP du système serait le meilleur endroit pour essayer de traiter cela puisqu'il obtiendra de nouveaux paquets, même s'il décide de les jeter, car il a déjà trop besoin d'être mis en file d'attente. Ensuite, cela pourrait prendre la décision de laisser tomber un ancien ou de laisser tomber un nouveau, mais je ne sais pas comment faire savoir que c'est ce que vous voudriez qu'il fasse. P>
Vous pouvez essayer Pour faire face à cela sur le récepteur en disposant d'un programme ou d'un fil qui tente toujours de lire dans le dernier paquet et un autre qui essaie toujours d'obtenir ce dernier paquet. Comment faire cela différerait en fonction de si vous l'avez fait deux programmes distincts ou comme deux threads. P>
comme threads dont vous auriez besoin d'un mutex (sémaphore ou quelque chose comme ça) pour protéger un pointeur (ou une référence) à une structure utilisée pour contenir 1 charge utile UDP et tout ce que vous vouliez là-bas (taille, adresse IP de l'expéditeur, porteur, horodatage, etc.). p>
Le fil qui lit réellement les paquets de la prise stockerait le paquet données dans une structure, acquérir le mutex protégeant ce pointeur, échangez le pointeur actuel pour un pointeur à la structure qu'il vient de faire, libérer le mutex, signalez le fil du processeur qu'il a quelque chose à faire, puis dégagez la structure qu'il contient. vient de recevoir un pointeur et l'utiliser pour maintenir le paquet suivant qui s'inscrit. P>
Le fil qui traçait réellement des charges utiles de paquets doit attendre sur le signal de l'autre thread et / ou périodiquement (500 ms ou de même. Probablement un bon point de départ pour cela, mais vous décidez) et aquiez le mutex, échanger son pointeur à une structure de charge utile UDP Re avec celui qui est là, relâchez le mutex, puis si la structure dispose de données de paquets, il devrait le traiter puis attendre sur le signal suivant. S'il n'a pas eu de données, il devrait simplement aller de l'avant et attendre sur le signal suivant. P>
Le thread du processeur doit probablement fonctionner à une priorité inférieure à celle de l'auditeur UDP afin que l'auditeur soit moins susceptible de manquer un paquet. Lors du traitement du dernier paquet (celui que vous vous souciez vraiment de), le processeur ne sera pas interrompu car il n'y a pas de nouveaux paquets pour l'auditeur à entendre. P>
Vous pouvez l'étendre en utilisant une file d'attente plutôt que juste un pointeur unique comme le lieu d'échange pour les deux threads. Le pointeur unique n'est qu'une file d'attente de longueur 1 et est très facile à traiter. P>
Vous pouvez également l'étendre en essayant de détecter le thread de l'écoute s'il y a plusieurs paquets en attente et ne posant que le dernier ceux-ci dans la file d'attente pour le fil du processeur. Comment cela vous différer sera différer par la plate-forme, mais si vous utilisez un * Nix, alors cela devrait renvoyer 0 pour les sockets sans rien attendre: p> un UDP_Packet devrait être attribué Pour chaque fil ainsi que 1 pour le pointeur d'échange. Si vous utilisez une file d'attente pour échanger, vous devez avoir suffisamment d'udp_tackets pour chaque position dans la file d'attente - car le pointeur est juste une file d'attente de longueur 1, il suffit de 1. P> Si vous utilisez un système POSIX Ensuite, envisagez de ne pas utiliser de signal en temps réel pour la signalisation car ils devaient faire la queue. L'utilisation d'un signal régulier vous permettra de traiter d'être signalé plusieurs fois de la même manière que l'étant signalé une seule fois jusqu'à ce que le signal soit manipulé, tandis que le temps réel signale la queue. Se réveiller périodiquement pour vérifier que la file d'attente vous permet également de gérer la possibilité du dernier signal arrivant juste après que vous aviez vérifié si vous aviez des nouveaux paquets, mais avant d'appeler pause code> pour attendre sur un signal. p> p>
Vous avez raison que ce que je cherche est un moyen de dire au système d'exploitation de changer de comportement lors de l'envoi de nouveaux datagrammes UDP. Votre algorithme proposé est mon "plan b" et je ressens de plus en plus que ce sera ma seule solution simple ...: / Merci beaucoup pour prendre le temps de l'expliquer dans de tels détails, c'était une lecture intéressante! PS: Dans la solution "Plan B" que j'avais à l'esprit, je n'utilisais pas de signaux, mais je n'aime pas les variables, que j'aime plus parce qu'elles ne sont pas aussi asynchrones comme des signaux.
@Nawak: L'utilisation de signaux permet à votre thread de traitement de ne pas utiliser le temps de la CPU pendant qu'il n'y a pas de travail à faire. S'il s'agit d'une machine de processeur unique, les deux threads seront déjà en concurrence pour le temps de processeur et l'attente occupée du processeur ralentit la totalité (probablement plus lente qu'elle ne l'est déjà). Dans votre gestionnaire de signal, vous pouvez incrémenter un Int volatil que le fil de processeur examine lorsqu'il se réveille. Si la variable n'est pas modifiée, les appels de threads pause code> retourner et se rendormir.
Les variables de condition ne sont pas "attendues en attente". Le fil appelant pthread_cond_wait () est en train de dormir jusqu'à ce qu'il soit réveillé par l'autre thread de pthread_cond_signal () ou PthreadCast () ... L'avantage supplémentaire est que le mutex protège la mémoire partagée peut être utilisé avec le cond_var et le fil édifié possède automatiquement le mutex de la mémoire partagée sur le réveil.
Comme tout le monde semble penser qu'il n'y a pas de solution pour modifier le comportement de la prise, je marquais cette question comme répondu et je choisis votre réponse car elle est la plus détaillée. Merci à tous!
Je suis à peu près sûr que c'est un problème d'insoluble de près lié à la Deux problèmes de l'armée . p>
Je peux penser à une solution sale: établir une connexion latérale latérale de "contrôle" TCP qui porte le dernier paquet qui est également une indication "de transmission finale". Sinon, vous devez utiliser l'un des moyens pragmatiques les plus généraux notés dans Approches d'ingénierie . p>
Ceci est une ancienne question, mais vous voulez essentiellement tourner la queue de la prise (FIFO) en une pile (LIFO). Ce n'est pas possible, sauf si vous voulez violer le noyau. P>
Vous aurez besoin de déplacer les datagrammes de l'espace du noyau à l'espace utilisateur, puis de traiter. L'approche la plus facile serait une boucle comme celle-ci ... p>
Quelle est la plate-forme et la langue de l'hôte destinataire?
«Consommer» ou «lire» être un remplacement approprié pour «Exploiter» dans votre question? "Exploit" semble déroutant ici.
@MSW: C'est actuellement un hôte Linux mais pourrait être une autre variante Unix à l'avenir ... Je vais ajouter cela à ma question. Idéalement, je demande un mécanisme assez répandu dans les prises BSD "Standard" BSD chaque outil de saveur UNIX de sa manière. (ioctl () s spécifique à une plate-forme pourrait aider entre-temps)
@Negoose: Oui, ce sont de meilleurs mots! (Je ne suis pas un orateur natif) Je vais éditer la question comme vous le suggérez