2016-04-17 4 views
2

У меня возникла проблема с SSLStream, возвращающей некоторые данные, когда удаленный клиент ничего не отправил. У меня возникает эта проблема, когда сервер прослушивает новую команду. Если сервер не получает новый запрос, функция ReadMessage() должна перехватывать IOException из-за таймаута чтения SSLStream. Проблема возникает, когда sslStream.Read() выполняется во второй раз, кажется, что он считывает 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, которая использует сервер. В вашем коде есть фундаментальная ошибка, посмотрите пример кода в статье MSDN для SslStream, обратите внимание на цикл do-while в методе ReadMessage(). Не хватает вашего. –

+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 предполагает, что тайм-аут вместе с любой другой IOException, когда один выбрасываются из внутреннего потока будет рассматриваться как фатальный его вызывающим , Повторное использование экземпляра SslStream после таймаута возвращает мусор. Приложение должно закрыть SslStream и выдать исключение в этих случаях.

Другая проблема заключается в том, что обновление Windows KB3147458 (обновление от Windows 10 апреля) меняет что-то в поведении функции Read. Похоже, что что-то в реализации SSLStream изменилось, и теперь он возвращает данные в 2 части, 1 байт и остальные байты каждый раз. Фактически документ MSDN не говорит о том, что функция Read() вернет все запрошенные байты за один шаг, а предоставленный пример использует цикл do-while для чтения точного количества байтов. Поэтому я полагаю, что функция Read() не гарантирует, что вы должны прочитать точное запрошенное количество байтов одновременно, возможно, потребуется больше итераций на чтение.

SSLstream работает правильно, поэтому он НЕ БРОКЕН. Вам просто нужно обратить внимание и использовать цикл do-while и проверить правильность чтения всех байтов.

Я изменил код, как показано здесь, чтобы найти ошибки, которые у меня были.

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

Кажется, я получаю такое же поведение для sslstream.write, где клиент закрывается после 1 байта. Я не могу повторно написать клиент, есть ли способ обхода? – SeeCoolGuy

+0

'Read (byte [], int, int)' технически не сломлен, но я определенно вижу проблемы с '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '. –

1

В отношении SslStream возвращения пять байт на Read() после тайм-аута, это происходит потому, что класс 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

Что касается методы Read() возвращает только часть полезной нагрузки на каждый Read(), ваш ответ уже фиксирует это правильно , Хотя это «недавнее» поведение для SslStream, к сожалению, ожидается, что поведение для всей сети и всего кода потребует создания некоторой формы буфера для хранения фрагментов, пока вы не получите полный пакет. Например, если ваши данные превышают 1500 байт (максимальный размер пакета для большинства Ethernet-адаптеров, принимая Ethernet-транспорт), вы, скорее всего, получите данные в нескольких частях и должны их собрать самостоятельно.

Надеюсь, что это помогает

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