Проблема с BinaryReader.ReadChars () - PullRequest
7 голосов
/ 26 ноября 2009

Я столкнулся с проблемой метода BinaryReader.ReadChars (). Когда я оборачиваю BinaryReader вокруг необработанного сокета NetworkStream, иногда я получаю повреждение потока, когда считываемый поток выходит из синхронизации. Данный поток содержит сообщения в двоичном протоколе сериализации.

Я отследил это до следующего

  • Это происходит только при чтении строки Unicode (кодируется с использованием Encoding.BigEndian)
  • Это происходит, только когда рассматриваемая строка разбита на два tcp-пакета (подтверждено с помощью wireshark)

Я думаю, что происходит следующее (в контексте примера ниже)

  • BinaryReader.ReadChars () вызывается с просьбой прочитать 3 символа (длины строк кодируются перед самой строкой)
  • Первый цикл внутренне запрашивает чтение 6 байтов (3 оставшихся символа * 2 байта / символ) из сетевого потока
  • В сетевом потоке доступно только 3 байта
  • 3 байта считаны в локальный буфер
  • Буфер передан декодеру
  • Декодер декодирует 1 символ и сохраняет второй байт в своем собственном внутреннем буфере
  • Второй цикл внутренне запрашивает чтение 4 байта! (2 оставшихся символа * 2 байта / символ)
  • В сетевом потоке есть все 4 доступных байта
  • 4 байта считываются в локальный буфер
  • Буфер передан декодеру
  • Декодер декодирует 2 символа и сохраняет оставшиеся 4 байта внутри
  • Декодирование строки завершено
  • Код сериализации пытается разархивировать следующий элемент и квакает из-за повреждения потока.

    char[] buffer = new char[3];
    int charIndex = 0;
    
    Decoder decoder = Encoding.BigEndianUnicode.GetDecoder();
    
    // pretend 3 of the 6 bytes arrives in one packet
    byte[] b1 = new byte[] { 0, 83, 0 };
    int charsRead = decoder.GetChars(b1, 0, 3, buffer, charIndex);
    charIndex += charsRead;
    
    // pretend the remaining 3 bytes plus a final byte, for something unrelated,
    // arrive next
    byte[] b2 = new byte[] { 71, 0, 114, 3 };
    charsRead = decoder.GetChars(b2, 0, 4, buffer, charIndex);
    charIndex += charsRead;
    

Я думаю, что root - это ошибка в коде .NET, которая использует charsRemaining * bytes / char в каждом цикле для вычисления оставшихся необходимых байтов. Из-за дополнительного байта, скрытого в декодере, этот расчет может быть отключен на один, вызывая расход дополнительного байта из входного потока.

Вот код .NET Framework, о котором идет речь

    while (charsRemaining>0) { 
        // We really want to know what the minimum number of bytes per char 
        // is for our encoding.  Otherwise for UnicodeEncoding we'd have to
        // do ~1+log(n) reads to read n characters. 
        numBytes = charsRemaining;
        if (m_2BytesPerChar)
            numBytes <<= 1;

        numBytes = m_stream.Read(m_charBytes, 0, numBytes);
        if (numBytes==0) { 
            return (count - charsRemaining); 
        } 
        charsRead = m_decoder.GetChars(m_charBytes, 0, numBytes, buffer, index);

        charsRemaining -= charsRead;
        index+=charsRead;
    }

Я не совсем уверен, является ли это ошибкой или просто неправильным использованием API. Чтобы обойти эту проблему, я просто сам вычисляю требуемые байты, читаю их и затем запускаю byte [] через соответствующую Encoding.GetString (). Однако это не сработает для чего-то вроде UTF-8.

Интересно услышать мысли людей по этому поводу и узнать, делаю ли я что-то не так или нет. И, возможно, это сэкономит следующему человеку несколько часов / дней утомительной отладки.

РЕДАКТИРОВАТЬ: опубликовано для подключения Подключить элемент отслеживания

Ответы [ 4 ]

3 голосов
/ 26 ноября 2009

Я воспроизвел проблему, о которой вы упомянули, BinaryReader.ReadChars.

Несмотря на то, что разработчик всегда должен учитывать упреждения при составлении таких вещей, как потоки и декодеры, в BinaryReader это кажется довольно существенной ошибкой, поскольку этот класс предназначен для чтения структур данных, состоящих из данных различных типов. В этом случае я согласен, что ReadChars должен был быть более консервативным в том, что он читал, чтобы не потерять этот байт.

В вашем обходном пути использования Decoder нет ничего плохого, ведь именно это ReadChars делает за сценой.

Unicode - это простой случай. Если вы думаете о произвольной кодировке, то на самом деле не существует универсального способа гарантировать, что правильное количество байтов используется, когда вы передаете количество символов вместо количества байтов (подумайте о символах различной длины и случаях, связанных с неправильным вводом). По этой причине отказ от BinaryReader.ReadChars в пользу чтения определенного количества байтов обеспечивает более надежное общее решение.

Я бы посоветовал вам обратить на это внимание Microsoft через http://connect.microsoft.com/visualstudio.

1 голос
/ 26 ноября 2009

Это напоминает один из моих собственных вопросов ( Сбой чтения из HttpResponseStream ), где у меня возникла проблема, заключающаяся в том, что при чтении из потока ответов HTTP StreamReader мог бы подумать, что преждевременно достиг конца потока так что мои парсеры неожиданно разбомбили.

Как и предлагал Марк для вашей проблемы, я сначала попытался выполнить предварительную буферизацию в MemoryStream, который работает хорошо, но означает, что вам, возможно, придется долго ждать, если у вас есть большой файл для чтения (особенно из сети / сети), прежде чем Вы можете сделать что-нибудь полезное с этим. В конце концов я решил создать собственное расширение TextReader, которое переопределяет методы Read и определяет их с помощью метода ReadBlock (который выполняет блокировку чтения, т. Е. Он ждет, пока не получит ровно столько символов, сколько вы запрашиваете)

Вероятно, ваша проблема, как и моя, связана с тем фактом, что методы Read не гарантируют возвращение количества запрашиваемых вами символов, например, если вы посмотрите документацию для BinaryReader.Read (http://msdn.microsoft.com/en-us/library/ms143295.aspx)). Метод, который вы увидите, что он гласит:

Возвращаемое значение
Тип: System .. ::. Int32
Количество символов, считанных в буфер. Это может быть меньше количества запрошенных байтов, если такое количество байтов недоступно, или может быть равно нулю, если достигнут конец потока.

Поскольку BinaryReader не имеет методов ReadBlock, таких как TextReader, все, что вы можете сделать, - это использовать свой собственный подход к мониторингу позиции самостоятельно или по методу предварительного кэширования Марка.

1 голос
/ 26 ноября 2009

Интересный; Вы можете сообщить об этом на «подключиться». В качестве пробела вы также можете попробовать обернуть BufferredStream, но я ожидаю, что это окутывает трещину (это может все же случиться, но не так часто).

Другой подход, конечно, заключается в предварительной буферизации всего сообщения (но не всего потока); затем прочитайте что-то вроде MemoryStream - при условии, что ваш сетевой протокол имеет логических (и в идеале с префиксом длины, и не слишком больших) сообщений. Затем при декодировании все данные доступны.

0 голосов
/ 18 августа 2014

Я работаю с Unity3D / Mono atm, и метод ReadChars может даже содержать больше ошибок. Я сделал такую ​​строку:

mat.name = new string(binaryReader.ReadChars(64));

mat.name даже содержал правильную строку, но я мог бы просто добавить строки до . Все после строки просто исчезло. Даже с String.Format. Мое решение до сих пор не использует метод ReadChars, а считывает данные в виде байтового массива и преобразует их в строку:

byte[] str = binaryReader.ReadBytes(64);
int lengthOfStr = Array.IndexOf(str, (byte)0); // e.g. 4 for "clip\0"
mat.name = System.Text.ASCIIEncoding.Default.GetString(str, 0, lengthOfStr);
...