Domanda

Ho riscontrato quello che credo sia un problema con il metodo BinaryReader.ReadChars (). Quando avvolgo un BinaryReader attorno a un socket Raw NetworkStream di tanto in tanto ottengo un danneggiamento dello stream in cui lo stream letto non è sincronizzato. Lo stream in questione contiene messaggi in un protocollo di serializzazione binario.

L'ho rintracciato nel seguente

  • Succede solo durante la lettura di una stringa unicode (codificata utilizzando Encoding.BigEndian)
  • Succede solo quando la stringa in questione è divisa in due pacchetti tcp (confermati usando WireShark)

Penso che ciò che sta accadendo sia il seguente (nel contesto dell'esempio seguente)

  • BinaryReader.ReadChars () viene chiamato chiedendogli di leggere 3 caratteri (le lunghezze delle stringhe sono codificate prima della stringa stessa)
  • Il primo ciclo richiede internamente una lettura di 6 byte (3 caratteri rimanenti * 2 byte / carattere) dal flusso di rete
  • Lo stream di rete ha solo 3 byte disponibili
  • 3 byte letti nel buffer locale
  • Buffer passato a Decoder
  • Il decodificatore decodifica 1 carattere e mantiene l'altro byte nel proprio buffer interno
  • Il secondo loop richiede internamente una lettura di 4 byte! (2 caratteri rimanenti * 2 byte / carattere)
  • Lo stream di rete ha tutti e 4 i byte disponibili
  • 4 byte letti nel buffer locale
  • Buffer passato a Decoder
  • Il decodificatore decodifica 2 caratteri e mantiene internamente i rimanenti 4 byte
  • La decodifica stringa è completa
  • Il codice di serializzazione tenta di annullare la traduzione dell'elemento successivo e grida a causa della corruzione del flusso.

    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;
    

Penso che la radice sia un bug nel codice .NET che utilizza charsRantaining * byte / char ogni ciclo per calcolare i byte rimanenti richiesti. A causa del byte aggiuntivo nascosto nel Decoder, questo calcolo può essere disattivato da uno causando il consumo di un byte aggiuntivo dal flusso di input.

Ecco il codice del framework .NET in questione

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

Non sono del tutto sicuro che si tratti di un bug o di un uso improprio dell'API. Per ovviare a questo problema, sto solo calcolando i byte richiesti, leggendoli e quindi eseguendo il byte [] attraverso il relativo Encoding.GetString (). Tuttavia, questo non funzionerebbe per qualcosa come UTF-8.

Sii interessato a sentire i pensieri delle persone su questo e se sto facendo qualcosa di sbagliato o no. E forse salverà la prossima persona un paio di ore / giorni di noioso debug.

EDIT: pubblicato per connettersi Connetti elemento di monitoraggio

È stato utile?

Soluzione

Ho riprodotto il problema che hai citato con BinaryReader.ReadChars .

Sebbene lo sviluppatore debba sempre tenere conto del lookahead durante la composizione di elementi come stream e decodificatori, questo sembra un bug abbastanza significativo in BinaryReader perché quella classe è destinata alla lettura di strutture di dati composte da vari tipi di dati. In questo caso, sono d'accordo che ReadChars avrebbe dovuto essere più prudente in ciò che leggeva per evitare di perdere quel byte.

Non c'è niente di sbagliato nel risolvere il problema dell'utilizzo diretto del Decoder , dopo tutto ciò che è ciò che ReadChars fa dietro le quinte.

Unicode è un caso semplice. Se si pensa a una codifica arbitraria, in realtà non esiste un modo generico per garantire che venga consumato il numero corretto di byte quando si passa un conteggio di caratteri anziché un conteggio di byte (si pensi a caratteri di lunghezza variabile e casi che comportano input non validi). Per questo motivo, evitare BinaryReader.ReadChars a favore della lettura del numero specifico di byte offre una soluzione più solida e generale.

Suggerirei di portarlo all'attenzione di Microsoft tramite http://connect.microsoft.com/visualstudio .

Altri suggerimenti

Interessante; puoi segnalarlo su " connect " ;. Come stop-gap, puoi anche provare a racchiudere il BufferredStream , ma mi aspetto che questo si stacchi su una crepa (può ancora succedere, ma meno frequentemente).

L'altro approccio, ovviamente, è pre-bufferizzare un intero messaggio (ma non l'intero flusso); quindi leggi qualcosa come MemoryStream - supponendo che il tuo protocollo di rete abbia messaggi logici (idealmente con prefisso di lunghezza e non troppo grandi). Quindi quando è decodifica tutti i dati sono disponibili.

Questo mi ricorda una delle mie domande ( La lettura da un HttpResponseStream non riesce ) dove avevo un problema che durante la lettura da un flusso di risposta HTTP StreamReader pensava che avesse colpito prematuramente la fine del flusso in modo che i miei parser si sarebbero bombardati inaspettatamente.

Come Marc ha suggerito per il tuo problema, ho provato prima il pre-buffering in un MemoryStream che funziona bene ma significa che potresti dover aspettare molto tempo se hai un file di grandi dimensioni da leggere (specialmente dal network / web) prima di poter fare qualcosa di utile con esso. Alla fine ho deciso di creare la mia estensione di TextReader che sovrascrive i metodi di lettura e li definisce utilizzando il metodo di ReadBlock (che esegue una lettura di blocco, cioè attende fino a quando non ottiene esattamente il numero di caratteri richiesti)

Il tuo problema è probabilmente dovuto al mio perché i metodi di lettura non sono garantiti per restituire il numero di caratteri richiesti, ad esempio se guardi la documentazione per BinaryReader.Read ( http://msdn.microsoft.com/en-us/library/ms143295 .aspx ) vedrai che afferma:

  

Valore di ritorno
  Tipo: System .. ::. Int32
  Il numero di caratteri letti nel buffer. Potrebbe essere inferiore al numero di byte richiesti se il numero di byte non è disponibile o potrebbe essere zero se viene raggiunta la fine del flusso.

Dato che BinaryReader non ha metodi ReadBlock come un TextReader, tutto ciò che puoi fare è adottare il tuo approccio personale per monitorare la posizione tu stesso o Marc di pre-cache.

Sto lavorando con Unity3D / Mono atm e il metodo ReadChars potrebbe contenere anche più errori. Ho creato una stringa come questa:

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

mat.name conteneva persino la stringa corretta, ma potevo semplicemente aggiungere le stringhe prima . Tutto dopo la stringa è appena scomparso. Anche con String.Format. Finora la mia soluzione non è usare il metodo ReadChars, ma leggere i dati come array di byte e convertirli in una stringa:

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);
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top