Я столкнулся с проблемой метода BinaryReader.ReadChars(). Когда я оборачиваю BinaryReader вокруг необработанного сокета NetworkStream, иногда я получаю повреждение потока, когда считываемый поток не синхронизируется. Рассматриваемый поток содержит сообщения в протоколе двоичной сериализации.
Я проследил это до следующего
- Это происходит только при чтении строки юникода (закодированной с использованием 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;
Я думаю, что корень - это ошибка в коде .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.
Интересно услышать мысли людей по этому поводу и делаю ли я что-то не так или нет. И, возможно, это сэкономит следующему человеку несколько часов/дней утомительной отладки.
РЕДАКТИРОВАТЬ: опубликовано для подключения Подключить элемент отслеживания