Miłosz Orzeł

.net, js, html, arduino, java... no rants or clickbaits.

IBM.WMQ.MQMessage.ReadString i EndOfStreamException

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

Wysyłanie komunikatu do WebSphere MQ w UTF-8

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