2009-11-26 5 views
7

Tôi đã chạy vào những gì tôi tin là một vấn đề với phương thức BinaryReader.ReadChars(). Khi tôi quấn một BinaryReader xung quanh một ổ cắm nguyên NetworkStream đôi khi tôi nhận được một dòng tham nhũng, nơi dòng được đọc được ra khỏi đồng bộ. Luồng được đề cập chứa các thông điệp trong giao thức tuần tự nhị phân.Vấn đề với BinaryReader.ReadChars()

Tôi đã theo dõi này xuống sau

  • Nó chỉ xảy ra khi đọc một chuỗi unicode (được mã hóa bằng cách sử dụng Encoding.BigEndian)
  • Nó chỉ xảy ra khi chuỗi trong câu hỏi được chia giữa hai gói tcp (xác nhận sử dụng Wireshark)

tôi nghĩ rằng những gì đang xảy ra là như sau (trong bối cảnh các ví dụ dưới đây)

  • BinaryReader.ReadChars() được gọi là yêu cầu đọc 3 ký tự (độ dài chuỗi được mã hóa trước chính chuỗi đó)
  • Vòng lặp đầu tiên yêu cầu đọc 6 byte (3 ký tự còn lại * 2 byte/char) dòng mạng
  • mạng suối chỉ có 3 byte sẵn
  • 3 byte đọc vào bộ đệm địa phương
  • buffer giao cho bộ giải mã
  • decoder giải mã 1 char, và giữ byte khác trong nó đệm nội bộ riêng
  • Second lặp lại nội bộ nhiệm vụ đọc 4 byte! (2 ký tự còn lại * 2 byte/char)
  • dòng Mạng có tất cả 4 byte sẵn
  • 4 byte đọc vào bộ đệm địa phương
  • Buffer giao cho bộ giải mã
  • Decoder giải mã 2 char, và giữ các byte thứ 4 còn lại trong nội bộ
  • Giải mã chuỗi hoàn tất
  • Mã tuần tự cố gắng loại trừ mục và mục tiếp theo do lỗi gián đoạn luồng.

    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; 
    

Tôi nghĩ gốc là một lỗi trong mã NET trong đó sử dụng charsRemaining * byte/char mỗi vòng lặp để tính toán các byte còn lại được yêu cầu. Vì byte thừa được ẩn trong Bộ giải mã nên việc tính toán này có thể bị tắt bởi một byte gây ra thêm byte được tiêu thụ ngoài luồng đầu vào.

Dưới đây là .NET framework đang trong câu hỏi

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

Tôi không hoàn toàn chắc chắn nếu điều này là một lỗi hay chỉ là một sự lạm dụng của API. Để làm việc vòng vấn đề này tôi chỉ tính toán các byte yêu cầu bản thân mình, đọc chúng, và sau đó chạy byte [] thông qua Encoding.GetString() có liên quan. Tuy nhiên điều này sẽ không làm việc cho một cái gì đó như UTF-8.

Hãy quan tâm để nghe suy nghĩ của mọi người về điều này và liệu tôi có đang làm điều gì đó sai hay không. Và có lẽ nó sẽ tiết kiệm cho người tiếp theo một vài giờ/ngày của tẻ nhạt tẻ nhạt.

CHỈNH SỬA: được đăng để kết nối Connect tracking item

+0

Làm cho tôi băn khoăn tại sao BinaryReader thậm chí có phương pháp ReadChars. Toàn bộ điểm của BinaryReader là đọc dữ liệu nhị phân, không phải dữ liệu văn bản. Tôi nghĩ điều đúng đắn cần làm là sử dụng các lớp mã hóa như bạn đã nói. – Josh

+0

Không chắc chắn, đoán nó đang cố gắng để được một mục đích chung Reader chuyển đổi từ nhị phân thành các loại nguyên thủy cơ bản (int, dài, chuỗi vv). Tôi nghĩ rằng cách tiếp cận tốt nhất tổng thể, mà sẽ làm việc cho UTF-8 quá, là để mã hóa số byte (chứ không phải là ký tự) ở phía gửi và sau đó làm một byte [] đọc + Mã hóa cuộc gọi. –

Trả lời

3

Tôi đã sao chép sự cố bạn đã đề cập với BinaryReader.ReadChars.

Mặc dù nhà phát triển luôn cần tính đến điều tra khi soạn những thứ như luồng và bộ giải mã, điều này có vẻ như một lỗi khá đáng kể trong BinaryReader vì lớp đó được dùng để đọc cấu trúc dữ liệu bao gồm nhiều loại dữ liệu khác nhau. Trong trường hợp này, tôi đồng ý rằng ReadChars cần phải thận trọng hơn trong những gì nó đọc để tránh mất byte đó.

Không có gì sai với cách giải quyết của bạn bằng cách sử dụng trực tiếp số Decoder, sau khi tất cả những gì là ReadChars thực hiện sau hậu trường.

Unicode là một trường hợp đơn giản. Nếu bạn nghĩ về mã hóa tùy ý, không có mục đích chung nào để đảm bảo rằng số lượng byte đúng được tiêu thụ khi bạn vượt qua số ký tự thay vì đếm byte (suy nghĩ về các ký tự độ dài khác nhau và các trường hợp liên quan đến đầu vào không đúng định dạng). Vì lý do này, tránh BinaryReader.ReadChars có lợi cho việc đọc số lượng byte cụ thể cung cấp giải pháp tổng quát, mạnh mẽ hơn.

Tôi khuyên bạn nên mang điều này đến sự chú ý của Microsoft qua http://connect.microsoft.com/visualstudio.

+0

Cảm ơn bạn đã xác nhận, đăng nó để kết nối, những người đang xem xét nó. –

1

Thú vị; bạn có thể báo cáo điều này về "kết nối". Như là một khoảng cách dừng lại, bạn cũng có thể thử gói với BufferredStream, nhưng tôi mong đợi điều này là papering trên một vết nứt (nó vẫn có thể xảy ra, nhưng ít thường xuyên hơn).

Cách tiếp cận khác, tất nhiên, là để đệm trước toàn bộ thư (nhưng không phải toàn bộ luồng); sau đó đọc từ một cái gì đó như MemoryStream - giả sử giao thức mạng của bạn thông điệp logic (và lý tưởng có độ dài tiền tố và không quá lớn). Sau đó, khi giải mã tất cả dữ liệu có sẵn.

1

Điều này nhắc nhở một trong những câu hỏi của riêng tôi (Reading from a HttpResponseStream fails), nơi tôi đã gặp sự cố khi đọc từ luồng phản hồi HTTP StreamReader sẽ nghĩ rằng nó đã kết thúc sớm của luồng sao cho các trình phân tích cú pháp của tôi sẽ xuất hiện bất ngờ.

Giống như Marc đã đề xuất cho vấn đề của bạn trước tiên bạn đã thử đặt trước trong một MemoryStream hoạt động tốt nhưng có nghĩa là bạn có thể phải chờ một thời gian dài nếu bạn có tệp lớn để đọc (đặc biệt là từ mạng/web) có thể làm bất cứ điều gì hữu ích với nó. Cuối cùng tôi đã giải quyết việc tạo phần mở rộng của riêng mình cho TextReader, nó ghi đè phương thức Read và định nghĩa chúng bằng phương thức ReadBlock (có nghĩa là nó sẽ đọc cho đến khi nó có thể nhận được chính xác số ký tự mà bạn yêu cầu)

có thể là do tôi thực tế là phương pháp đọc không bảo đảm trả về số ký tự bạn yêu cầu, ví dụ: nếu bạn xem tài liệu cho phương pháp BinaryReader.Read (http://msdn.microsoft.com/en-us/library/ms143295.aspx), bạn sẽ thấy rằng nó nêu rõ:

Giá trị trả lại
Loại: Hệ thống .. ::. Int32
numbe r ký tự đọc vào bộ đệm. Điều này có thể ít hơn số byte được yêu cầu nếu nhiều byte không có sẵn hoặc có thể bằng 0 nếu kết thúc luồng.

Vì BinaryReader không có phương pháp ReadBlock giống như TextReader, tất cả những gì bạn có thể làm là tự mình theo dõi vị trí của mình hoặc của Marc khi lưu trước.

0

Tôi đang làm việc với Unity3D/Mono atm và phương pháp ReadChars thậm chí có thể chứa nhiều lỗi hơn. Tôi đã thực hiện một chuỗi như thế này:

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

mat.name thậm chí chứa chuỗi đúng, nhưng tôi chỉ có thể thêm các chuỗi trước nó. Tất cả mọi thứ sau khi chuỗi chỉ biến mất. Ngay cả với String.Format. Giải pháp của tôi cho đến nay không sử dụng phương thức ReadChars, nhưng đọc dữ liệu dưới dạng mảng byte và chuyển đổi nó thành chuỗi:

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