Страницы

Поиск по вопросам

суббота, 14 декабря 2019 г.

Почему нельзя использовать StreamReader вместе с NetworkStream?

#c_sharp #сокет #потоки_данных


В книге Албахари "C# 6.0 in a Nutshell" написано следующее:


  В действительности класс StreamReader абсолютно запрещено применять
  вместе с NetworkStream, даже если вы планируете вызывать только
  метод ReadLine. Причина в том, что класс StreamReader имеет буфер
  опережающего чтения, который может привести к чтению большего
  числа байтов, чем доступно в текущий момент, и бесконечному блокированию
  (или до возникновения тайм-аута сокета). Другие потоки, такие
  как FileStream, не страдают подобной несовместимостью с классом
  StreamReader, потому что они поддерживают определенный признак
  окончания, при достижении которого метод Read немедленно завершается,
  возвращая значение 0.


Какая ошибка, например, в этом классе?

public class AsyncSocket : IDisposable
{
    Socket socket;
    StreamReader sr;

    public AsyncSocket()
    {
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        sr = new StreamReader(new NetworkStream(socket));
    }

    // ... остальные члены убраны

    public async Task ReadLineAsync()
    {
        return await sr.ReadLineAsync();
    }

    public void Dispose()
    {
        if (sr != null) sr.Close();
        if (socket != null) socket.Dispose();
    }
}


Допустим, пришла строка "123\r\nqwe". StreamReader считает всю её в свой внутренний
буфер при вызове ReadLineAsync() вернет "123". Данные из внутреннего буфера не удаляются
же? При втором вызове ReadLineAsync() завершится, когда придет конец строки и в результатом
вернется "qwe". 
Албахари пишет про бесконечное блокирование, так к нему может привести и обычный
метод Read, если никакие данные не будут отправлены сокету.

Объясните почему нельзя использовать класс StreamReader вместе с NetworkStream? 
    


Ответы

Ответ 1



"Буфер опережающего чтения" означает, что StreamReader попытается вычитать из потока сразу килобайт (bufferSize) данных. "Про запас". Это улучшает производительность в случае чтения с диска, но может приводить к непредсказуемым последствиям при чтении. Особенность NetworkStream.Read в том, что он может блокироваться при чтении (в случае, если сокет еще открыт, но данных нет). Албахари пишет о теоретической ситуации, когда StreamReader мог бы вернуть вам данные (т.к. они есть в буфере), но обнаружил, что в буфере есть место и решил на всякий случай дочитать побольше данных в буфер вызвал NetworkStream.Read NetworkStream.Read заблокировался И вы оказываетесь в ситуации, когда вроде как нужные вам данные пришли, но StreamReader висит и не возвращает ничего, т.к. хочет еще больше данных из потока. На практике (в текущей реализации StreamReader и NetworkStream) такая ситуация не возникает, т.к. StreamReader читает данные из входного потока только в случае, если в буфере недостаточно данных для завершения требуемой операции. NetworkStream.Read не блокируется, если хоть какие-то данные есть. Т.е. блокировка произойдет только в случае, когда нужные данные действительно не пришли. Но реализация — это одно, нет никаких гарантий что она не поменяется. На самом деле проблема решается тем, что сочетание NetworkStream + StreamReader не имеет практической ценности. StreamReader приспособлен для работы с чисто текстовыми данными. Например, он пытается искать в читаемом потоке преамбулу (BOM), и вообще — использует сами данные для определения концов строк, что уже дико неудобно при работе с сетью. По сети обычно передаются смешанные данные. Например, те же строки принято передавать в виде <длина><текст>, чтобы не попадать в ловушку "непонятно сколько данных вычитали и сколько еще ждать". Для такой работы со стримами удобнее использовать BinaryReader.

Комментариев нет:

Отправить комментарий