Fiabilité de reconnaissance à l'aide d'UDP

16

J'ai une question sur UDP. Pour le contexte, je travaille sur un jeu d'action en temps réel.

J'ai beaucoup lu sur les différences entre UDP et TCP et je sens que je les comprends assez bien, mais il y a un élément qui ne s'est jamais senti correct, et c'est la fiabilité , et en particulier les remerciements . Je comprends que UDP n'offre aucune fiabilité par défaut (c'est-à-dire que les paquets peuvent être abandonnés ou arriver en panne). Lorsqu'une certaine fiabilité est requise, la solution que j'ai vue (ce qui est logique sur le plan conceptuel) consiste à utiliser des accusés de réception (c'est-à-dire que le serveur envoie un paquet au client, et lorsque le client reçoit ce message, il renvoie un accusé de réception au serveur) .

Que se passe-t-il lorsque l'accusé de réception est abandonné?

Dans l'exemple ci-dessus (un serveur envoie un paquet à un client), le serveur gère la perte potentielle de paquets en renvoyant des paquets à chaque trame jusqu'à ce que des accusés de réception soient reçus pour ces paquets. Vous pouvez toujours rencontrer des problèmes de bande passante ou des messages hors service, mais uniquement du point de vue de la perte de paquets, le serveur est couvert.

Cependant, si le client envoie un accusé de réception qui n'arrive jamais, le serveur n'aurait d'autre choix que d'arrêter éventuellement d'envoyer ce message, ce qui pourrait interrompre le jeu si les informations contenues dans ce paquet étaient nécessaires. Vous pouvez adopter une approche similaire pour le serveur (c.-à-d. Continuer d'envoyer des accusés de réception jusqu'à ce que vous receviez un accusé de réception pour l'acquittement?), Mais cette approche vous obligerait à boucler d'avant en arrière pour toujours (puisque vous auriez besoin d'un accusé de réception pour l'accusé de réception pour l'accusé de réception etc).

Je pense que ma logique de base est correcte ici, ce qui me laisse deux options.

  1. Envoyez un seul paquet d'accusé de réception et espérez le meilleur.
  2. Envoyez une poignée de paquets d'accusé de réception (peut-être 3-4) et espérez le meilleur, en supposant qu'ils ne seront pas tous abandonnés.

Y a-t-il une réponse à ce problème? Suis-je fondamentalement incompréhensible quelque chose? Y a-t-il une garantie d'utiliser UDP dont je ne suis pas au courant? J'hésite à avancer avec trop de code de réseautage jusqu'à ce que je sois à l'aise que ma logique soit solide.

Grimelios
la source
11
Peut-être vous manque-t-il une idée de "délais d'attente" et de "nouvelles tentatives".
Kromster dit soutenir Monica le
Je pourrais être sûr. Vous suggérez que ma logique est correcte et, pour ne pas paraître trop négative, mais pendant la programmation réseau, je ne peux assumer aucune garantie sur pratiquement n'importe quelle information en réseau? Au cours d'un jeu en temps réel, c'est une tonne d'informations potentiellement perdues, ce qui est bien, mais je veux juste m'assurer de bien comprendre le problème.
Grimelios
10
Aucune garantie. Droite. N'incluez jamais "l'espoir" dans vos algorithmes. Ils doivent gérer TOUTES les combinaisons malchanceuses. PS Nous avons simplement basculé vers TCP dans notre RTS, où eberything est pris en charge, car nous avons besoin d'une communication fiable (pour la simulation de verrouillage).
Kromster dit soutenir Monica le
5
Utilisez TCP lorsque la fiabilité est nécessaire, utilisez UDP quand cela n'a pas d'importance. Par exemple, les coordonnées du joueur sont envoyées dans mon jeu via UDP. J'utilise l'interpolation et le lissage pour éliminer les paquets manquants. fonctionne comme un charme. les choses qui doivent vraiment être fiables mais qui peuvent être un peu plus lentes sont envoyées via TCP. Si vous avez un état où un état plus récent invalide l'ancien état, UDP est un bon choix car peu importe quand quelque chose entre les deux a été abandonné 8e.g. La position du joueur).
Polygnome
Ce n'est pas une réponse directe à votre question, mais je recommande fortement de ne demander un accusé de réception dans un jeu en temps réel que lorsqu'ils sont absolument nécessaires (par exemple, lors de la connexion initiale). Il est beaucoup plus simple (et robuste) de concevoir à la fois le client et le serveur afin qu'ils "fonctionnent avec ce qu'ils ont" jusqu'à ce qu'ils obtiennent un nouveau paquet dans un système sans état si vous le pouvez. Quake 3 a incroyablement bien réussi avec un système basé sur des instantanés . De plus, des bibliothèques comme Enet ne peuvent envoyer que certains paquets de manière fiable, pour les cas où vous en avez vraiment besoin
jrh

Réponses:

32

C'est une forme du problème des deux généraux , et vous avez raison - aucun nombre de tentatives n'est suffisant pour garantir parfaitement la réception.

Dans la pratique des jeux, il y a généralement un horizon au-delà duquel les informations n'ont pas vraiment d'importance, même si elles arrivent techniquement de manière fiable. Comme découvrir que vous aviez un tir à la tête parfait il y a 2 secondes - il est trop tard pour que le joueur utilise cette information maintenant.

Si votre perte de paquets est si élevée que vous ne pouvez pas systématiquement obtenir les informations nécessaires à l'intérieur d'une fenêtre de réaction serrée, alors pour un jeu en temps réel, vous feriez mieux de donner un coup de pied au joueur et d'essayer de trouver une meilleure correspondance pour lui ailleurs, plutôt que de continuer à essayer d'envoyer le paquet pour émuler une connexion fiable.

Pour cette raison, certains systèmes de réplication de jeux ignorent complètement les accusés de réception et les tentatives et choisissent de spammer la plus récente mise à jour aussi souvent qu'ils le peuvent. Si l'un est tombé ou arrive en retard, tant pis, sautez-le, prenez le suivant et continuez, en vous appuyant sur les systèmes de prédiction et d'interpolation pour lisser l'écart et minimiser les hoquets visibles pour le joueur.

Je veux soudainement commencer à appeler cette "réplication Simba" pour la façon dont elle ignore les problèmes du passé et essaie de vivre dans le moment présent. ;)

Rafiki établissant une réductio ad absurdum sur cette philosophie de vie

Une solution hybride consiste à prendre de l'avance en envoyant la nouvelle mise à jour ET (puisque les mises à jour de l'état du jeu peuvent souvent être assez petites / compressibles ) également intégrer la dernière mise à jour, et peut-être celle d'avant ... Donc, juste au cas où le client les manquerait , vous n'avez pas besoin d'attendre un aller-retour complet pour le découvrir et le corriger. La plupart du temps, le client a déjà vu cela, il y a donc des données redondantes de cette façon, mais la latence pour corriger un message manqué est plus faible. Les mises à jour du client peuvent inclure le numéro d'index de la dernière mise à jour consécutive qu'ils ont vue, vous pouvez donc être minimalement conservateur avec le nombre d'anciennes mises à jour que vous incluez dans le prochain paquet de mise à jour.

Vous pouvez également implémenter un système à deux niveaux comme un autre type d'hybride, où l'état à courte durée de vie est répliqué de manière peu fiable et où l'état à long terme est synchronisé de manière fiable, en utilisant TCP ou votre propre implémentation de fiabilité avec une nouvelle tentative élevée compter. Cela devient cependant plus complexe à gérer, car vous avez deux systèmes de messagerie à gérer et les deux instantanés peuvent être désynchronisés, ajoutant une toute nouvelle classe de cas de périphérie.

DMGregory
la source
1
+1, bien écrit. Je voudrais simplement souligner que cela est plus pertinent pour les jeux d'action / temps réel. Les jeux TBS et RTS (et certains événements de jeux d'action) ont une vision différente de "l'horizon temporel au-delà duquel l'information n'a pas vraiment d'importance".
Kromster dit soutenir Monica le
3
Oui, pour un jeu au tour par tour, j'imagine que l'on utiliserait TCP plutôt que d'essayer de rouler sa propre couche de fiabilité au-dessus d'UDP. ;) Je classerais toujours le micro dans un RTS comme le type de gameplay avec un horizon temporel précis - cette approche hybride peut bien faire là-bas, où vous avez à la fois des mises à jour à faible latence pour la chaleur du moment ainsi qu'un filet de sécurité pour gérer rétroactivement les événements critiques manqués comme les dépenses en ressources.
DMGregory
C'est extrêmement utile et valide en quelque sorte ma préoccupation initiale. Merci beaucoup.
Grimelios
2
Il pourrait également être utile de mentionner la correction d'erreur directe. Concevez votre protocole de telle sorte que le récepteur puisse déterminer indépendamment qu'un paquet a été abandonné lors de la réception du prochain paquet, en ajoutant des données supplémentaires pour lisser l'interpolation requise. Cela peut être utile car souvent les paquets UDP ne sont pas pleins de toute façon, et vous envoyez simplement des paquets plus petits plus souvent pour réduire la latence. L'ajout d'octets supplémentaires ne nuira pas à la latence, et la bande passante n'est pas un problème dans ces cas.
MSalters
@MSalters Je dirais que cela mérite d'être développé dans sa propre réponse, si vous le souhaitez. Je voterais pour cela. :)
DMGregory
9

L'approche utilisée par TCP est que l'expéditeur continuera à renvoyer le paquet jusqu'à ce qu'il reçoive un accusé de réception. Le récepteur ignorera les paquets en double, mais enverra toujours des accusés de réception pour eux. L'expéditeur ignorera les accusés de réception en double.

Si un paquet est perdu, l'expéditeur le renvoie, comme vous le savez déjà.
Si un accusé de réception est perdu, l'expéditeur renvoie le paquet d'origine, ce qui oblige le destinataire à renvoyer l'accusé de réception.

Si un accusé de réception n'est pas reçu dans un certain délai (peut-être 60 secondes ou 20 tentatives), le joueur est considéré comme déconnecté du jeu. Vous devez implémenter une sorte de règle de temporisation, sinon un joueur qui débranche son câble réseau bloquera les ressources de votre serveur pour toujours.

user253751
la source
Une caractéristique essentielle de TCP est que l'expéditeur n'a pas besoin de se soucier de savoir si un paquet particulier a été reconnu, mais doit surtout se soucier de la «marque haute» et de la durée pendant laquelle les paquets ont été en attente sans que la marque haute ne bouge.
supercat
1
@supercat Je ne dirais pas que c'est essentiel; plus comme une optimisation.
user253751
En ce qui concerne la chose entre parenthèses (envoyer un ACK pour les paquets que vous avez déjà reçus), je pense que vous devriez en fait le souligner au lieu de le mettre entre parenthèses. Il semble manquer à la compréhension du PO (ou du moins à sa description).
Angew n'est plus fier de SO
@Angew fait maintenant.
user253751
6

Si vous souhaitez réinventer TCP, il est judicieux d' examiner d' abord TCP , qui traite du problème exact que vous décrivez (une partie de la solution consiste à utiliser des valeurs définies par l'utilisateur pour les tentatives de tentative et les délais d'attente).

Les solutions qui utilisent 2 canaux, un canal TCP (pour une communication fiable) ainsi qu'un canal UDP (pour une communication à faible latence) ne sont pas rares.

Certaines solutions détectent quand un client manque des informations pendant trop longtemps et démarrent une resynchronisation, qui peut utiliser UDP ou TCP.

Une autre approche courante consiste à concevoir une communication telle qu'elle ne repose pas du tout sur des remerciements, mais cela sort du cadre de la question.

Peter
la source
3

Dans un RTS, vous ne pouvez vraiment pas utiliser un protocole comme TCP et vous ne pouvez pas non plus rendre UDP fiable. Si vous essayez, le jeu se fige chaque fois qu'il y a une coupure de réseau.

Au lieu de cela, vous concevez le protocole de sorte que les paquets manqués ne soient pas trop importants.

La version courte est que vous ne vous souciez pas de la dernière image des autres joueurs tant que vous savez où ils sont maintenant . La version longue est plus compliquée.

La question devient alors, que faites-vous quand un paquet est manquant? Et la réponse est ... vous devinez. Le joueur se déplace probablement en ligne droite, non? Déplacez-les simplement un peu plus loin sur cette ligne. ... Sauf qu'aucun joueur RTS ne se déplace jamais en ligne droite. Et puis il y a la détection de collision.

C'est dur. De nombreux jeux se trompent. On peut faire valoir qu'il n'y a pas de bonne réponse à cela, seulement divers torts qui peuvent être échangés.

La raison pour laquelle ces jeux fonctionnent plutôt bien est non seulement qu'ils ont longuement réfléchi à ces problèmes, mais aussi que l'Internet est devenu assez fiable. Presque tous les paquets UDP atteignent réellement leur destination en temps opportun. (Sauf s'il y a un problème permanent comme un pare-feu)

Stig Hemmer
la source
Warcraft 3 utilise TCP.
fsp