2017-03-07 3 views
2

Dans mon application, chaque paquet a une longueur de 2 octets au démarrage. Cependant, après un certain temps, l'application commence à recevoir une longueur inférieure à zéro. Dans le client synchrone tout fonctionne correctement, mais c'est trop lent. Je suis sûr à 100% dans le serveur que tout est correct.Lire des données de manière asynchrone à partir de NetworkStream avec une quantité énorme de paquets

Connect:

public void Connect(IPAddress ip, int port) 
    { 
     tcpClient.Connect(ip, port); 
     stream = tcpClient.GetStream(); 
     byte[] len_buffer = new byte[2]; 
     stream.BeginRead(len_buffer, 0, len_buffer.Length, OnDataRead, len_buffer); 
    } 

OnDataRead:

private void OnDataRead(IAsyncResult ar) 
    { 
      byte[] len = ar.AsyncState as byte[]; 
      int length = BitConverter.ToInt16(len, 0); 
      byte[] buffer = new byte[length]; 

      int remaining = length; 
      int pos = 0; 
      while (remaining != 0) 
      { 
       int add = stream.Read(buffer, pos, remaining); 
       pos += add; 
       remaining -= add; 
      } 
      Process(buffer); 
      len = new byte[2]; 

      stream.EndRead(ar); 
      stream.BeginRead(len, 0, len.Length, OnDataRead, len); 
    } 
+2

Même avec juste un tampon de longueur 2, vous n'êtes pas sûr qu'un seul appel à 'Read' (ou' BeginRead' ici) aura pour résultat la lecture de 2 octets. Vous devez toujours vérifier le résultat et émettre d'autres lectures si vous avez besoin de lire un nombre particulier d'octets. –

+0

Vous mélangez 'stream.Read 'synchrone et' stream.BeginRead' asynchrone. Cela sonne quelques cloches d'alarme ici ... –

Répondre

1

Comme je peux voir, vous mélanger synchronious et asynchronious. C'est une mauvaise pratique.

Qu'est-ce que vous voulez est quelque chose comme:

var header = ReadHeader(); // 2 bytes 
var data = ReadData(header.DataSize); 

Je ne pas utiliser le flux réseau, mais .... Voici un exemple de mon async SocketReader:

public static class SocketReader 
{ 
    // This method will continues read until count bytes are read. (or socket is closed) 
    private static void DoReadFromSocket(Socket socket, int bytesRead, int count, byte[] buffer, Action<ArraySegment<byte>> endRead) 
    { 
     // Start a BeginReceive. 
     try 
     { 
      socket.BeginReceive(buffer, bytesRead, count - bytesRead, SocketFlags.None, (asyncResult) => 
      { 
       // Get the bytes read. 
       int read = 0; 
       try 
       { 
        // if this goes wrong, the read remains 0 
        read = socket.EndReceive(asyncResult); 
       } 
       catch (ObjectDisposedException) { } 
       catch (Exception exception) 
       { 
        Trace.TraceError(exception.Message); 
       } 


       // if zero bytes received, the socket isn't available anymore. 
       if (read == 0) 
       { 
        endRead(new ArraySegment<byte>(buffer, 0, 0)); 
        return; 
       } 

       // increase the bytesRead, (position within the buffer) 
       bytesRead += read; 

       // if all bytes are read, call the endRead with the buffer. 
       if (bytesRead == count) 
        // All bytes are read. Invoke callback. 
        endRead(new ArraySegment<byte>(buffer, 0, count)); 
       else 
        // if not all bytes received, start another BeginReceive. 
        DoReadFromSocket(socket, bytesRead, count, buffer, endRead); 

      }, null); 
     } 
     catch (Exception exception) 
     { 
      Trace.TraceError(exception.Message); 
      endRead(new ArraySegment<byte>(buffer, 0, 0)); 
     } 
    } 

    public static void ReadFromSocket(Socket socket, int count, Action<ArraySegment<byte>> endRead) 
    { 
     // read from socket, construct a new buffer. 
     DoReadFromSocket(socket, 0, count, new byte[count], endRead); 
    } 

    public static void ReadFromSocket(Socket socket, int count, byte[] buffer, Action<ArraySegment<byte>> endRead) 
    { 
     // if you do have a buffer available, you can pass that one. (this way you do not construct new buffers for receiving and able to reuse buffers) 

     // if the buffer is too small, raise an exception, the caller should check the count and size of the buffer. 
     if (count > buffer.Length) 
      throw new ArgumentOutOfRangeException(nameof(count)); 

     DoReadFromSocket(socket, 0, count, buffer, endRead); 
    } 
} 

Utilisation:

SocketReader.ReadFromSocket(socket, 2, (headerData) => 
{ 
    if(headerData.Count == 0) 
    { 
     // nothing/closed 
     return; 
    } 

    // Read the length of the data. 
    int length = BitConverter.ToInt16(headerData.Array, headerData.Offset); 

    SocketReader.ReadFromSocket(socket, length, (dataBufferSegment) => 
    { 
     if(dataBufferSegment.Count == 0) 
     { 
      // nothing/closed 
      return; 
     } 

     Process(dataBufferSegment); 

     // extra: if you need a binaryreader.. 
     using(var stream = new MemoryStream(dataBufferSegment.Array, dataBufferSegment.Offset, dataBufferSegment.Count)) 
     using(var reader = new BinaryReader(stream)) 
     { 
      var whatever = reader.ReadInt32(); 
     } 
    } 
}); 

Vous pouvez optimiser le tampon de réception en passant un tampon (voir les surcharges)


Poursuit la réception:(réutilisation receivebuffer)

public class PacketReader 
{ 
    private byte[] _receiveBuffer = new byte[2]; 

    // This will run until the socket is closed.  
    public void StartReceiving(Socket socket, Action<ArraySegment<byte>> process) 
    { 
     SocketReader.ReadFromSocket(socket, 2, _receiveBuffer, (headerData) => 
     { 
      if(headerData.Count == 0) 
      { 
       // nothing/closed 
       return; 
      } 

      // Read the length of the data. 
      int length = BitConverter.ToInt16(headerData.Array, headerData.Offset); 

      // if the receive buffer is too small, reallocate it. 
      if(_receiveBuffer.Length < length) 
       _receiveBuffer = new byte[length]; 

      SocketReader.ReadFromSocket(socket, length, _receiveBuffer, (dataBufferSegment) => 
      { 
       if(dataBufferSegment.Count == 0) 
       { 
        // nothing/closed 
        return; 
       } 

       try 
       { 
        process(dataBufferSegment); 
       } 
       catch { } 

       StartReceiving(socket, process); 
      }); 
     }); 
    } 
} 

Utilisation:

private PacketReader _reader; 

public void Start() 
{ 
    _reader = new PacketReader(socket, HandlePacket); 
} 

private void HandlePacket(ArraySegment<byte> packet) 
{ 
    // do stuff..... 
} 
+0

Merci, je vais utiliser votre classe à coup sûr! Le probelm était ici 'int length = BitConverter.ToInt16 (len, 0);'. Cela devrait être "court". – Freshek

+0

Le stockage d'un 'short' dans un' int' n'est pas un problème. (Le code original était un int comme champ de datasize) –

+0

J'ai changé 'int' en' short' et il a magiquement commencé à fonctionner. – Freshek