Pergunta

Já corri em que eu acredito é um problema com os BinaryReader.ReadChars () método. Quando eu embrulhar um BinaryReader em torno de um socket raw NetworkStream ocasionalmente eu recebo uma corrupção riacho onde o que está sendo lido fluxo fica fora de sincronia. O fluxo em questão contém mensagens em um protocolo de serialização binária.

Eu rastreou esse baixo para o seguinte

  • Isso só acontece quando se lê uma string unicode (codificados usando o Encoding.BigEndian)
  • Isso só acontece quando a corda em questão é dividida em dois pacotes TCP (confirmado usando wireshark)

Eu acho que o que está acontecendo é o seguinte (no contexto do exemplo abaixo)

  • BinaryReader.ReadChars () é chamado pedindo-lhe para ler 3 caracteres (comprimentos de cordas são codificados antes do próprio string)
  • Primeiro circuito internamente solicita uma leitura de 6 bytes (3 caracteres restantes bytes * 2 / carvão animal) fora da corrente de rede
  • fluxo de rede tem apenas 3 bytes disponíveis
  • 3 bytes lidos em buffer local
  • Tampão entregue a Decoder
  • Decoder decodifica 1 char, e mantém o outro byte em seu próprio buffer interno
  • segundo loop solicita internamente uma leitura de 4 bytes! (2 caracteres restantes bytes * 2 / carvão animal)
  • fluxo de Network tem todos os 4 bytes disponíveis
  • 4 bytes lidos em buffer local
  • Tampão entregue a Decoder
  • Decoder decodifica 2 char, e mantém o quarto restante bytes internamente
  • Cordas decodificação é completa
  • serialização tentativas de código para desempacotar o próximo item e croaks por causa da corrupção fluxo.

    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;
    

Eu acho que a raiz é um bug no código .NET que usa charsRemaining * bytes / char cada loop para calcular os bytes restantes necessários. Por causa do byte extra escondido no Decoder este cálculo pode ser desligado por um causando um byte extra para ser consumida fora do fluxo de entrada.

Aqui está o código do framework .NET em questão

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

Eu não estou totalmente certo se este é um bug ou apenas um mau uso da API. Para trabalho em volta deste problema que estou apenas calcular os bytes exigido mim, lê-los, e então executar o byte [] através do Encoding.GetString relevante (). No entanto, isto não funcionaria para algo como UTF-8.

estar interessado em ouvir os pensamentos das pessoas sobre isso e se eu estou fazendo algo errado ou não. E talvez ele irá salvar a próxima pessoa algumas horas / dias de depuração tedioso.

EDIT: lançado para conectar Ligação item de rastreamento

Foi útil?

Solução

Eu ter reproduzido o problema que você mencionou com BinaryReader.ReadChars.

Embora o desenvolvedor precisa sempre conta para lookahead ao compor coisas como córregos e decodificadores, este parece ser um bug bastante significativo em BinaryReader porque essa classe é destinado para ler estruturas de dados compostas de vários tipos de dados. Neste caso, concordo que ReadChars deveria ter sido mais conservador no que ele leia para evitar a perda que byte.

Não há nada de errado com a sua solução alternativa de usar o Decoder diretamente, depois de tudo isso é o que ReadChars faz nos bastidores.

Unicode é um caso simples. Se você pensar sobre uma codificação arbitrária, não há realmente nenhuma maneira de propósito geral para garantir que o número correto de bytes são consumidos quando você passa em uma contagem de caracteres em vez de uma contagem de bytes (pense variando caracteres de comprimento e casos que envolvem entrada malformado). Por esta razão, evitando BinaryReader.ReadChars em favor de ler o número específico de bytes fornece uma solução mais robusta, em geral.

Gostaria de sugerir que você trazer isso para a atenção da Microsoft via http://connect.microsoft.com/visualstudio .

Outras dicas

Interessante; você poderia informar que esta em "conectar". Como um tapa-buracos, você também pode tentar embrulho com BufferredStream , mas espero que isso é papering sobre uma rachadura (ele ainda pode acontecer, mas com menos frequência).

A outra abordagem, é claro, é pré-tamponar uma mensagem inteira (mas não a totalidade do fluxo); em seguida, ler a partir de algo como MemoryStream - assumindo que o seu protocolo de rede tem lógico (e, idealmente, o prefixo de comprimento, e não muito grandes) mensagens. Então quando é decodificação todos os dados estão disponíveis.

Isto lembra de uma das minhas próprias perguntas ( leitura de um HttpResponseStream falhar ) onde eu tinha um problema que quando a leitura de um fluxo de resposta HTTP o StreamReader pensaria que tinha atingido o fim do fluxo prematuramente para que meus analisadores iria bombardear para fora inesperada.

Como Marc sugerido para o seu problema Eu tentei primeiro pré-buffering em um MemoryStream que funciona bem, mas significa que você pode ter que esperar um longo tempo se você tem um grande arquivo para ler (especialmente a partir da rede / web) antes de fazer algo útil com ele. I finalmente a acordo sobre a criação de minha própria extensão do TextReader que substitui os métodos de leitura e define-los usando o método ReadBlock (que faz uma leitura de blocos ou seja, ele espera até que possa obter exatamente o número de caracteres que você pedir)

Seu problema é provavelmente devido como o meu para o fato de que Leia métodos não são guarenteed para retornar o número de caracteres que você pedir, por exemplo, se você olhar a documentação para o BinaryReader.Read ( http://msdn.microsoft.com/en-us/library/ms143295.aspx ) método verá que ele afirma:

Valor de retorno
Tipo: System .. ::. Int32
O número de caracteres ler em memória intermédia. Isso pode ser menor do que o número de bytes solicitados se que muitos bytes não estão disponíveis, ou pode ser zero se o fim do fluxo é alcançado.

Desde BinaryReader não tem métodos ReadBlock como um TextReader tudo o que você pode fazer é levar sua própria abordagem de monitorar a posição de si mesmo ou Marc de pré-caching.

Eu estou trabalhando com Unity3D / Mono atm eo ReadChars-método pode até conter mais erros. Fiz uma string como esta:

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

mat.name ainda continha a seqüência correta, mas eu poderia apenas adicionar strings antes -lo. Tudo após a string apenas disappered. Mesmo com String.Format. Minha solução até agora não está usando o ReadChars-método, mas ler os dados como matriz de bytes e convertê-lo para uma string:

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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top