2009-08-26 6 views
6

J'envoie une grande quantité de données en une fois entre un C# écrit par le client et le serveur. Cela fonctionne bien quand je cours le client et le serveur sur ma machine locale mais quand je mets le serveur sur un ordinateur distant sur Internet il semble laisser tomber des données.Gestion des paquets TCP abandonnés en C#

J'envoie 20000 chaînes en utilisant la méthode socket.Send() et je les reçois en utilisant une boucle qui fait socket.Receive(). Chaque chaîne est délimitée par des caractères uniques que j'utilise pour compter le nombre reçu (c'est le protocole si vous voulez). Le protocole est prouvé, car même avec des messages fragmentés, chaque chaîne est correctement comptée. Sur ma machine locale, je reçois tous 20000, sur Internet, je reçois quelque chose entre 17000-20000. Il semble être pire la connexion plus lente que l'ordinateur distant a. Pour ajouter à la confusion, activer Wireshark semble réduire le nombre de messages abandonnés.

Tout d'abord, quelle en est la cause? Est-ce un problème TCP/IP ou quelque chose de mal avec mon code?

Deuxièmement, comment puis-je contourner cela? Recevoir toutes les 20000 chaînes est essentiel.

Socket Code réception:

private static readonly Encoding encoding = new ASCIIEncoding(); 
///... 
while (socket.Connected) 
{ 
    byte[] recvBuffer = new byte[1024]; 
    int bytesRead = 0; 

    try 
    { 
     bytesRead = socket.Receive(recvBuffer); 
    } 
    catch (SocketException e) 
    { 
    if (! socket.Connected) 
    { 
     return; 
    } 
    } 

    string input = encoding.GetString(recvBuffer, 0, bytesRead); 
    CountStringsIn(input); 
} 

Socket Code envoi:

private static readonly Encoding encoding = new ASCIIEncoding(); 
//... 
socket.Send(encoding.GetBytes(string)); 
+2

Il semble que vous avalez SocketExceptions ici aussi longtemps que la prise est connectée. Vous devriez au moins toujours imprimer l'exception si elle n'est pas renvoyée (et nettoyer la socket dans un bloc finally). Essayez d'ajouter cela et voyez s'il lance sans fermer le Socket. –

+0

Merci - aucune exception n'a été levée. Je vois des pauses (ou des retards dans la transmission) sur la réception lorsque je cours sur Internet - je pense que cela doit être l'effet de l'algorithme de Nagles comme vous l'avez mentionné. L'autre chose intéressante est le nombre moyen d'octets reçus par appel à recevoir() est beaucoup plus élevé lorsqu'il est exécuté sur Internet. – Nosrama

+0

En outre, si je désactive l'algorithme de Nagles, je laisse tomber beaucoup plus de messages. Je suppose que c'est parce que j'envoie plus de paquets bruts car Nagles ne les agrège pas en paquets plus gros et donc les chances que certains soient abandonnés augmentent. – Nosrama

Répondre

3

Si vous laissez tomber des paquets, vous verrez un retard dans la transmission car il doit retransmettre les paquets abandonnés.Cela peut être très significatif bien qu'il existe une option TCP appelée acquittement sélectif qui, si elle est supportée par les deux côtés, déclenchera un renvoi de seulement les paquets qui ont été abandonnés et non tous les paquets depuis le dépôt. Il n'y a aucun moyen de contrôler cela dans votre code. Par défaut, vous pouvez toujours supposer que chaque paquet est livré dans l'ordre pour TCP et s'il y a une raison pour laquelle il ne peut pas livrer chaque paquet dans l'ordre, la connexion tombera, soit par un timeout, soit par une extrémité de la connexion. Paquet RST.

Ce que vous voyez est probablement le résultat de l'algorithme de Nagle. Qu'est-ce qu'il fait est au lieu d'envoyer chaque bit de données que vous postez, il envoie un octet, puis attend un accusé de réception de l'autre côté. Pendant qu'il attend, il agrège toutes les autres données que vous voulez envoyer et les combine en un gros paquet, puis l'envoie. Comme la taille maximale pour TCP est de 65k, il peut combiner un peu de données en un paquet, bien qu'il soit extrêmement improbable que cela se produise, d'autant plus que la taille de buffer par défaut de winsock est d'environ 10k (j'en oublie le montant exact). De plus, si la taille maximale de la fenêtre du récepteur est inférieure à 65 Ko, elle n'enverra que la dernière taille annoncée de la fenêtre du récepteur. La taille de la fenêtre affecte également l'algorithme de Nagle en termes de quantité de données qu'il peut agréger avant l'envoi, car il ne peut pas envoyer plus que la taille de la fenêtre. La raison pour laquelle vous voyez ceci est parce que sur Internet, contrairement à votre réseau, cette première réception prend plus de temps pour revenir ainsi l'algorithme de Naggle agrège plus de vos données dans un seul paquet. Localement, le retour est effectivement instantané, il est donc capable d'envoyer vos données aussi rapidement que vous pouvez les publier sur le socket. Vous pouvez désactiver l'algorithme de Naggle du côté client en utilisant SetSockOpt (winsock) ou Socket.SetSocketOption (.Net) mais je vous recommande fortement de NE PAS désactiver Naggling sur le socket à moins que vous ne soyez sûr à 100% de ce que vous faites. C'est là pour une très bonne raison.

+1

De plus, compter les cordes n'est peut-être pas la meilleure façon de déterminer ce qui se passe. Comptez le nombre total d'octets reçus et comparez aux octets envoyés (assurez-vous de compter les octets envoyés également). S'ils sont différents, il se passe quelque chose de bizarre que je vais devoir demander à l'équipe de Winsock. Si elles sont identiques, votre analyse syntaxique de chaîne est rompue pour les chaînes qui couvrent plusieurs mémoires tampons. –

+0

Compter maintenant les octets - merci – Nosrama

+0

C'était la réponse la plus informative et m'a aidé à exclure TCP/IP et winsocks comme problème. – Nosrama

1

Combien de temps sont chacune des chaînes? Si elles ne sont pas exactement 1024 octets, elles seront fusionnées par la pile TCP/IP distante en un seul grand flux, dont vous lisez les gros blocs dans votre appel de réception. Par exemple, l'utilisation de trois appels Send pour envoyer «A», «B» et «C» arrivera très probablement à votre client distant sous la forme «ABC» (comme la pile distante ou votre propre pile mettra le tampon en mémoire). octets jusqu'à ce qu'ils soient lus). Si vous avez besoin que chaque chaîne vienne sans qu'elle soit fusionnée avec d'autres chaînes, regardez dans un "protocole" avec un identificateur pour montrer le début et la fin de chaque chaîne, ou configure the socket pour éviter la mise en mémoire tampon et la combinaison de paquets.

+0

Bon appel, Socket.NoDelay = true est meilleur que Socket.SetSocketOption avec l'option NoDelay, faites-le de cette façon au lieu de la façon dont j'ai suggéré dans ma réponse en utilisant Socket.SetSocketOption –

+0

L'utilisation de 'NoDelay' n'est pas un correctif correct. Il ne garantit pas que le destinataire verra les données dans les mêmes groupes dans lesquels il a été envoyé, et pour aggraver les choses, il peut avoir pour effet de diminuer considérablement l'efficacité de l'utilisation du réseau. –

3

Ce n'est certainement pas la faute de TCP. Le protocole TCP garantit une livraison en une seule fois.

Quelles sont les chaînes manquantes? Je parierais que ce sont les derniers; essayez de tirer de la fin de l'envoi. De plus, votre «protocole» ici (je parle du protocole de couche d'application que vous inventez) fait défaut: vous devriez envisager d'envoyer le nombre d'objets et/ou leur longueur pour que le récepteur sache quand il est réellement fini de les recevoir.

+0

OK, 2 downvotes ... qu'est-ce que je me trompe ici? – DarkSquid

+1

Il n'y a rien de tel que le vidage de la fin de l'envoi avec Socket.Send(). Vous pouvez le forcer à envoyer un drapeau PSH qui demande au récepteur d'arrêter de mettre en mémoire tampon et d'envoyer tout ce qu'il contient dans la mémoire tampon de l'application, mais c'est à peu près tout ce qui est difficile à faire. Si les chaînes sont "manquantes", la seule possibilité est qu'elles soient sauvegardées dans winsock et que vous ne les lisez pas pour une raison quelconque. En outre, si le tampon de winsock est dépassé avant de le lire et qu'il ne peut pas tamponner une nouvelle entrée, il enverra généralement un paquet RST à l'expéditeur –

+0

Et je ne vous ai pas downvote, je suggère simplement un problème possible avec votre réponse Tout le reste à ce sujet est bon –

4

Eh bien il y a une chose ne va pas avec votre code pour commencer, si vous comptez le nombre d'appels à Receive qui complète: vous semblez être en supposant que vous verrez le plus grand nombre Receive appels terminent que vous avez fait Send appels . TCP est un protocole basé sur les flux - vous ne devriez pas vous soucier des paquets individuels ou des lectures; vous devriez vous préoccuper de lire les données, en espérant que parfois vous n'obtiendrez pas un message entier dans un paquet et parfois vous pourriez recevoir plus d'un message en une seule lecture. (Une lecture peut également ne pas correspondre à un paquet.)

Vous devez soit préfixer chaque méthode avec sa longueur avant l'envoi, soit en avoir délimité un entre les messages.

+0

Toutes mes excuses - J'ai trop simplifié mon exemple de code - Je ne compte pas les appels de réception terminés. Les chaînes sont délimitées et je compte le nombre reçu dans chaque tampon, en comptant ceux qui se répartissent sur 2 appels à recevoir. – Nosrama

+3

Dans ce cas, veuillez poster un programme court mais complet qui démontre le problème. –

+0

... afin que nous puissions répondre à votre problème sans avoir besoin de recourir à nos boules de cristal;) –

Questions connexes