2015-12-13 2 views
2

Я пытаюсь создать TCP-сервер & Клиент, который будет иметь постоянное соединение, чтобы сервер и клиент в любой момент времени могли уведомлять друг друга о некоторых «событиях» (так нажмите вместо опроса).C# TcpClient, читающий несколько сообщений по постоянному соединению

У меня почти все работает, клиенты могут подключаться, соединение остается открытым, а клиент и сервер могут писать &, прочитанные из потока tcp.

Проблема с чтением, я определил границу сообщения, сначала отправив 8 байтов, которые содержат длину сообщения. После того, как я его получил, сообщения длины x читаются и возникает событие.

Это все работает нормально, но после того, как сообщение было прочитано, я хочу, чтобы «ожидание stream.ReadAsync» ожидало новые входящие данные, но он продолжает циклизацию (и возвращает 0 данных) вместо ожидания, вызвав 100% -ное использование процессора.

Есть ли способ сказать «Сбросить» поток, чтобы он снова начал ждать, как это было первоначально.

Это код для моего tcpclient (используется для отправки и получения), вы можете перейти к методу RunListener, я не думаю, что остальное имеет значение.

public class SSLTcpClient : IDisposable { 
     /** 
     * Public fields 
     */ 
     public SslStream SslStream { get; private set; } 

     /** 
     * Events 
     */ 
     public ConnectionHandler connected; 
     public ConnectionHandler disconnected; 
     public DataTransfer dataReceived; 

     /** 
     * Constructors 
     */ 
     public SSLTcpClient() { } 
     public SSLTcpClient(TcpClient pClient, X509Certificate2 pCert) { 
      SslStream = new SslStream(
       pClient.GetStream(), 
       false, 
       new RemoteCertificateValidationCallback(
        delegate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { 
         return true; 
        } 
       ), 
       new LocalCertificateSelectionCallback(
        delegate(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers) { 
         return new X509Certificate2(pCert); 
        } 
       ) 
      ); 

      try { 
       SslStream.AuthenticateAsServer(pCert, true, SslProtocols.Tls, true); 
      } catch (AuthenticationException) { 
       pClient.Close(); 
       return; 
      } 

      Thread objThread = new Thread(new ThreadStart(RunListener)); 
      objThread.Start(); 

      if (connected != null) { 
       connected(this); 
      } 
     } 

     /** 
     * Connect the TcpClient 
     */ 
     public bool ConnectAsync(IPAddress pIP, int pPort, string pX509CertificatePath, string pX509CertificatePassword) { 
      TcpClient objClient = new TcpClient(); 
      try { 
       if(!objClient.ConnectAsync(pIP, pPort).Wait(1000)) { 
        throw new Exception("Connect failed"); 
       }; 
      } catch (Exception) { 
       return false; 
      } 
      X509Certificate2 clientCertificate; 
      X509Certificate2Collection clientCertificatecollection = new X509Certificate2Collection(); 
      try { 
       clientCertificate = new X509Certificate2(pX509CertificatePath, pX509CertificatePassword); 
       clientCertificatecollection.Add(clientCertificate); 
      } catch(CryptographicException) { 
       objClient.Close(); 
       return false; 
      } 

      SslStream = new SslStream(
       objClient.GetStream(), 
       false, 
       new RemoteCertificateValidationCallback(
        delegate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { 
         return true; 
        } 
       ), 
       new LocalCertificateSelectionCallback(
        delegate(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers) { 
         var cert = new X509Certificate2(pX509CertificatePath, pX509CertificatePassword); 
         return cert; 
        } 
       ) 
      ); 

      try { 
       SslStream.AuthenticateAsClient(pIP.ToString(), clientCertificatecollection, SslProtocols.Tls, false); 
      } catch (AuthenticationException) { 
       objClient.Close(); 
       return false; 
      } 

      Thread objThread = new Thread(new ThreadStart(RunListener)); 
      objThread.Start(); 

      if (connected != null) { 
       connected(this); 
      } 
      return true; 
     } 

     /** 
     * Reading 
     */ 
     private async void RunListener() { 
      try { 
       while (true) { 
        byte[] bytes = new byte[8]; 
        await SslStream.ReadAsync(bytes, 0, (int)bytes.Length); 

        int bufLenght = BitConverter.ToInt32(bytes, 0); 
        if (bufLenght > 0) { 
         byte[] buffer = new byte[bufLenght]; 
         await SslStream.ReadAsync(buffer, 0, bufLenght); 

         if (dataReceived != null) { 
          dataReceived(this, buffer); 
         } 
        } 
       } 
      } catch (Exception) { 
       Dispose(); 
      } 
     } 

     /** 
     * Writing 
     */ 
     public bool Send(byte[] pData) { 
      try { 
       byte[] lenght = BitConverter.GetBytes(pData.Length); 
       Array.Resize(ref lenght, 8); 

       SslStream.Write(lenght); 
       if (!SslStream.WriteAsync(pData, 0, pData.Length).Wait(1000)) { 
        throw new Exception("Send timed out"); 
       } 
      } catch (Exception) { 
       Dispose(); 
       return false; 
      } 
      return true; 
     } 

     public bool Send(string pData) { 
      byte[] bytes = System.Text.Encoding.UTF8.GetBytes(pData); 
      return Send(bytes); 
     } 

     /** 
     * Shutdown 
     */ 
     public void Dispose() { 
      SslStream.Close(); 
      if (disconnected != null) { 
       disconnected(this); 
      } 
     } 
    } 
+2

На первый взгляд вы называете 'ReadAsync', не глядя на' int', он возвращается. Это всегда ошибка кодирования. Перейдите к [документации] (https://msdn.microsoft.com/en-us/library/hh137813 (v = vs.110) .aspx) * «Задача, представляющая операцию асинхронного чтения. Значение TResult параметр содержит общее количество байтов, считанных в буфере.** Значение результата может быть меньше количества запрошенных байтов **, если количество доступных в настоящий момент байтов меньше запрошенного числа или оно может быть 0 (ноль), если конец потока достигнут. "* –

+1

Я чувствую себя таким глупым сейчас, я закончил поток, мое тестовое приложение, которое я сделал, вышло после отправки 1 сообщения. По крайней мере, это было простое исправление, спасибо за указание на ошибку. – Crazy

ответ

1

То, как вы читаете 4 или 8 байт, неверно. Вам нужно зациклиться до тех пор, пока вы их не получите. Вы можете получить 1.

Вы здесь и в других местах, что будете читать сумму, которую вы хотели. Вы прочтете хотя бы один байт или ноль, если соединение было отключено удаленной стороной.

Возможно, вы должны использовать BinaryReader для абстрагирования от цикла.

Кроме того, вам необходимо очистить ресурсы. Почему вы не обворачиваете их в using? Все вызовы Close небезопасны и не нужны.

Также я не понимаю, почему здесь нужны исключения для потока управления. Отправляй это.

0

Just 2 мысли, которые, мы надеемся, помогут улучшению вашего кода, но не имеют никакого ответа на ваш первоначальный вопрос:

  • Вы отправляете 8 байт указывает на следующую длину полезной нагрузки, но используют только 4 из их в следующем вызове BitConverter.ToInt32, поэтому достаточно 4 байтов.
  • Что произойдет, если передача будет срезана с другой стороны? На мой взгляд, вы не можете определить, что данные, которые вы получили, недействительны. Возможно, создание чего-то вроде небольшого протокола низкого уровня поможет, например. 4 bytes raw data length, а затем raw data itselft, а затем some bytes of checksum (что позволит проверить, правильно ли переданы данные, полученные вами).
+1

- Вы правы в 8 байтах, Я изменил его в 4 байта. - как и для конца передачи, над tcpclient реализовано средство keep-alive, которое должно обрабатывать это (все еще работающее над этим atm), CRC в конце является хорошим идея, все еще думала о том, как я проверил данные – Crazy

Смежные вопросы