Problèmes de conception - Envoi de petits segments de données via TCP avec Winsock

Lorsque vous devez envoyer de petits paquets de données via TCP, la conception de votre application Winsock est particulièrement critique. Une conception qui ne tient pas compte de l’interaction de l’accusé de réception différé, de l’algorithme Nagle et de la mise en mémoire tampon Winsock peut affecter considérablement les performances. Cet article traite de ces questions à l’aide de quelques études de cas. Il dérive également une série de recommandations pour l’envoi efficace de petits paquets de données à partir d’une application Winsock.

Version d’origine du produit : Winsock
Numéro de la base de connaissances d’origine : 214397

Contexte

Lorsqu’une pile Microsoft TCP reçoit un paquet de données, un minuteur de retard de 200 ms s’éteint. Lorsqu’un ACK est envoyé, le minuteur de délai est réinitialisé et démarre un autre délai de 200 ms lors de la réception du paquet de données suivant. Pour augmenter l’efficacité dans les applications Internet et intranet, la pile TCP utilise les critères suivants pour décider quand envoyer un ACK sur les paquets de données reçus :

  • Si le deuxième paquet de données est reçu avant l’expiration du minuteur de délai, l’ACK est envoyé.
  • Si des données doivent être envoyées dans la même direction que l’ACK avant la réception du deuxième paquet de données et l’expiration du minuteur de retard, l’ACK est piggybacké avec le segment de données et envoyé immédiatement.
  • Lorsque le minuteur de délai expire, l’ACK est envoyé.

Pour éviter que de petits paquets de données ingèrent le réseau, la pile TCP active l’algorithme Nagle par défaut, qui fusionne une petite mémoire tampon de données à partir de plusieurs appels d’envoi et retarde l’envoi jusqu’à ce qu’un ACK pour le paquet de données précédent envoyé soit reçu de l’hôte distant. Voici deux exceptions à l’algorithme Nagle :

  • Si la pile a fusionné une mémoire tampon de données supérieure à l’unité de transmission maximale (MTU), un paquet de taille réelle est immédiatement envoyé sans attendre l’ACK de l’hôte distant. Sur un réseau Ethernet, la MTU pour TCP/IP est de 1460 octets.

  • L’option TCP_NODELAY socket est appliquée pour désactiver l’algorithme Nagle afin que les petits paquets de données soient remis à l’hôte distant sans délai.

Pour optimiser les performances au niveau de la couche application, Winsock copie les mémoires tampons de données à partir de l’envoi d’appels d’application vers une mémoire tampon du noyau Winsock. Ensuite, la pile utilise ses propres heuristiques (comme l’algorithme Nagle) pour déterminer quand placer réellement le paquet sur le câble. Vous pouvez modifier la quantité de mémoire tampon du noyau Winsock allouée au socket à l’aide de l’option SO_SNDBUF (8K par défaut). Si nécessaire, Winsock peut mettre en mémoire tampon plus que la taille de la SO_SNDBUF mémoire tampon. Dans la plupart des cas, la fin de l’envoi dans l’application indique uniquement que la mémoire tampon de données dans un appel d’envoi d’application est copiée dans la mémoire tampon du noyau Winsock et n’indique pas que les données ont atteint le support réseau. La seule exception concerne la désactivation de la mise en mémoire tampon Winsock en définissant SO_SNDBUF sur 0.

Winsock utilise les règles suivantes pour indiquer une fin d’envoi à l’application (selon la façon dont l’envoi est appelé, la notification d’achèvement peut être la fonction retournant à partir d’un appel bloquant, signalant un événement ou appelant une fonction de notification, etc.) :

  • Si le socket est toujours dans SO_SNDBUF quota, Winsock copie les données de l’envoi de l’application et indique la fin de l’envoi à l’application.

  • Si le socket est au-delà SO_SNDBUF du quota et qu’il n’y a qu’un seul envoi précédemment mis en mémoire tampon dans la mémoire tampon du noyau de pile, Winsock copie les données de l’envoi de l’application et indique la fin de l’envoi à l’application.

  • Si le socket est au-delà SO_SNDBUF du quota et qu’il y a plusieurs envois précédemment mis en mémoire tampon dans la mémoire tampon du noyau de pile, Winsock copie les données de l’envoi de l’application. Winsock n’indique pas la fin de l’envoi à l’application tant que la pile n’a pas terminé suffisamment d’envois pour remettre le socket dans le quota SO_SNDBUF ou une seule condition d’envoi en attente.

Étude de cas 1

Un client TCP Winsock doit envoyer 10 000 enregistrements à un serveur TCP Winsock pour le stocker dans une base de données. La taille des enregistrements varie entre 20 octets et 100 octets. Pour simplifier la logique d’application, la conception est la suivante :

  • Le client bloque uniquement l’envoi. Le serveur effectue uniquement le blocage recv .
  • Le socket client définit le sur SO_SNDBUF 0 afin que chaque enregistrement sorte d’un segment de données unique.
  • Le serveur appelle recv dans une boucle. La mémoire tampon publiée dans recv est de 200 octets afin que chaque enregistrement puisse être reçu en un seul recv appel.

Performances

Pendant le test, le développeur trouve que le client ne peut envoyer que cinq enregistrements par seconde au serveur. L’envoi au serveur des 10 000 enregistrements, au maximum à 976 Ko de données (10 000 * 100 / 1024), prend plus d’une demi-heure.

Analyse

Étant donné que le client ne définit pas l’option TCP_NODELAY , l’algorithme Nagle force la pile TCP à attendre un ACK avant de pouvoir envoyer un autre paquet sur le câble. Toutefois, le client a désactivé la mise en mémoire tampon Winsock en définissant l’option SO_SNDBUF sur 0. Par conséquent, les 10 000 appels d’envoi doivent être envoyés et ACK’ed individuellement. Chaque ACK est retardé de 200 ms, car les éléments suivants se produisent sur la pile TCP du serveur :

  • Lorsque le serveur obtient un paquet, son minuteur de retard de 200 ms s’éteint.
  • Le serveur n’a pas besoin de renvoyer quoi que ce soit, de sorte que l’ACK ne peut pas être piggybacké.
  • Le client n’envoie pas un autre paquet, sauf si le paquet précédent est accusé de réception.
  • Le minuteur de retard sur le serveur expire et l’ACK est renvoyé.

Comment améliorer

Cette conception pose deux problèmes. Tout d’abord, il y a le problème du minuteur de délai. Le client doit être en mesure d’envoyer deux paquets au serveur dans un délai de 200 ms. Étant donné que le client utilise l’algorithme Nagle par défaut, il doit simplement utiliser la mise en mémoire tampon Winsock par défaut et ne pas définir sur SO_SNDBUF 0. Une fois que la pile TCP a fusionné une mémoire tampon supérieure à l’unité de transmission maximale (MTU), un paquet de taille réelle est envoyé immédiatement sans attendre l’ACK à partir de l’hôte distant.

Deuxièmement, cette conception appelle un envoi pour chaque enregistrement de si petite taille. L’envoi de ce petit d’une taille n’est pas efficace. Dans ce cas, le développeur peut vouloir compléter chaque enregistrement sur 100 octets et envoyer 80 enregistrements à la fois à partir d’un appel d’envoi client. Pour indiquer au serveur le nombre total d’enregistrements envoyés, le client peut souhaiter démarrer la communication avec un en-tête de taille fixe contenant le nombre d’enregistrements à suivre.

Étude de cas 2

Une application cliente Winsock TCP ouvre deux connexions avec une application serveur Winsock TCP fournissant un service de cotations boursières. La première connexion est utilisée comme canal de commande pour envoyer le symbole de stock au serveur. La deuxième connexion est utilisée comme canal de données pour recevoir la cotation boursière. Une fois les deux connexions établies, le client envoie un symbole boursier au serveur via le canal de commande et attend que la cotation boursière revienne par le canal de données. Il envoie la demande de symbole boursier suivant au serveur uniquement après la réception du premier cours boursier. Le client et le serveur ne définissent pas l’option SO_SNDBUF et TCP_NODELAY .

  • Performances

    Pendant le test, le développeur trouve que le client ne peut obtenir que cinq devis par seconde.

  • Analyse

    Cette conception n’autorise qu’une seule demande de cotation boursière à la fois. Le premier symbole de stock est envoyé au serveur via le canal de commande (connexion) et une réponse est immédiatement renvoyée du serveur au client via le canal de données (connexion). Ensuite, le client envoie immédiatement la deuxième demande de symbole de stock et l’envoi retourne immédiatement lorsque la mémoire tampon de requête dans l’appel d’envoi est copiée dans la mémoire tampon du noyau Winsock. Toutefois, la pile TCP cliente ne peut pas envoyer immédiatement la requête à partir de sa mémoire tampon du noyau, car le premier envoi sur le canal de commande n’est pas encore reconnu. Après l’expiration du minuteur de retard de 200 ms au niveau du canal de commande du serveur, l’ACK pour la première demande de symboles revient au client. Ensuite, la deuxième demande de devis est correctement envoyée au serveur après avoir été retardée de 200 ms. La cotation du deuxième symbole boursier revient immédiatement par le biais du canal de données, car, à ce stade, le minuteur de retard au niveau du canal de données client a expiré. Un ACK pour la réponse de devis précédente est reçu par le serveur. (N’oubliez pas que le client n’a pas pu envoyer une deuxième demande de cotation boursière pour 200 ms, ce qui laisse le temps à la minuterie de délai sur le client d’expirer et d’envoyer un ACK au serveur.) Par conséquent, le client obtient la deuxième réponse de devis et peut émettre une autre demande de devis, qui est soumise au même cycle.

  • Comment améliorer

    La conception des deux connexions (canal) n’est pas nécessaire ici. Si vous n’utilisez qu’une seule connexion pour la demande de cotation boursière et la réponse, l’ACK pour la demande de cotation peut être piggybacké sur la réponse de devis et revenir immédiatement. Pour améliorer davantage les performances, le client peut multiplexer plusieurs demandes de cotation boursière en un seul appel au serveur et le serveur peut également multiplexer plusieurs réponses de devis en un seul appel au client. Si la conception des deux canaux unidirectionnels est nécessaire pour une raison quelconque, les deux côtés doivent définir l’option TCP_NODELAY afin que les petits paquets puissent être envoyés immédiatement sans avoir à attendre un ACK pour le paquet précédent.

Recommandations

Bien que ces deux études de cas soient fabriquées, elles permettent d’illustrer certains pires scénarios. Lorsque vous concevez une application qui implique des envois de segments de données volumineux et recvs, vous devez prendre en compte les instructions suivantes :

  • Si les segments de données ne sont pas critiques dans le temps, l’application doit les fusionner dans un bloc de données plus volumineux pour passer à un appel d’envoi. Étant donné que la mémoire tampon d’envoi est susceptible d’être copiée dans la mémoire tampon du noyau Winsock, la mémoire tampon ne doit pas être trop volumineuse. Un peu moins de 8K est efficace. Tant que le noyau Winsock obtient un bloc plus grand que le MTU, il envoie plusieurs paquets de taille réelle et un dernier paquet avec tout ce qui reste. Le côté expéditeur, à l’exception du dernier paquet, n’est pas atteint par le minuteur de retard de 200 ms. Le dernier paquet, s’il s’agit d’un paquet impair, est toujours soumis à l’algorithme d’accusé de réception différé. Si la pile de fin d’envoi obtient un autre bloc plus grand que le MTU, elle peut toujours contourner l’algorithme Nagle.

  • Si possible, évitez les connexions de socket avec un flux de données unidirectionnel. Les communications sur des sockets unidirectionnels sont plus facilement affectées par les algorithmes Nagle et d’accusé de réception différé. Si la communication suit une demande et un flux de réponse, vous devez utiliser un seul socket pour effectuer les deux envois et recvs afin que l’ACK puisse être piggybacké sur la réponse.

  • Si tous les petits segments de données doivent être envoyés immédiatement, définissez TCP_NODELAY l’option à la fin de l’envoi.

  • Sauf si vous souhaitez garantir qu’un paquet est envoyé sur le réseau lorsqu’une fin d’envoi est indiquée par Winsock, vous ne devez pas définir sur SO_SNDBUF zéro. En fait, la mémoire tampon de 8 Ko par défaut a été déterminée de manière heuristique pour fonctionner correctement dans la plupart des situations et vous ne devez pas la modifier, sauf si vous avez testé que votre nouveau paramètre de mémoire tampon Winsock vous offre de meilleures performances que la valeur par défaut. En outre, la définition de la valeur SO_SNDBUF zéro est principalement bénéfique pour les applications qui effectuent un transfert de données en bloc. Même dans ce cas, pour une efficacité maximale, vous devez l’utiliser conjointement avec une double mise en mémoire tampon (plusieurs envois en attente à un moment donné) et des E/S superposées.

  • Si la remise des données n’a pas besoin d’être garantie, utilisez UDP.

References

Pour plus d’informations sur l’accusé de réception différé et l’algorithme Nagle, consultez les rubriques suivantes :

Braden, R.[1989], RFC 1122, Requirements for Internet Hosts--Communication Layers, Internet Engineering Task Force.