문제

BinaryReader.ReadChars() 메서드에 문제가 있다고 생각되는 문제가 발생했습니다.원시 소켓 NetworkStream 주위에 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 코드의 버그라고 생각합니다.디코더에 숨겨진 추가 바이트로 인해 이 계산은 입력 스트림에서 추가 바이트가 소비되는 원인이 될 수 있습니다.

문제의 .NET 프레임워크 코드는 다음과 같습니다.

    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의 오용인지는 확실하지 않습니다.이 문제를 해결하기 위해 필요한 바이트를 직접 계산하고 읽은 다음 관련 Encoding.GetString()을 통해 byte[]를 실행합니다.그러나 이것은 UTF-8과 같은 것에서는 작동하지 않습니다.

이것에 대한 사람들의 생각과 내가 뭔가 잘못하고 있는지 아닌지에 관심을 가지십시오.그리고 아마도 다음 사람이 지루한 디버깅에 소요되는 몇 시간/일을 절약할 수 있을 것입니다.

편집하다:연결하라고 게시됨 추적항목 연결

도움이 되었습니까?

해결책

나는 당신이 언급 한 문제를 재현했습니다 BinaryReader.ReadChars.

개발자는 스트림 및 디코더와 같은 것들을 구성 할 때 항상 룩보드를 설명해야하지만 이것은 상당히 중요한 버그처럼 보입니다. BinaryReader 해당 클래스는 다양한 유형의 데이터로 구성된 데이터 구조를 읽기위한 것이기 때문입니다. 이 경우 동의합니다 ReadChars 바이트를 잃지 않기 위해 읽은 내용이 더 보수적이었을 것입니다.

사용하는 해결 방법에는 아무런 문제가 없습니다. Decoder 직접적으로, 결국 그게 무엇입니다 ReadChars 무대 뒤에서합니다.

유니 코드는 간단한 경우입니다. 임의의 인코딩에 대해 생각한다면, 바이트 카운트 대신 문자 수를 통과 할 때 올바른 바이트 수를 소비 할 수있는 범용 방법은 없습니다 (다양한 길이 문자 및 기형 입력과 관련된 케이스에 대해 생각하십시오). 이런 이유로 피하십시오 BinaryReader.ReadChars 특정 바이트 수를 읽기 위해보다 강력하고 일반적인 솔루션을 제공합니다.

나는 당신이 이것을 Microsoft의 관심에 가져 오는 것을 제안합니다. http://connect.microsoft.com/visualstudio.

다른 팁

흥미로운; 이것을 "Connect"에보 고 할 수 있습니다. 스톱 갭으로서, 당신은 또한 마감을 시도 할 수 있습니다. BufferredStream, 그러나 나는 이것이 균열에 대한 종이가 될 것으로 기대합니다 (여전히 발생할 수 있지만 덜 자주).

물론 다른 접근법은 전체 메시지 (전체 스트림이 아님)를 사전 버퍼하는 것입니다. 그런 다음 같은 것을 읽으십시오 MemoryStream - 네트워크 프로토콜을 가정합니다 가지다 논리적 (그리고 이상적으로 길이가 정해져 있고 너무 크지 않음) 메시지. 그렇다면 디코딩 모든 데이터를 사용할 수 있습니다.

이것은 내 자신의 질문 중 하나를 생각나게 합니다(HttpResponseStream에서 읽는 데 실패했습니다.) HTTP 응답 스트림에서 읽을 때 StreamReader가 스트림의 끝에 조기에 도달했다고 생각하여 파서가 예기치 않게 폭발하는 문제가 있었습니다.

Marc가 귀하의 문제에 대해 제안한 것처럼 저는 먼저 사전 버퍼링을 시도했습니다. MemoryStream 이는 잘 작동하지만 읽을 큰 파일이 있는 경우(특히 네트워크/웹에서) 유용한 작업을 수행하기 전에 오랜 시간을 기다려야 할 수도 있음을 의미합니다.나는 결국 Read 메서드를 재정의하고 ReadBlock 메서드(블로킹 읽기를 수행함)를 사용하여 이를 정의하는 자체 TextReader 확장을 만들기로 결정했습니다.요청한 문자 수를 정확하게 얻을 수 있을 때까지 기다립니다.)

귀하의 문제는 아마도 Read 메소드가 귀하가 요청한 문자 수를 반환하도록 보장되지 않는다는 사실 때문에 발생했을 것입니다. 예를 들어, BinaryReader.Read (http://msdn.microsoft.com/en-us/library/ms143295.aspx) 메소드에 따르면 다음과 같은 내용이 표시됩니다.

반환 값
유형:시스템..::.Int32
버퍼로 읽어온 문자 수입니다.많은 바이트를 사용할 수 없는 경우 요청된 바이트 수보다 작을 수 있으며, 스트림 끝에 도달하면 0이 될 수 있습니다.

BinaryReader에는 TextReader와 같은 ReadBlock 메서드가 없으므로 사용자가 할 수 있는 것은 직접 위치를 모니터링하거나 Marc의 사전 캐싱 위치를 모니터링하는 고유한 접근 방식을 취하는 것뿐입니다.

Unity3D/Mono ATM으로 작업하고 있으며 Readchars-Method에는 더 많은 오류가 포함되어있을 수도 있습니다. 나는 다음과 같이 문자열을 만들었습니다.

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

mat.name 올바른 문자열도 포함되어 있었지만 문자열을 추가 할 수 있습니다. ~ 전에 그것. 문자열 이후의 모든 것이 방금 분해되었습니다. string.format도 마찬가지입니다. 지금까지의 솔루션은 Readchars-Method를 사용하지 않고 데이터를 바이트 배열로 읽고 문자열로 변환합니다.

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