2016-04-17 14 views
2

リモートクライアントが何も送信しなかったときにSSLStreamでデータを返す問題があります。サーバーが新しいコマンドをリッスンしているときにこの問題が発生しています。 サーバーが新しい要求を受け取らない場合、ReadStream()関数はSSLStreamの読み取りタイムアウトのためにIOExceptionをキャッチする必要があります。問題は、sslStream.Read()が2回目にクライアントによって送信されなかった5バイトを読み取ったように実行されたときに発生します。だから、問題は、この順序で起こる:SSLStreamで無効なデータが読み取られました+ KB3147458 SSLStreamのバグ(?)

- > ReadMessage() - - > sslstream.Read() - >タイムアウト> ReadMessage() - > sslstream.Read() - 予想

として捉え>タイムアウト例外キャッチされない例外は、クライアントが

何も送信しませんでしたにもかかわらずさえ読ん5バイト - > ReadMessage() - > sslstream.Read() - >期待通りにタイムアウト例外をキャッチ

- > ReadMessage() - >をsslstream.Read() - >クライアントが何も送信しなかったにもかかわらず、5バイトが読み取られなかったタイムアウト例外...

など..

public void ClientHandle(object obj) 
    { 
     nRetry = MAX_RETRIES; 

     // Open connection with the client 
     if (Open() == OPEN_SUCCESS) 
     { 
      String request = ReadMessage(); 
      String response = null; 

      // while loop for the incoming commands from client 
      while (!String.IsNullOrEmpty(request)) 
      { 
       Console.WriteLine("[{0}] {1}", RemoteIPAddress, request); 

       response = Execute(request); 

       // If QUIT was received, close the connection with the client 
       if (response.Equals(QUIT_RESPONSE)) 
       { 
        // Closing connection 
        Console.WriteLine("[{0}] {1}", RemoteIPAddress, response); 

        // Send QUIT_RESPONSE then return and close this thread 
        SendMessage(response); 
        break; 
       } 

       // If another command was received, send the response to the client 
       if (!response.StartsWith("TIMEOUT")) 
       { 
        // Reset nRetry 
        nRetry = MAX_RETRIES; 

        if (!SendMessage(response)) 
        { 
         // Couldn't send message 
         Close(); 
         break; 
        } 
       } 


       // Wait for new input request from client 
       request = ReadMessage(); 

       // If nothing was received, SslStream timeout occurred 
       if (String.IsNullOrEmpty(request)) 
       { 
        request = "TIMEOUT"; 
        nRetry--; 

        if (nRetry == 0) 
        { 
         // Close everything 
         Console.WriteLine("Client is unreachable. Closing client connection."); 
         Close(); 
         break; 
        } 
        else 
        { 
         continue; 
        } 
       } 
      } 

      Console.WriteLine("Stopped"); 
     } 
    } 



    public String ReadMessage() 
    { 
     if (tcpClient != null) 
     { 
      int bytes = -1; 
      byte[] buffer = new byte[MESSAGE_SIZE]; 

      try 
      { 
       bytes = sslStream.Read(buffer, 0, MESSAGE_SIZE); 
      } 
      catch (ObjectDisposedException) 
      { 
       // Streams were disposed 
       return String.Empty; 
      } 
      catch (IOException) 
      { 
       return String.Empty; 
      } 
      catch (Exception) 
      { 
       // Some other exception occured 
       return String.Empty; 
      } 

      if (bytes != MESSAGE_SIZE) 
      { 
       return String.Empty; 
      } 

      // Return string read from the stream 
      return Encoding.Unicode.GetString(buffer, 0, MESSAGE_SIZE).Replace("\0", String.Empty); 
     } 

     return String.Empty; 
    } 


    public bool SendMessage(String message) 
    { 
     if (tcpClient != null) 
     { 
      byte[] data = CreateMessage(message); 

      try 
      { 
       // Write command message to the stream and send it 
       sslStream.Write(data, 0, MESSAGE_SIZE); 
       sslStream.Flush(); 
      } 
      catch (ObjectDisposedException) 
      { 
       // Streamers were disposed 
       return false; 
      } 
      catch (IOException) 
      { 
       // Error while trying to access streams or connection timedout 
       return false; 
      } 
      catch (Exception) 
      { 
       return false; 
      } 

      // Data sent successfully 
      return true; 
     } 

     return false; 
    } 

    private byte[] CreateMessage(String message) 
    { 
     byte[] data = new byte[MESSAGE_SIZE]; 

     byte[] messageBytes = Encoding.Unicode.GetBytes(message); 

     // Can't exceed MESSAGE_SIZE parameter (max message size in bytes) 
     if (messageBytes.Length >= MESSAGE_SIZE) 
     { 
      throw new ArgumentOutOfRangeException("message", String.Format("Message string can't be longer than {0} bytes", MESSAGE_SIZE)); 
     } 

     for (int i = 0; i < messageBytes.Length; i++) 
     { 
      data[i] = messageBytes[i]; 
     } 
     for (int i = messageBytes.Length; i < MESSAGE_SIZE; i++) 
     { 
      data[i] = messageBytes[messageBytes.Length - 1]; 
     } 

     return data; 
    } 

非常に同じReadMessage()、のSendMessage()とCreateMessage()関数は、サーバーにメッセージを送信するためにクライアントによっても使用されています。 MESSAGE_SIZE定数も同じで、2048に設定されています。

+1

クリスタルボールは、あなたがMESSAGE_SIZEを変更し、サーバが使用するDLLのコピーを更新するのを忘れていることを言うことができます。コードに基本的なバグがあります.SSlStreamのMSDN記事のコードサンプルを見て、ReadMessage()メソッドのdo-whileループに注意してください。あなたには欠けています。 –

+0

MESSAGE_SIZE定数は同じで、2048に設定されています。問題は、sslStreamを再利用していることがわかりました。 MSDNの記事によると、SSLStreamはタイムアウト例外が発生した後にgargabeを返します。 –

+0

はい、ReadMessage()は、すべてのバイトが正しく読み取られることを確認するdo-whileループを必要とします。 –

答えて

2

問題は、タイムアウト後にSSLStreamを再使用したことです。そこで、nRetry変数を削除してより長いタイムアウトを設定するだけで問題を解決しました。関連のMSDNの記事はSSLStreamがタイムアウト例外(https://msdn.microsoft.com/en-us/library/system.net.security.sslstream(v=vs.110).aspx)の後にゴミを返すことを言う:

SslStream 1は、内側ストリームからスローされる他のIOExceptionと一緒にタイムアウトが呼び出し元によって致命的なものとして扱われることを前提としてい。タイムアウト後にSslStreamインスタンスを再利用すると、ゴミが返されます。これらの場合、アプリケーションはSslStreamを閉じて例外をスローする必要があります。

もう1つの問題は、Windows Update KB3147458(Windows 10 April'sの更新)がRead関数の動作の何かを変更することです。これはSSLStream実装の何かが変更されたように見え、今度は1バイトごとに2バイト、1バイト、残りのバイトのデータを返します。実際には、MSDNドキュメントは、Read()関数が要求されたバイトを1ステップで返し、提供された例が正確なバイト数を読み取るためにdo-whileループを使用するとは言いません。だから私は、Read()関数は、一度に正確に要求されたバイト数を読み取ることを保証しない、より多くの読み取りの反復が必要かもしれないと思います。

SSLstreamが正常に動作していないため、壊れていません。あなたは注意を払い、do-whileループを使い、すべてのバイトが正しく読み込まれていることを確認するだけです。

ここに示すコードを変更して、私が持っていたバグに対処しました。タイムアウト後に読むの5つのバイトを()を返すSslStreamに関しては

public String ReadMessage() 
    { 
     if (tcpClient != null) 
     { 
      int bytes = -1, offset = 0; 
      byte[] buffer = new byte[MESSAGE_SIZE]; 

      try 
      { 
       // perform multiple read iterations 
       // and check the number of bytes received 
       while (offset < MESSAGE_SIZE) 
       { 
        bytes = sslStream.Read(buffer, offset, MESSAGE_SIZE - offset); 
        offset += bytes; 

        if (bytes == 0) 
        { 
         return String.Empty; 
        } 
       } 
      } 
      catch (Exception) 
      { 
       // Some exception occured 
       return String.Empty; 
      } 

      if (offset != MESSAGE_SIZE) 
      { 
       return String.Empty; 
      } 

      // Return string read from the stream 
      return Encoding.Unicode.GetString(buffer, 0, MESSAGE_SIZE).Replace("\0", String.Empty); 
     } 

     return String.Empty; 
    } 
+1

"関連するMSDN記事では、SSLStreamがタイムアウト例外の後にガーベジを返すと報告しています。 >このMSDN記事はどこですか? – Pol

+0

クライアントが1バイト後に閉じるsslstream.writeの場合と同じ動作をしているようです。私はクライアントを書き換えることができませんが、そのための推奨される回避策はありますか? – SeeCoolGuy

+0

'Read(byte []、int、int)'は技術的には壊れていませんが、 'Peek()'/'Read()'のコンボの問題は '' StreamReader' ' –

1

、これはSslStreamクラスが優雅に基本となるストリームから任意のIOExceptionが処理しないためであり、前述のように、これは文書化されています。

SslStreamは、一方が内側ストリームからスローされる任意の他のIOExceptionと共にタイムアウトは、その発信者が致命的なものとして扱われることを前提としています。タイムアウト後にSslStreamインスタンスを再利用すると、ゴミが返されます。これらの場合、アプリケーションはSslStreamを閉じて例外をスローする必要があります。

https://msdn.microsoft.com/en-us/library/system.net.security.sslstream(v=vs.110).aspx

ただし、機能の損失なし(一見)で、無害タイムアウト例外をキャッチし、抑制TcpのNetworkStreamとSslStreamの間に位置するラッパークラスを作成することで問題を解決することができます。

本の完全なコードは、あなたの答えはすでにこれを正しく修正し、各読む()のペイロードの一部だけを返す読む()メソッドに関しては、ここでhttps://stackoverflow.com/a/48231248/8915494

、同様のスレッドで私の答えであります。これはSslStreamの "最近の"動作ですが、残念なことにすべてのネットワーキングで期待される動作であり、完全なパケットが得られるまでフラグメントを保存するためにはすべてのコードでバッファを作成する必要があります。たとえば、データが1500バイト(ほとんどのイーサネット・アダプターの最大パケット・サイズ、イーサネット・トランスポートを前提としています)を超える場合、データを複数の部分で受信する可能性が非常に高く、自分で再構成する必要があります。

ホープこれは

関連する問題