Почему SslStream.Read всегда устанавливает TcpClient.Available в 0, а NetworkStream.Read этого не делает - PullRequest
2 голосов
/ 17 января 2020

У меня TcpClient клиент, подключенный к серверу, который отправляет сообщение обратно клиенту.

При чтении этих данных с помощью класса NetworkStream.Read я могу указать количество байтов, которое я хочу читать с помощью параметра count, который уменьшит TcpClient.Available на count после завершения чтения. Из документов :

count Int32
Максимальное количество байтов для чтения из текущего потока.

Например :

public static void ReadResponse()
{
    if (client.Available > 0) // Assume client.Available is 500 here
    {
        byte[] buffer = new byte[12]; // I only want to read the first 12 bytes, this could be a header or something
        var read = 0;

        NetworkStream stream = client.GetStream();
        while (read < buffer.Length)
        {
            read = stream.Read(buffer, 0, buffer.Length);
        }
    // breakpoint
    }
}

Считывает первые 12 байтов из 500, доступных на TcpClient в buffer, а проверка client.Available в точке останова даст (ожидаемый) результат 488 ( 500 - 12).

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

public static void ReadResponse()
{
    if (client.Available > 0) // Assume client.Available is 500 here
    {
        byte[] buffer = new byte[12]; // I only want to read the first 12 bytes, this could be a header or something
        var read = 0;

        SslStream stream = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);

        while (read < buffer.Length)
        {
            read = stream.Read(buffer, 0, buffer.Length);
        }
    // breakpoint
    }
}

Этот код будет читать первые 12 байтов в buffer, как и ожидалось. Однако при проверке client.Available в точке останова теперь будет получен результат 0.

Как и обычный NetworkStream.Read, документация для SslStream.Read гласит, что count указывает максимальное количество байтов для чтения.

count Int32
A Int32, которое содержит максимальное количество байтов для чтения из этого потока.

Хотя он читает только эти 12 байтов, и больше ничего, мне интересно, где остальные 488 байтов go.

В документах для SslStream или TcpClient я не смог найти ничего, указывающего, что использование SslStream.Read очищает поток или иным образом очищает client.Available. Какова причина для этого (и где это задокументировано)?


Существует этот вопрос , который запрашивает эквивалент TcpClient.Available, то есть не , что я и просил. Я хочу знать , почему это происходит, что там не рассматривается.

Ответы [ 2 ]

2 голосов
/ 17 января 2020

Помните, что SslStream может считывать большие порции из базового TcpStream сразу и буферизировать их внутренне, по соображениям эффективности или потому, что процесс дешифрования не работает побайтово и требует наличия блока данных, чтобы быть доступным , Поэтому тот факт, что ваш TcpClient содержит 0 доступных байтов, ничего не значит, потому что эти байты, вероятно, находятся в буфере внутри SslStream.


Кроме того, ваш код для чтения 12 байтов неверен, что может быть влияет на то, что вы видите.

Помните, что Stream.Read может вернуть меньше байт, чем вы ожидали. Последующие вызовы Stream.Read вернут количество прочитанных байтов во время этого вызова , а не общее.

Так что вам нужно что-то вроде этого:

int read = 0;
while (read < buffer.Length)
{
    int readThisTime = stream.Read(buffer, read, buffer.Length - read);
    if (readThisTime == 0)
    {
        // The end of the stream has been reached: throw an error?
    }
    read += readThisTime;
}
1 голос
/ 17 января 2020

Когда вы читаете из потока TLS, он перечитывает , поддерживая внутренний буфер данных, который еще предстоит расшифровать - или который уже расшифровал , но не расшифровал еще потребляется. Это распространенный подход, используемый в потоках, особенно когда они видоизменяют содержимое (сжатие, шифрование и т. Д. c), поскольку необязательно существует соотношение 1: 1 между размерами полезной нагрузки на входе и на выходе, и it может потребоваться прочитать целые кадры из источника - т.е. вы не можете просто прочитать 3 байта - API должен прочитать весь кадр (скажем, 512 байтов), расшифровать кадр , дать Вы 3, который вы хотели, и удерживайте оставшиеся 509, чтобы дать вам в следующий раз, когда вы спросите. Это означает, что ему часто нужно потреблять больше из источника (в данном случае сокета), чем дает вам.

Многие потоковые API также делают то же самое из соображений производительности, например, StreamReader перечитывание из базовый Stream и поддерживает внутренне как byteBuffer байтов, еще не декодированных, так и charBuffer декодированных символов, доступных для использования. Тогда ваш вопрос будет сопоставим с:

При использовании StreamReader я прочитал только 3 символа, но у моего Stream было 512 байтов; почему?

...