2012-01-21 2 views
1

I ont problème avec OutOfMemoryException intermittent, sur la ligneTcpClient lire OutOfMemoryException

buffer = new byte [metaDataSize];

(Sous // Lire les méta-données de la commande.)

Est-ce que cela veut dire que j'essaie de lire le message complet alors qu'une partie seulement de celui-ci a été reçue? Dans le cas, quelle est une manière fiable de gérer cela? Btw, j'ai besoin de messages de longueur variable, car la plupart sont très courts, alors que les messages occasionnels sont très volumineux. Dois-je joindre la taille complète du message devant le message? Pourtant, comment puis-je savoir combien le flux contient avant d'essayer de lire? (Comme il semble que la lecture échoue parfois lorsque vous essayez de lire une certaine longueur, comme je le fais actuellement)

public static Command Read(NetworkStream ns) 
    { 
     try 
     { 
       //Read the command's Type. 
       byte[] buffer = new byte[4]; 
       int readBytes = ns.Read(buffer, 0, 4); 
       if (readBytes == 0) 
        return null; 
       CommandType cmdType = (CommandType)(BitConverter.ToInt32(buffer, 0)); 

       //Read cmdID 
       buffer = new byte[4]; 
       readBytes = ns.Read(buffer, 0, 4); 
       if (readBytes == 0) 
        return null; 
       int cmdID = BitConverter.ToInt32(buffer, 0); 

       //Read MetaDataType 
       buffer = new byte[4]; 
       readBytes = ns.Read(buffer, 0, 4); 
       if (readBytes == 0) 
        return null; 
       var metaType = (MetaTypeEnum)(BitConverter.ToInt32(buffer, 0)); 

       //Read the command's MetaData size. 
       buffer = new byte[4]; 
       readBytes = ns.Read(buffer, 0, 4); 
       if (readBytes == 0) 
        return null; 
       int metaDataSize = BitConverter.ToInt32(buffer, 0); 

       //Read the command's Meta data. 
       object cmdMetaData = null; 
       if (metaDataSize > 0) 
       { 
        buffer = new byte[metaDataSize]; 

        int read = 0, offset = 0, toRead = metaDataSize; 
        //While 
        while (toRead > 0 && (read = ns.Read(buffer, offset, toRead)) > 0) 
        { 
         toRead -= read; 
         offset += read; 
        } 
        if (toRead > 0) throw new EndOfStreamException(); 

        // readBytes = ns.Read(buffer, 0, metaDataSize); 
        //if (readBytes == 0) 
        // return null; 
        // readBytes should be metaDataSize, should we check? 

        BinaryFormatter bf = new BinaryFormatter(); 
        MemoryStream ms = new MemoryStream(buffer); 
        ms.Position = 0; 
        cmdMetaData = bf.Deserialize(ms); 
        ms.Close(); 
       } 
       //Build and return Command 
       Command cmd = new Command(cmdType, cmdID, metaType, cmdMetaData); 

       return cmd; 
     } 
     catch (Exception) 
     { 

      throw; 
     } 

    } 

méthode Write:

public static void Write(NetworkStream ns, Command cmd) 
    { 
     try 
     { 

      if (!ns.CanWrite) 
       return; 

      //Type [4] 
      // Type is an enum, of fixed 4 byte length. So we can just write it. 
      byte[] buffer = new byte[4]; 
      buffer = BitConverter.GetBytes((int)cmd.CommandType); 
      ns.Write(buffer, 0, 4); 
      ns.Flush(); 

      // Write CmdID, fixed length [4] 
      buffer = new byte[4];     // using same buffer 
      buffer = BitConverter.GetBytes(cmd.CmdID); 
      ns.Write(buffer, 0, 4); 
      ns.Flush(); 

      //MetaDataType [4] 
      buffer = new byte[4]; 
      buffer = BitConverter.GetBytes((int)cmd.MetaDataType); 
      ns.Write(buffer, 0, 4); 
      ns.Flush(); 

      //MetaData (object) [4,len] 
      if (cmd.MetaData != null) 
      { 
       BinaryFormatter bf = new BinaryFormatter(); 
       MemoryStream ms = new MemoryStream(); 
       bf.Serialize(ms, cmd.MetaData); 

       ms.Seek(0, SeekOrigin.Begin); 

       byte[] metaBuffer = ms.ToArray(); 
       ms.Close(); 

       buffer = new byte[4]; 
       buffer = BitConverter.GetBytes(metaBuffer.Length); 
       ns.Write(buffer, 0, 4); 
       ns.Flush(); 

       ns.Write(metaBuffer, 0, metaBuffer.Length); 
       ns.Flush(); 

       if (cmd.MetaDataType != MetaTypeEnum.s_Tick) 
        Console.WriteLine(cmd.MetaDataType.ToString() + " Meta: " + metaBuffer.Length); 
      } 
      else 
      { 
       //Write 0 length MetaDataSize 
       buffer = new byte[4]; 
       buffer = BitConverter.GetBytes(0); 
       ns.Write(buffer, 0, 4); 
       ns.Flush(); 
      } 

     } 
     catch (Exception) 
     { 

      throw; 
     } 
    } 

VB.NET:

Private tcp As New TcpClient 
Private messenger As InMessenger  
Private ns As NetworkStream 

Public Sub New(ByVal messenger As InMessenger) 
    Me.messenger = messenger 
End Sub 

Public Sub Connect(ByVal ip As String, ByVal port As Integer) 

    Try 
     tcp = New TcpClient 


     Debug.Print("Connecting to " & ip & " " & port) 

     'Connect with a 5sec timeout 
     Dim res = tcp.BeginConnect(ip, port, Nothing, Nothing) 
     Dim success = res.AsyncWaitHandle.WaitOne(5000, True) 

     If Not success Then 
      tcp.Close() 

     Else 
      If tcp.Connected Then 
       ns = New NetworkStream(tcp.Client) 

       Dim bw As New System.ComponentModel.BackgroundWorker 
       AddHandler bw.DoWork, AddressOf DoRead 
       bw.RunWorkerAsync() 

      End If 
     End If 


    Catch ex As Exception 
     Trac.Exception("Connection Attempt Exception", ex.ToString) 
     CloseConnection() 
    End Try 
End Sub 


Private Sub DoRead() 

    Try 
     While Me.tcp.Connected 

      ' read continuously : 
      Dim cmd = CommandCoder.Read(ns) 

      If cmd IsNot Nothing Then 
       HandleCommand(cmd) 
      Else 
       Trac.TraceError("Socket.DoRead", "cmd is Nothing") 
       CloseConnection() 
       Exit While 
      End If 

      If tcp.Client Is Nothing Then 
       Trac.TraceError("Socket.DoRead", "tcp.client = nothing") 
       Exit While 
      End If 
     End While 
    Catch ex As Exception 
     Trac.Exception("Socket.DoRead Exception", ex.ToString()) 
     CloseConnection() 
     EventBus.RaiseErrorDisconnect() 
    End Try 

End Sub 

EDIT:

J'ai mis dans certains WriteLine, et a constaté que certains paquets envoyés sont reconnus avec la mauvaise taille du côté du récepteur. Ainsi, le métaDataSize qui devrait être 9544 pour un certain message, est lu comme 5439488, ou une valeur incorrecte similaire. Je supposons dans quelques occasions ce nombre est si grand qu'il provoque l'exception OutOfMemoryException.

Semble la réponse de Douglas peut être sur la marque (?), Je vais tester. Pour info: Le programme serveur (expéditeur) est construit comme "Any CPU", fonctionnait sous Windows 7 x64 pc. Alors que le client (récepteur) est construit comme x86, et (pendant ce test) a fonctionné sur XP. Mais doit également être codé pour fonctionner sur d'autres fenêtres x86 ou x64.

+0

cela signifie simplement que votre tas est plein ... – Yahia

+0

Utilisez-vous ceci pour communiquer avec un ordinateur potentiellement malveillant? AFAIK 'BinaryFormatter' ne devrait être utilisé que sur des données de confiance. – CodesInChaos

+0

Je code à la fois le client et l'application-serveur, et je ne lance des tests que sur mes propres ordinateurs. Mais certainement en production, tout ordinateur inconnu pourrait tenter de se connecter car le port est ouvert. – bretddog

Répondre

2

Vous devez faire attention au endianness de votre architecture, en particulier puisque le comportement de BitConverter dépend de l'architecture. En l'état, votre code échoue probablement lorsque vous transmettez des données entre des architectures d'endianness différentes. Imaginez, par exemple, un message dont la taille est de 241 octets. L'expéditeur - que nous supposerons être big-endian - indiquerait cette taille en envoyant une séquence d'octets de [0,0,0,241]. Cela sera correctement interprété comme 241 sur un récepteur big-endian, mais comme 4.043.309.056 (égal à 241 × 256) sur un petit-boutiste. Si vous essayez d'allouer un tableau d'octets de cette taille, vous obtiendrez probablement un OutOfMemoryException.

En supposant que votre flux entrant est toujours grand-boutiste, vous gérez en adaptant votre code pour inverser le tableau lorsque votre architecture est petit-boutiste:

buffer = new byte[4]; 
readBytes = ns.Read(buffer, 0, 4); 
if (readBytes == 0) 
    return null; 
if (BitConverter.IsLittleEndian) 
    Array.Reverse(buffer); 

Modifier: En réponse à un commentaire:

Vous devez corriger pour l'endianness chaque fois que vous allez utiliser la méthode BitConverter.ToInt32 pour convertir une séquence de 4 octets en nombre entier. Vous n'avez pas besoin de corriger pour l'endianness en utilisant le BinaryFormatter, car il gère l'endianness de manière transparente.Je suppose que l'endianness dépend de l'architecture physique de votre machine, mais je n'ai jamais étudié les détails. Pour être sûr, vous ne devriez jamais supposer une endianness particulière, indépendamment du fait que vous courez sur x86 ou x64.

Si vous êtes également responsable du code serveur, vous devez également y remédier. Par exemple, pour envoyer la valeur cmdID du serveur:

int cmdID = 22; // for the example 

byte[] buffer = BitConverter.GetBytes(cmdID); 
if (BitConverter.IsLittleEndian) 
    Array.Reverse(buffer); 
ns.Write(buffer, 0, 4); 
+0

Merci! J'ai ajouté quelques informations à la fin de la question. Donc, il semblerait encore ajouter ceci à la conversion conditionnelle comme vous le suggérez, après tous mes ns.Read devrait être suffisant? Et pour ma compréhension, cette exception serait-elle due à l'architecture du système d'exploitation ou à la construction du logiciel?Je veux dire, si je lance le client sur x64 pc, est-ce que l'endian-issue peut encore se produire? (sans votre solution, je veux dire) – bretddog

+0

Bien que ce soit un défaut dans son code, je ne pense pas que ce soit responsable du problème de l'OP. Les deux x86 et AMD64 sont peu endian. – CodesInChaos

+0

'BinaryFormatter' est une bête complètement différente. Avez-vous mélangé avec 'BinaryReader'? Envoyer quelque chose sur le réseau avec 'BinaryFormatter' ressemble à une mauvaise idée. – CodesInChaos

2

Vous parlez des paquets, mais ce n'est pas un concept exposé par TCP. TCP expose un flux d'octets, rien de plus. Peu importe le nombre d'appels Send. Il peut diviser un appel Send en plusieurs lectures, et fusionner plusieurs envois, ou un mélange de ceux-ci.

La valeur de retour de Read indique le nombre d'octets lus. Si cette valeur est supérieure à 0, mais inférieure à la longueur que vous avez passée à Read, vous avez moins d'octets que vous l'avez transmise. Votre code suppose que 0 ou length octets ont été lus. Ceci est une hypothèse invalide.

Votre code souffre également de problèmes endian, mais je pense que vos deux systèmes étaient peu endian, il est donc peu probable que cela cause vos problèmes actuels.


Si vous ne se soucient pas de blocage (votre code existant déjà blocs dans la boucle, donc ce n'est pas question supplémentaire), vous pouvez simplement utiliser un BinaryReader sur votre flux.

Il a des méthodes d'aide comme ReadInt32 qui prennent automatiquement soin des lectures partielles, et il utilise l'endianness fixe (toujours peu).

buffer = new byte[4]; 
readBytes = ns.Read(buffer, 0, 4); 
if (readBytes == 0) 
    return null; 
int cmdID = BitConverter.ToInt32(buffer, 0); 

devient:

int cmdId = reader.ReadInt32(); 

Il lancera une EndOfStreamException si elle rencontre inopinément la fin du cours d'eau, au lieu de retourner null.

+0

Un préfixe de longueur est une solution courante, oui. 'BinaryReader' vous évite d'écrire manuellement des boucles. – CodesInChaos

+0

Le code est définitivement faux et devrait être corrigé. Cela n'explique cependant pas le problème, les machines little-endian ne produisent que des valeurs trop petites. Il crée un nouvel octet [] pour chaque numéro afin que les données périmées ne puissent pas l'être non plus. 5439488 a cependant beaucoup de zéros. –

+0

@HansPassant il lit plusieurs valeurs successivement. Il est donc possible qu'il lise d'abord les chiffres les plus significatifs d'un nombre. Et commence alors à lire les chiffres les moins significatifs comme les chiffres les plus significatifs du nombre suivant. – CodesInChaos