2016-11-25 8 views
0

J'ai une simple question concernant l'ordre des messages envoyés et reçus via les classes TCP, je ne trouve pas de réponse à 100% et mon anglais ne va pas assez bien.TCPClient et TCPListener - NetworkStream - ordre des messages

Si j'ai l'exemple suivant:

Serveur:

 IPAddress IP = IPAddress.Parse("127.0.0.1"); 
     int Port = 13000; 

     TcpListener Server = new TcpListener(IP, Port); 
     TcpClient Client = Server.AcceptTcpClient(); 
     NetworkStream Stream = Client.GetStream(); 

     Stream.Write(Buffer1, 0, 4); 
     //random time 
     Stream.Write(Buffer2, 0, 4); 
     //random time 
     Stream.Write(Buffer3, 0, 4); 

et Client: J'ai

 TCPClient Client = new TcpClient("127.0.0.1", 13000); 
     NetworkStream Stream = Client.GetStream(); 

     Stream.Read(A, 0, 4); 
     //random time 
     Stream.Read(B, 0, 4); 
     //random time 
     Stream.Read(C, 0, 4); 

Est-il sûr à 100% que A = buffer1, B = buffer2, C = Buffer3?

+0

Non! Vous devez vérifier la valeur de retour de 'Stream.Read' pour voir combien d'octets vous avez réellement. TCP est * ordonné * (ce qui signifie que vous avez la garantie de recevoir des octets dans l'ordre où vous les avez envoyés) mais aussi * basé sur les flux * (ce qui signifie que le paquet d'octets n'est pas garanti). Pour le rendre plus explicite: TCP n'a pas de messages. Si vous voulez des messages, vous devrez les construire vous-même (le préfixer avec une longueur est l'approche la plus courante). Consultez n'importe quel tutoriel sur le code réseau pour voir comment écrire une boucle de réception appropriée. –

+0

J'ai déjà mis en œuvre ce préfixe le premier 4B de mon message est la longueur du message, donc de l'autre côté, j'ai lu le premier 4B, puis définissez NumberOfBytesToRead à ce nombre pour une autre lecture. Je voulais juste confirmer l'ordre des messages auxquels vous avez probablement répondu dans la première partie de votre commentaire :) –

+0

Attention: vous n'avez même pas la garantie d'obtenir 4 octets si vous demandez 4 octets (ils peuvent être répartis entre les paquets), donc même en lisant la longueur doit être fait dans une boucle jusqu'à ce que vous ayez ces 4 octets. Et puis vous aurez besoin de boucle à nouveau pour obtenir le message complet. Il est probablement l'erreur la plus courante dans la programmation réseau, et particulièrement sournoise car ce type de code peut très bien fonctionner par accident ou dans un environnement de test, puis échouer en production. –

Répondre

0

Il n'y a aucune garantie que pour chaque opération NetworkStream.Write, les opérations NetworkStream.Read correspon- dant aux opérations qui renvoient les données exactes qui ont été écrites dans le flux. Voici un exemple simple de connexion TcpListner et TcpClient:

public class NetworkUtils{ 

    //Client 
    TcpClient client = null; 
    int port = 40555; 
    string serverIpAddress = "127.0.0.1"; 
    public Mutex mut = new Mutex(); 
    int byteToExpecting = 0; 
    int savedBufferOffset = 0; 
    Byte[] saveDataBuffer = new Byte[20000]; 
    NetworkStream stream; 

    public string ServerIpAddress 
    { 
     get { return serverIpAddress; } 
     set { serverIpAddress = value;} 
    } 

    string lastSentMsg = String.Empty; 
    public string LastSentMsg 
    { 
     get { return lastSentMsg; } 
     set { lastSentMsg = value;} 
    } 

    //Server 
    string clientMsg = String.Empty; 
    public string ClientMsg 
    { 
     get { return clientMsg; } 
    } 
    public void ClearClientMsg() 
    { 
     clientMsg = String.Empty; 
    } 

    TcpListener server=null; 

    private string errMsg = String.Empty; 
    public string ErrMsg 
    { 
     get { return errMsg; } 
     set { errMsg = value;} 
    } 

    void ConnectToServer() 
    { 
     client = new TcpClient(serverIpAddress, port); 
    } 

    public bool ClientSendMsg(string message) 
    { 
     try{ 

      ConnectToServer(); 

      Byte[] lengthByteArr = IntToByteArr(message.Length); 
      client.GetStream().Write(lengthByteArr, 0, lengthByteArr.Length); 

      Byte[] data = Encoding.ASCII.GetBytes(message); 
      client.GetStream().Write(data, 0, data.Length); 

      client.GetStream().Close(); 
     } 
     catch (Exception e) 
     { 
      errMsg = e.Message; 
     } 

     return errMsg.Length == 0; 
    } 

    public bool LaunchServer() { 
     try { 
      IPAddress localAddr = IPAddress.Parse("127.0.0.1"); 
      server = new TcpListener(localAddr, port); 
      server.Start(); 
      ListenToClients(); 
     } 
     catch(Exception e) 
     { 
      server.Stop(); 
     } 

     return errMsg.Length == 0; 
    } 

    void ProcessInformation(IAsyncResult result) 
    { 
     try{ 

      TcpClient client; 
      client = server.EndAcceptTcpClient(result); 
      stream = client.GetStream(); 
      stream.BeginRead(saveDataBuffer, 0, sizeof(Int32), new AsyncCallback(callbackGetHeadrer), null); 
      ListenToClients(); 
     } 
     catch(Exception e) 
     { 
      errMsg = e.Message; 
      server.Stop(); 
     } 
    } 

    void callbackGetHeadrer (IAsyncResult asyncResult) { 
     int lenToRead = stream.EndRead(asyncResult); 

     savedBufferOffset = 0; 
     byteToExpecting = ByteArrToInt (saveDataBuffer); 
     saveDataBuffer = new byte[byteToExpecting]; 
     stream.BeginRead (saveDataBuffer, 0, byteToExpecting, callback, null); 
    } 

    void callback (IAsyncResult asyncResult) { 

     int lenToRead = stream.EndRead(asyncResult); 

     byteToExpecting -= lenToRead; 
     savedBufferOffset += lenToRead; 

     /*No one is gurentee that the 'lenToRead' will be correspanding to NetworkStream.Write execution order. 
     We need to keep read from the stream until we will get waht we are expecting accrding 'byteToExpecting' 
     So here we are keep calling 'stream.BeginRead'.*/ 
     if (byteToExpecting > 0) { 
      stream.BeginRead (saveDataBuffer, savedBufferOffset, byteToExpecting, callback, null); 
     } 
     else{ 
      mut.WaitOne(); 
      clientMsg = System.Text.Encoding.ASCII.GetString(saveDataBuffer,0, saveDataBuffer.Length); 
      mut.ReleaseMutex(); 

      savedBufferOffset = 0; 
      stream.Close(); 
      client.Close(); 
     } 
    } 

    bool ListenToClients() 
    { 
     try{ 
      server.BeginAcceptTcpClient(new AsyncCallback(ProcessInformation), server); 
     } 
     catch(Exception e) 
     { 
      errMsg = e.Message; 
      server.Stop(); 
     } 

     return errMsg.Length == 0; 
    } 

    public Byte[] IntToByteArr(Int32 intValue) 
    { 
     byte[] intBytes = BitConverter.GetBytes(intValue); 

     if (BitConverter.IsLittleEndian) 
      Array.Reverse(intBytes); 
     return intBytes; 
    } 

    public Int32 ByteArrToInt(Byte[] intByteArr) 
    { 
     Int32 Int32_NUM_OF_BYTES = 4; 
     Byte[] buffer = new Byte[Int32_NUM_OF_BYTES]; 

     for (int i = 0; i < Int32_NUM_OF_BYTES; ++i) 
      buffer [i] = intByteArr [i]; 

     if (BitConverter.IsLittleEndian) 
      Array.Reverse (buffer); 

     return BitConverter.ToInt32 (buffer, 0); 
    } 
} 

Notez que responsable de « callbackGetHeadrer » obtenir la taille des données que nous nous attendrons à recevoir. plus tard, nous continuons à lire le flux en utilisant 'stream.BeginRead' jusqu'à ce que nous obtenions ce que nous attendons, en ce qui concerne la séquence de l'opération 'stream.Write'.

+1

Oui, vous avez raison, j'ai utilisé quelque chose de similaire. Btw à partir de .NET 4.5, vous pouvez utiliser async await. NetworkStream a de nouvelles méthodes comme '.ReadAsync' et' .WriteAsync' qui sont beaucoup plus faciles à utiliser que '' .BeginRead'' –