J'envoie pas mal de données vers et depuis un serveur, pour un jeu que je fais.
J'envoie actuellement des données de localisation comme celle-ci:
sendToClient((("UID:" + cl.uid +";x:" + cl.x)));
sendToClient((("UID:" + cl.uid +";y:" + cl.y)));
sendToClient((("UID:" + cl.uid +";z:" + cl.z)));
De toute évidence, il envoie les valeurs respectives X, Y et Z.
Serait-il plus efficace d'envoyer des données comme celle-ci?
sendToClient((("UID:" + cl.uid +"|" + cl.x + "|" + cl.y + "|" + cl.z)));
networking
joehot200
la source
la source
Réponses:
Un segment TCP a beaucoup de surcharge. Lorsque vous envoyez un message de 10 octets avec un paquet TCP, vous envoyez réellement:
résultant en 42 octets de trafic pour le transport de 10 octets de données. Vous n'utilisez donc que moins de 25% de votre bande passante disponible. Et cela ne tient pas encore compte de la surcharge que consomment les protocoles de bas niveau comme Ethernet ou PPPoE (mais ceux-ci sont difficiles à estimer car il existe de nombreuses alternatives).
En outre, de nombreux petits paquets mettent davantage à rude épreuve les routeurs, les pare-feu, les commutateurs et les autres équipements d'infrastructure réseau.Par conséquent, lorsque vous, votre fournisseur de services et vos utilisateurs n'investissez pas dans du matériel de haute qualité, cela pourrait se transformer en un autre goulot d'étranglement.
Pour cette raison, vous devriez essayer d'envoyer toutes les données dont vous disposez en une seule fois dans un segment TCP.
Concernant la gestion de la perte de paquets : lorsque vous utilisez TCP, vous n'avez pas à vous en soucier. Le protocole lui-même garantit que tous les paquets perdus sont renvoyés et que les paquets sont traités dans l'ordre, vous pouvez donc supposer que tous les paquets que vous envoyez arriveront de l'autre côté, et ils arriveront dans l'ordre dans lequel vous les envoyez. Le prix à payer est que, en cas de perte de paquets, votre lecteur subira un décalage considérable, car un paquet abandonné arrêtera tout le flux de données jusqu'à ce qu'il soit demandé et reçu à nouveau.
En cas de problème, vous pouvez toujours utiliser UDP. Mais alors vous devez trouver votre propre solution pour la perte et hors ordre des messages (au moins la garantie que les messages qui n'arrivent, arrivent complets et en bon état).
la source
Un grand (dans des limites raisonnables) est préférable.
Comme vous l'avez dit, la perte de paquets est la principale raison. Les paquets sont généralement envoyés dans des trames de taille fixe, il est donc préférable de prendre une trame avec un gros message que 10 trames avec 10 petites.
Cependant, avec TCP standard, ce n'est pas vraiment un problème, sauf si vous le désactivez. (Il s'agit de l'algorithme de Nagle , et pour les jeux, vous devez le désactiver.) TCP attendra un délai fixe ou jusqu'à ce que le package soit "plein". Où "plein" est un nombre légèrement magique, déterminé en partie par la taille du cadre.
la source
recv()
appel pour chaquesend()
appel, ce que la plupart des gens recherchent. Utiliser un protocole qui garantit cela, comme le fait UDP. "Lorsque tout ce que vous avez est TCP, tout ressemble à un flux"Toutes les réponses précédentes sont incorrectes. En pratique, peu importe que vous émettiez un
send()
appel long ou plusieurs petitssend()
appels.Comme l'indique Phillip, un segment TCP a des frais généraux, mais en tant que programmeur d'application, vous n'avez aucun contrôle sur la façon dont les segments sont générés. En termes simples:
L'OS est totalement gratuit mettre en mémoire tampon toutes vos données et de les envoyer en un seul segment, ou de prendre le long et de le diviser en plusieurs petits segments.
Cela a plusieurs implications, mais la plus importante est la suivante:
Le raisonnement derrière cela est que TCP est un protocole de flux . TCP traite vos données comme un long flux d'octets et n'a absolument aucun concept de "paquets". Avec
send()
vous ajoutez des octets à ce flux, et avecrecv()
vous obtenez des octets de l'autre côté. TCP tamponnera et divisera vos données de manière agressive là où il le jugera bon pour vous assurer que vos données parviennent de l'autre côté aussi rapidement que possible.Si vous souhaitez envoyer et recevoir des "paquets" avec TCP, vous devez implémenter des marqueurs de début de paquet, des marqueurs de longueur, etc. Que diriez-vous d'utiliser un protocole orienté message comme UDP à la place? UDP garantit qu'un
send()
appel se traduit par un datagramme envoyé et par unrecv()
appel!la source
recv()
, vous devez créer votre propre tampon pour compenser cela. Je le classerais dans la même difficulté que la mise en œuvre de la fiabilité sur UDP.while(1) { uint16_t size; read(sock, &size, sizeof(size)); size = ntoh(size); char message[size]; read(sock, buffer, size); handleMessage(message); }
(en omettant la gestion des erreurs et les lectures partielles par souci de concision, mais cela ne change pas grand-chose). Faire celaselect
n'ajoute pas beaucoup plus de complexité et si vous utilisez TCP, vous devrez probablement tamponner les messages partiels de toute façon. La mise en œuvre d'une fiabilité robuste sur UDP est beaucoup plus compliquée que cela.Beaucoup de petits paquets sont très bien. En fait, si vous êtes préoccupé par la surcharge TCP, insérez simplement un
bufferstream
qui collecte jusqu'à 1500 caractères (ou quel que soit votre MTU TCP, il est préférable de le demander dynamiquement), et traitez le problème en un seul endroit. Cela vous épargne la surcharge de ~ 40 octets pour chaque paquet supplémentaire que vous auriez autrement créé.Cela dit, il est toujours préférable d'envoyer moins de données, et la construction d'objets plus gros y contribue. Bien sûr, il est plus petit d'envoyer
"UID:10|1|2|3
que d'envoyerUID:10;x:1UID:10;y:2UID:10;z:3
. En fait, à ce stade, vous ne devriez pas non plus réinventer la roue, utilisez une bibliothèque comme protobuf qui peut réduire des données comme celle-ci à une chaîne de 10 octets ou moins.La seule chose que vous ne devez pas oublier est d'insérer un
Flush
commande sur votre flux aux emplacements appropriés, car dès que vous arrêtez d'ajouter des données à votre flux, il peut attendre infini avant d'envoyer quoi que ce soit. Vraiment problématique lorsque votre client attend ces données, et votre serveur n'enverra rien de nouveau jusqu'à ce que le client envoie la commande suivante.La perte de colis est quelque chose que vous pouvez affecter ici, de manière marginale. Chaque octet que vous envoyez peut être potentiellement corrompu, et TCP demandera automatiquement une retransmission. Des packages plus petits signifient une chance moindre pour chaque package d'être corrompu, mais parce qu'ils s'additionnent sur la surcharge, vous envoyez encore plus d'octets, augmentant encore plus les chances d'un package perdu. Lorsqu'un package est perdu, TCP mettra en mémoire tampon toutes les données suivantes jusqu'à ce que le package manquant soit renvoyé et reçu. Cela entraîne un retard important (ping). Bien que la perte totale de bande passante en raison de la perte de package puisse être négligeable, le ping plus élevé ne serait pas souhaitable pour les jeux.
Conclusion: envoyez aussi peu de données que possible, envoyez de gros paquets et n'écrivez pas vos propres méthodes de bas niveau pour le faire, mais comptez sur des bibliothèques et des méthodes bien connues comme
bufferstream
et protobuf pour gérer le gros du travail.la source
bufferstream
est trivial, c'est pourquoi je l'ai appelé une méthode. Vous souhaitez toujours le gérer en un seul endroit et ne pas intégrer votre logique de tampon à votre code de message. En ce qui concerne la sérialisation d'objets, je doute fortement que vous obteniez quelque chose de mieux que les milliers d'heures de travail que d'autres y mettent, même si vous essayez, je vous suggère fortement de comparer votre solution avec les implémentations connues.Bien qu'étant néophyte de la programmation réseau moi-même, je voudrais partager mon expérience limitée en ajoutant quelques points:
Concernant les mesures, les métriques à considérer sont:
Comme mentionné, si vous découvrez que vous n'êtes pas limité dans un sens et que vous pouvez utiliser UDP, allez-y. Il existe des implémentations basées sur UDP, vous n'avez donc pas à réinventer la roue ou à travailler contre des années d'expérience et une expérience éprouvée. Ces implémentations qui méritent d'être mentionnées sont:
Conclusion: étant donné qu'une implémentation UDP pourrait surpasser (par un facteur de 3x) une implémentation TCP, il est logique de la considérer, une fois que vous avez identifié votre scénario comme compatible UDP. Être averti! L'implémentation de la pile TCP complète au-dessus d'UDP est toujours une mauvaise idée.
la source