Pregunta

Me he topado con lo que creo que es un problema con el método BinaryReader.ReadChars (). Cuando envuelvo un BinaryReader alrededor de un socket en bruto NetworkStream, ocasionalmente obtengo una corrupción de flujo en la que el flujo que se lee se desincroniza. El flujo en cuestión contiene mensajes en un protocolo de serialización binario.

He rastreado esto hasta lo siguiente

  • Solo sucede cuando se lee una cadena Unicode (codificada mediante Encoding.BigEndian)
  • Solo sucede cuando la cadena en cuestión se divide en dos paquetes TCP (confirmados mediante el uso de wireshark)

Creo que lo que está sucediendo es lo siguiente (en el contexto del ejemplo a continuación)

  • BinaryReader.ReadChars () se llama para pedirle que lea 3 caracteres (las longitudes de las cadenas están codificadas antes de la cadena en sí)
  • El primer bucle solicita internamente una lectura de 6 bytes (3 caracteres restantes * 2 bytes / char) fuera del flujo de la red
  • El flujo de red solo tiene 3 bytes disponibles
  • 3 bytes leídos en el búfer local
  • Buffer entregado a Decoder
  • El decodificador decodifica 1 carácter, y mantiene el otro byte en su propio búfer interno
  • ¡El segundo bucle solicita internamente una lectura de 4 bytes! (2 caracteres restantes * 2 bytes / char)
  • El flujo de red tiene los 4 bytes disponibles
  • 4 bytes leídos en búfer local
  • Buffer entregado a Decoder
  • El decodificador decodifica 2 caracteres, y mantiene los 4tos bytes restantes internamente
  • La decodificación de cadena está completa
  • El código de serialización intenta desmarcar el siguiente ítem y tiembla debido a la corrupción de la secuencia.

    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;
    

Creo que la raíz es un error en el código .NET que usa charsRemaining * bytes / char en cada bucle para calcular los bytes restantes requeridos. Debido al byte adicional oculto en el decodificador, este cálculo puede desactivarse en uno, lo que hace que se consuma un byte adicional fuera del flujo de entrada.

Aquí está el código del framework .NET en cuestión

    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;
    }

No estoy completamente seguro de si se trata de un error o simplemente de un uso incorrecto de la API. Para solucionar este problema, solo estoy calculando los bytes que necesito, los leo y luego ejecuto el byte [] a través del Encoding.GetString () correspondiente. Sin embargo, esto no funcionaría para algo como UTF-8.

Estar interesado en escuchar los pensamientos de la gente sobre esto y si estoy haciendo algo mal o no. Y tal vez le ahorrará a la siguiente persona unas horas / días de depuración tediosa.

EDIT: publicado para conectarse Conectar elemento de seguimiento

¿Fue útil?

Solución

He reproducido el problema que mencionaste con BinaryReader.ReadChars .

Aunque el desarrollador siempre debe tener en cuenta el lookahead al componer cosas como secuencias y decodificadores, esto parece ser un error bastante significativo en BinaryReader porque esa clase está diseñada para leer estructuras de datos compuestas por varios tipos de datos. En este caso, estoy de acuerdo en que ReadChars debería haber sido más conservador en lo que se lee para evitar perder ese byte.

No hay nada de malo en su solución de usar el Decoder directamente, después de todo eso es lo que ReadChars hace entre bastidores.

Unicode es un caso simple. Si piensa en una codificación arbitraria, realmente no existe una forma de propósito general para garantizar que se consuma el número correcto de bytes cuando pasa un conteo de caracteres en lugar de un conteo de bytes (piense en caracteres de longitud variable y casos que involucran una entrada mal formada). Por este motivo, evitar BinaryReader.ReadChars a favor de leer el número específico de bytes proporciona una solución general más sólida.

Le sugiero que llame la atención de Microsoft a través de http://connect.microsoft.com/visualstudio .

Otros consejos

Interesante; puede informar de esto en " conectar " ;. Como una brecha, también puede intentar envolver con BufferredStream , pero creo que esto se está acumulando sobre una grieta (aún puede suceder, pero con menos frecuencia).

El otro enfoque, por supuesto, es pre-amortiguar un mensaje completo (pero no la secuencia completa); luego lea algo como MemoryStream : suponiendo que su protocolo de red tenga mensajes lógicos (e idealmente con un prefijo de longitud y no demasiado grandes). Luego, cuando está decodificando , todos los datos están disponibles.

Esto recuerda una de mis propias preguntas ( la lectura de un HttpResponseStream falla ) donde tuve un problema que al leer de un flujo de respuesta HTTP, StreamReader pensaría que había llegado al final del flujo prematuramente, de modo que mis analizadores se bombardearan inesperadamente.

Como sugirió Marc para su problema, primero probé el búfer previo en un MemoryStream que funciona bien, pero significa que puede tener que esperar mucho tiempo si tiene un archivo grande para leer (especialmente del red / web) antes de poder hacer algo útil con él. Finalmente decidí crear mi propia extensión de TextReader, que reemplaza los métodos de lectura y los define con el método ReadBlock (que realiza una lectura de bloqueo, es decir, espera hasta que pueda obtener exactamente la cantidad de caracteres que solicitas)

Su problema probablemente se deba al mío, ya que los métodos de lectura no están garantizados para devolver el número de caracteres que solicita, por ejemplo, si consulta la documentación del BinaryReader.Read ( http://msdn.microsoft.com/en-us/library/ms143295 .aspx ) método verás que dice:

  

Valor de retorno
  Tipo: Sistema .. ::. Int32
  El número de caracteres leídos en el búfer. Esto podría ser menor que el número de bytes solicitados si la cantidad de bytes no está disponible, o podría ser cero si se llega al final de la secuencia.

Como BinaryReader no tiene métodos de ReadBlock como un TextReader, todo lo que puede hacer es adoptar su propio enfoque de monitoreo de la posición o la de Marc para el almacenamiento en caché previo.

Estoy trabajando con Unity3D / Mono atm y el método ReadChars puede incluso contener más errores. Hice una cadena como esta:

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

mat.name incluso contenía la cadena correcta, pero solo podía agregar cadenas antes . Todo después de la cadena simplemente desapareció. Incluso con String.Format. Mi solución hasta ahora no utiliza el método ReadChars, pero lee los datos como una matriz de bytes y la convierte en una cadena:

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);
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top