Ostatnio podczas przeróbki programu do komunikacji z WebSphere MQ (v6.0.2.7) zacząłem znajdować w logach wyjątek typu EndOfStreamException. Jako, że kod adaptera był dość złożony chwilę zajęło zanim znalazłem banalną przyczynę problemów ;)
System.IO.EndOfStreamException: Nie można odczytać danych spoza końca
strumienia.
w System.IO.__Error.EndOfFile()
w System.IO.BinaryReader.ReadByte()
w System.IO.BinaryReader.Read7BitEncodedInt()
w System.IO.BinaryReader.ReadString()
w IBM.WMQ.MQMessage.ReadString(Int32 length)
Błąd był zgłaszany dlatego, że czasem w dwóch różnych miejscach występowało wywołanie metody ReadString na tym samym obiekcie MQMessage:
string text = message.ReadString(message.MessageLength);
By pozbyć się kłopotu wystarczy dodać jedną linię kodu:
string text = message.ReadString(message.MessageLength);
message.Seek(0);
Skąd problem?
ReadString to metoda odczytująca strumień bajtów i konwertująca go do stringa*. Po poprawnym odczycie całej treści komunikatu znacznik bieżącej pozycji w strumieniu pozostawał na jego końcu, więc następne wywołanie ReadString musiało skończyć się wyjątkiem EndOfStreamException. Dlaczego musiało? ReadString (IBM.WMQ.MQMessage) korzysta w środku z danych przechowywanych w obiekcie typu MemoryStream. Podczas pobierania tekstu, w zależności od właściwości DataLength komunikatu może być wywoływana metoda ReadString z klasy .NET Framework System.IO.BinaryReader. By odczytać tekst musi ona najpierw pobrać jego zakodowaną długość - do tego służy metoda Read7BitEncodedInt widoczna na śladzie stosu. Korzysta ona z kolei z ReadByte, która po natrafieniu na koniec strumienia rzuca omawiany wyjątek.
* Konwersja zachodzi z użyciem właściwości CharacterSet komunikatu (CCSID).
Jeśli chcesz wysłać do kolejki WebSphere MQ tekst zakodowany w standardzie UTF-8, pamiętaj o ustawieniu właściwości CharacterSet obiektu MQMessage na 1208. Jeśli tego nie zrobisz, tekst zostanie zakodowany z użyciem UTF-16 (CCSID 1200).
MQQueueManager queueManager = new MQQueueManager(...);
MQQueue queue = queueManager.AccessQueue(...);
MQPutMessageOptions putMessageOptions = new MQPutMessageOptions(...);
MQMessage message = new MQMessage();
message.Format = MQC.MQFMT_STRING;
message.CharacterSet = 1208;
message.WriteString("abcąćę");
queue.Put(message, putMessageOptions);
Stringi w .NET kodowane są przy użyciu UTF-16. Czasem jednak warto do wymiany informacji zastosować kodowanie UFT-8. Dlaczego? Wersja 8 jest oszczędniejsza, ponieważ znaki z tabeli US-ASCII są kodowane za pomocą 1 bajta, a nie za pomocą 2, jak w przypadku UTF-16. Jeśli wiec będziesz przesyłał tekst składający się jedynie z tego zestawu znaków, zużyjesz dwa razy mniej miejsca! W przypadku polskich liter znaki zostaną zakodowane na 2 bajtach (tak jak w UTF-16).
Oto porównanie bajtów dla tekstu "abcąćę":
UTF-8 61 62 63 C4 85 C4 87 C4 99
UTF-16 61 00 62 00 63 00 05 01 07 01 19 01