Вопрос

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

Я проследил за этим до следующего

  • Это происходит только при чтении строки в юникоде (закодированной с использованием 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;
    

Я думаю, что корень - это ошибка в .СЕТЕВОЙ код, который использует 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[] через соответствующую кодировку.getString().Однако это не сработало бы для чего-то вроде UTF-8.

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

Редактировать:опубликовано для подключения Подключить элемент отслеживания

Это было полезно?

Решение

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

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

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

mat.name даже содержал правильную строку, но я мог бы просто добавить строки до того , как IT.Все, что было после строки, просто исчезло.Даже со строкой.Формат.Мое решение пока не использует 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);
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top