Udostępnij za pośrednictwem


Kanał dzielący na fragmenty

W przykładzie ChunkingChannel pokazano, jak można używać niestandardowego protokołu lub kanału warstwowego do fragmentowania i fragmentowania dowolnych dużych komunikatów.

Podczas wysyłania dużych komunikatów przy użyciu programu Windows Communication Foundation (WCF) często pożądane jest ograniczenie ilości pamięci używanej do buforowania tych komunikatów. Jednym z możliwych rozwiązań jest przesyłanie strumieniowe treści komunikatu (przy założeniu, że większość danych znajduje się w treści). Jednak niektóre protokoły wymagają buforowania całego komunikatu. Niezawodne komunikaty i zabezpieczenia to dwa takie przykłady. Innym możliwym rozwiązaniem jest podzielenie dużego komunikatu na mniejsze komunikaty nazywane fragmentami, wysłanie tych fragmentów pojedynczo i ponowne utworzenie dużego komunikatu po stronie odbieranej. Sama aplikacja może wykonać tę fragmentowanie i odchylić fragmentowanie lub użyć niestandardowego kanału, aby to zrobić.

Fragmentowanie powinno być zawsze stosowane dopiero po utworzeniu całego komunikatu do wysłania. Kanał fragmentowania powinien być zawsze warstwowy poniżej kanału zabezpieczeń i kanału niezawodnej sesji.

Uwaga

Procedura instalacji i instrukcje kompilacji dla tego przykładu znajdują się na końcu tego tematu.

Założenia i ograniczenia dotyczące fragmentowania kanału

Struktura komunikatów

Kanał fragmentowania zakłada następującą strukturę komunikatów, która ma być fragmentowana:

<soap:Envelope>
  <!-- headers -->
  <soap:Body>
    <operationElement>
      <paramElement>data to be chunked</paramElement>
    </operationElement>
  </soap:Body>
</soap:Envelope>

W przypadku korzystania z modelu ServiceModel operacje kontraktu, które mają 1 parametr wejściowy, są zgodne z tym kształtem komunikatu dla komunikatu wejściowego. Podobnie operacje kontraktu, które mają 1 parametr wyjściowy lub wartość zwracaną są zgodne z tym kształtem komunikatu dla komunikatu wyjściowego. Poniżej przedstawiono przykłady takich operacji:

[ServiceContract]
interface ITestService
{
    [OperationContract]
    Stream EchoStream(Stream stream);

    [OperationContract]
    Stream DownloadStream();

    [OperationContract(IsOneWay = true)]
    void UploadStream(Stream stream);
}

Sesje

Kanał fragmentowania wymaga dostarczenia komunikatów dokładnie raz w uporządkowanym dostarczaniu komunikatów (fragmentów). Oznacza to, że bazowy stos kanału musi być sesji. Sesje mogą być dostarczane przez transport (na przykład transport TCP) lub przez kanał protokołu sesji (na przykład Kanał ReliableSession).

Asynchroniczne wysyłanie i odbieranie

Metody wysyłania i odbierania asynchroniczne nie są implementowane w tej wersji przykładowego kanału fragmentowania.

Protokół fragmentowania

Kanał fragmentowania definiuje protokół, który wskazuje początek i koniec serii fragmentów, a także numer sekwencji każdego fragmentu. W poniższych trzech przykładowych komunikatach przedstawiono komunikaty początkowe, fragmentujące i końcowe z komentarzami opisujące kluczowe aspekty każdego z nich.

Rozpocznij wiadomość

<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing"
            xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  <s:Header>
<!--Original message action is replaced with a chunking-specific action. -->
    <a:Action s:mustUnderstand="1">http://samples.microsoft.com/chunkingAction</a:Action>
<!--
Original message is assigned a unique id that is transmitted
in a MessageId header. Note that this is different from the WS-Addressing MessageId header.
-->
    <MessageId s:mustUnderstand="1" xmlns="http://samples.microsoft.com/chunking">
53f183ee-04aa-44a0-b8d3-e45224563109
</MessageId>
<!--
ChunkingStart header signals the start of a chunked message.
-->
    <ChunkingStart s:mustUnderstand="1" i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://samples.microsoft.com/chunking" />
<!--
Original message action is transmitted in OriginalAction.
This is required to re-create the original message on the other side.
-->
    <OriginalAction xmlns="http://samples.microsoft.com/chunking">
http://tempuri.org/ITestService/EchoStream
    </OriginalAction>
   <!--
    All original message headers are included here.
   -->
  </s:Header>
  <s:Body>
<!--
Chunking assumes this structure of Body content:
<element>
  <childelement>large data to be chunked<childelement>
</element>
The start message contains just <element> and <childelement> without
the data to be chunked.
-->
    <EchoStream xmlns="http://tempuri.org/">
      <stream />
    </EchoStream>
  </s:Body>
</s:Envelope>

Komunikat fragmentu

<s:Envelope
  xmlns:a="http://www.w3.org/2005/08/addressing"
  xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  <s:Header>
   <!--
    All chunking protocol messages have this action.
   -->
    <a:Action s:mustUnderstand="1">
      http://samples.microsoft.com/chunkingAction
    </a:Action>
<!--
Same as MessageId in the start message. The GUID indicates which original message this chunk belongs to.
-->
    <MessageId s:mustUnderstand="1"
               xmlns="http://samples.microsoft.com/chunking">
      53f183ee-04aa-44a0-b8d3-e45224563109
    </MessageId>
<!--
The sequence number of the chunk.
This number restarts at 1 with each new sequence of chunks.
-->
    <ChunkNumber s:mustUnderstand="1"
                 xmlns="http://samples.microsoft.com/chunking">
      1096
    </ChunkNumber>
  </s:Header>
  <s:Body>
<!--
The chunked data is wrapped in a chunk element.
The encoding of this data (and the entire message)
depends on the encoder used. The chunking channel does not mandate an encoding.
-->
    <chunk xmlns="http://samples.microsoft.com/chunking">
kfSr2QcBlkHTvQ==
    </chunk>
  </s:Body>
</s:Envelope>

Komunikat końcowy

<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing"
            xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  <s:Header>
    <a:Action s:mustUnderstand="1">
      http://samples.microsoft.com/chunkingAction
    </a:Action>
<!--
Same as MessageId in the start message. The GUID indicates which original message this chunk belongs to.
-->
    <MessageId s:mustUnderstand="1"
               xmlns="http://samples.microsoft.com/chunking">
      53f183ee-04aa-44a0-b8d3-e45224563109
    </MessageId>
<!--
ChunkingEnd header signals the end of a chunk sequence.
-->
    <ChunkingEnd s:mustUnderstand="1" i:nil="true"
                 xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
                 xmlns="http://samples.microsoft.com/chunking" />
<!--
ChunkingEnd messages have a sequence number.
-->
    <ChunkNumber s:mustUnderstand="1"
                 xmlns="http://samples.microsoft.com/chunking">
      79
    </ChunkNumber>
  </s:Header>
  <s:Body>
<!--
The ChunkingEnd message has the same <element><childelement> structure
as the ChunkingStart message.
-->
    <EchoStream xmlns="http://tempuri.org/">
      <stream />
    </EchoStream>
  </s:Body>
</s:Envelope>

Architektura kanału fragmentowania

Kanał fragmentowania jest taki IDuplexSessionChannel , który na wysokim poziomie jest zgodny z typową architekturą kanału. Istnieje element ChunkingBindingElement , który może utworzyć element ChunkingChannelFactory i .ChunkingChannelListener Tworzy ChunkingChannelFactory wystąpienia ChunkingChannel , gdy zostanie wyświetlony monit. Tworzy ChunkingChannelListener wystąpienia ChunkingChannel , gdy nowy kanał wewnętrzny jest akceptowany. Sam ChunkingChannel jest odpowiedzialny za wysyłanie i odbieranie komunikatów.

Na następnym poziomie ChunkingChannel opiera się na kilku składnikach w celu zaimplementowania protokołu fragmentowania. Po stronie wysyłania kanał używa niestandardowej XmlDictionaryWriter nazwy, ChunkingWriter która wykonuje rzeczywiste fragmentowanie. ChunkingWriter używa kanału wewnętrznego bezpośrednio do wysyłania fragmentów. Użycie niestandardowego XmlDictionaryWriter umożliwia wysyłanie fragmentów w miarę pisania dużej treści oryginalnej wiadomości. Oznacza to, że nie buforujemy całej oryginalnej wiadomości.

Diagram przedstawiający architekturę wysyłania fragmentów kanału.

Po stronie ChunkingChannel odbierającej pobiera komunikaty z kanału wewnętrznego i przekazuje je do niestandardowego XmlDictionaryReader o nazwie ChunkingReader, który ponownie tworzy oryginalny komunikat z przychodzących fragmentów. ChunkingChannel Opakowuje to ChunkingReader w niestandardowej Message implementacji o nazwie ChunkingMessage i zwraca ten komunikat do powyższej warstwy. Ta kombinacja ChunkingReader elementów i ChunkingMessage pozwala nam usunąć fragment oryginalnej treści wiadomości, ponieważ jest ona odczytywana przez warstwę powyżej, zamiast buforować całą oryginalną treść wiadomości. ChunkingReader zawiera kolejkę, w której buforuje przychodzące fragmenty do maksymalnej konfigurowalnej liczby buforowanych fragmentów. Po osiągnięciu tego maksymalnego limitu czytnik czeka na opróżnienie komunikatów z kolejki przez warstwę powyżej (czyli tylko odczyt z oryginalnej treści komunikatu) lub do momentu osiągnięcia maksymalnego limitu czasu odbierania.

Diagram przedstawiający architekturę odbierania fragmentów kanału.

Model programowania fragmentowania

Deweloperzy usług mogą określić, które komunikaty mają być fragmentowane, stosując ChunkingBehavior atrybut do operacji w ramach kontraktu. Atrybut uwidacznia AppliesTo właściwość, która umożliwia deweloperowi określenie, czy fragmentowanie ma zastosowanie do komunikatu wejściowego, komunikatu wyjściowego lub obu tych elementów. W poniższym przykładzie pokazano użycie atrybutu ChunkingBehavior :

[ServiceContract]
interface ITestService
{
    [OperationContract]
    [ChunkingBehavior(ChunkingAppliesTo.Both)]
    Stream EchoStream(Stream stream);

    [OperationContract]
    [ChunkingBehavior(ChunkingAppliesTo.OutMessage)]
    Stream DownloadStream();

    [OperationContract(IsOneWay=true)]
    [ChunkingBehavior(ChunkingAppliesTo.InMessage)]
    void UploadStream(Stream stream);

}

Na podstawie tego modelu programowania kompiluje ChunkingBindingElement listę identyfikatorów URI akcji, które identyfikują komunikaty do fragmentowania. Akcja każdego komunikatu wychodzącego jest porównywana z tą listą, aby określić, czy komunikat powinien być fragmentowany, czy wysyłany bezpośrednio.

Implementowanie operacji wysyłania

Na wysokim poziomie operacja Wyślij najpierw sprawdza, czy komunikat wychodzący musi być fragmentowany, a jeśli nie, wysyła komunikat bezpośrednio przy użyciu kanału wewnętrznego.

Jeśli komunikat musi być fragmentowany, wyślij tworzy nowy ChunkingWriter element i wywołuje WriteBodyContents komunikat wychodzący przekazujący ten ChunkingWriterkomunikat . Następnie ChunkingWriter wykonuje fragmentowanie komunikatu (w tym kopiowanie oryginalnych nagłówków wiadomości do komunikatu początkowego fragmentu) i wysyła fragmenty przy użyciu kanału wewnętrznego.

Warto zauważyć kilka szczegółów:

  • Wyślij pierwsze wywołania ThrowIfDisposedOrNotOpened , aby upewnić się, CommunicationState że element jest otwarty.

  • Wysyłanie jest synchronizowane, dzięki czemu tylko jeden komunikat może być wysyłany jednocześnie dla każdej sesji. Nazwa jest ManualResetEvent sendingDone resetowana po wysłaniu fragmentowanego komunikatu. Po wysłaniu komunikatu końcowego fragmentu zostanie ustawione to zdarzenie. Metoda Send czeka na ustawienie tego zdarzenia przed podjęciem próby wysłania wiadomości wychodzącej.

  • Wysyła blokady, CommunicationObject.ThisLock aby zapobiec zmianom stanu synchronizacji podczas wysyłania. Zapoznaj się z dokumentacją, CommunicationObject aby uzyskać więcej informacji na temat CommunicationObject stanów i maszyny stanu.

  • Limit czasu przekazany do wysyłania jest używany jako limit czasu dla całej operacji wysyłania, która obejmuje wysyłanie wszystkich fragmentów.

  • Projekt niestandardowy XmlDictionaryWriter został wybrany, aby uniknąć buforowania całej oryginalnej treści komunikatu. Gdybyśmy mieli dostać XmlDictionaryReader się na ciało przy użyciu message.GetReaderAtBodyContents całego ciała byłoby buforowane. Zamiast tego mamy niestandardowy element XmlDictionaryWriter przekazany do message.WriteBodyContentselementu . Gdy komunikat wywołuje metodę WriteBase64 na składniku zapisywania, składnik zapisywania pakuje fragmenty do komunikatów i wysyła je przy użyciu kanału wewnętrznego. ZapisBase64 blokuje się do momentu wysłania fragmentu.

Implementowanie operacji odbierania

Na wysokim poziomie operacja Odbieranie najpierw sprawdza, czy przychodzący komunikat nie null jest i że jego akcja to ChunkingAction. Jeśli nie spełnia obu kryteriów, komunikat zostanie zwrócony bez zmian z komunikatu Odbierz. W przeciwnym razie funkcja Receive tworzy nową ChunkingReader i nową ChunkingMessage opakowaną wokół niej (przez wywołanie metody GetNewChunkingMessage). Przed zwróceniem tego nowego ChunkingMessageelementu funkcja Odbieraj używa wątku do wykonania ReceiveChunkLoopwątku , który wywołuje innerChannel.Receive pętlę i przekazuje fragmenty do ChunkingReader komunikatu do momentu odebrania końcowego fragmentu lub przekroczenia limitu czasu odbierania.

Warto zauważyć kilka szczegółów:

  • Na przykład Wyślij, odbierz pierwsze wywołania ThrowIfDisposedOrNotOpened , aby upewnić się, że CommunicationState element jest otwarty.

  • Odbieranie jest również synchronizowane, tak aby tylko jeden komunikat mógł zostać odebrany naraz z sesji. Jest to szczególnie ważne, ponieważ po odebraniu komunikatu o rozpoczęciu fragmentu wszystkie kolejne odebrane komunikaty będą fragmentami w tej nowej sekwencji fragmentów do momentu odebrania komunikatu końcowego fragmentu. Odbieranie komunikatów nie może ściągnąć z kanału wewnętrznego, dopóki nie zostaną odebrane wszystkie fragmenty, które należą do wiadomości, która jest obecnie cofniętą. W tym celu funkcja Receive używa ManualResetEvent nazwy currentMessageCompleted, która jest ustawiana po odebraniu komunikatu końcowego fragmentu i zresetowaniu po odebraniu nowego komunikatu fragmentu uruchamiania.

  • W przeciwieństwie do funkcji Wyślij, funkcja Odbierz nie uniemożliwia synchronizowanych przejść stanu podczas odbierania. Na przykład zamknij można wywołać podczas odbierania i czekać, aż oczekiwanie na odebranie oryginalnej wiadomości zostanie ukończone lub zostanie osiągnięta określona wartość limitu czasu.

  • Limit czasu przekazany do odbierania jest używany jako limit czasu dla całej operacji odbierania, która obejmuje odbieranie wszystkich fragmentów.

  • Jeśli warstwa korzystająca z komunikatu zużywa treść komunikatu z szybkością niższą niż szybkość przychodzących komunikatów fragmentów, ChunkingReader buforuje te fragmenty przychodzące do limitu określonego przez ChunkingBindingElement.MaxBufferedChunks. Po osiągnięciu tego limitu nie są pobierane więcej fragmentów z niższej warstwy, dopóki nie zostanie użyty buforowany fragment lub zostanie osiągnięty limit czasu odbierania.

Przesłonięcia obiektu CommunicationObject

OnOpen (Otwórz)

OnOpen wywołuje metodę innerChannel.Open otwierania kanału wewnętrznego.

OnClose

OnClose pierwsze zestawy, stopReceive aby true zasygnalizować oczekiwanie ReceiveChunkLoop na zatrzymanie. Następnie czeka na receiveStopped ManualResetEventwartość , która jest ustawiona po ReceiveChunkLoop zatrzymaniu. Przy założeniu, że zatrzymanie ReceiveChunkLoop w określonym przedziale czasu, OnClose wywołania innerChannel.Close z pozostałym limitem czasu.

OnAbort

OnAbort wywołania innerChannel.Abort w celu przerwania kanału wewnętrznego. Jeśli istnieje oczekiwanie ReceiveChunkLoop , otrzyma wyjątek od oczekującego innerChannel.Receive wywołania.

OnFaulted

Element ChunkingChannel nie wymaga specjalnego zachowania, gdy kanał jest uszkodzony, więc OnFaulted nie jest zastępowany.

Implementowanie fabryki kanałów

Jest ChunkingChannelFactory on odpowiedzialny za tworzenie wystąpień ChunkingDuplexSessionChannel i przejścia stanu kaskadowego do fabryki kanałów wewnętrznych.

OnCreateChannel używa wewnętrznej fabryki kanałów do utworzenia kanału wewnętrznego IDuplexSessionChannel . Następnie tworzy nowy ChunkingDuplexSessionChannel kanał wewnętrzny wraz z listą akcji komunikatów, które mają być fragmentowane, oraz maksymalną liczbę fragmentów do buforowania po odebraniu. Lista akcji komunikatów, które mają być fragmentowane, a maksymalna liczba fragmentów do buforowania to dwa parametry przekazywane do ChunkingChannelFactory w konstruktorze. W sekcji dotyczącej ChunkingBindingElement opisano, skąd pochodzą te wartości.

Klasy OnOpen, OnCloseOnAbort i ich asynchroniczne odpowiedniki wywołają odpowiednią metodę przejścia stanu w fabryce kanału wewnętrznego.

Implementowanie odbiornika kanału

Jest ChunkingChannelListener otoką wokół odbiornika kanału wewnętrznego. Jego główną funkcją, oprócz delegowania wywołań do tego odbiornika kanału wewnętrznego, jest opakowywanie nowych ChunkingDuplexSessionChannels wokół kanałów akceptowanych z odbiornika kanału wewnętrznego. Odbywa się to w plikach OnAcceptChannel i OnEndAcceptChannel. Nowo utworzony ChunkingDuplexSessionChannel kanał wewnętrzny jest przekazywany wraz z innymi opisanymi wcześniej parametrami.

Implementowanie elementu powiązania i powiązania

ChunkingBindingElement jest odpowiedzialny za tworzenie elementów ChunkingChannelFactory i ChunkingChannelListener. SprawdzaChunkingBindingElement, czy język T w><CanBuildChannelFactory T i CanBuildChannelListener<T> jest typu IDuplexSessionChannel (jedynym kanałem obsługiwanym przez kanał fragmentowania) i czy inne elementy powiązania w powiązaniu obsługują ten typ kanału.

BuildChannelFactory<T> najpierw sprawdza, czy żądany typ kanału można skompilować, a następnie pobiera listę akcji komunikatów, które mają być fragmentowane. Aby uzyskać więcej informacji, zobacz następującą sekcję. Następnie tworzy nową ChunkingChannelFactory fabrykę kanałów wewnętrznych (zwracaną z context.BuildInnerChannelFactory<IDuplexSessionChannel>), listę akcji komunikatów i maksymalną liczbę fragmentów do buforu. Maksymalna liczba fragmentów pochodzi z właściwości o nazwie MaxBufferedChunks uwidocznionej przez element ChunkingBindingElement.

BuildChannelListener<T> ma podobną implementację do tworzenia ChunkingChannelListener i przekazywania go odbiornika kanału wewnętrznego.

Istnieje przykładowe powiązanie zawarte w tym przykładzie o nazwie TcpChunkingBinding. To powiązanie składa się z dwóch elementów powiązania: TcpTransportBindingElement i ChunkingBindingElement. Oprócz uwidaczniania MaxBufferedChunks właściwości powiązanie ustawia również niektóre TcpTransportBindingElement właściwości, takie jak MaxReceivedMessageSize (ustawia je na ChunkingUtils.ChunkSize + 100 KB bajtów dla nagłówków).

TcpChunkingBinding Implementuje IBindingRuntimePreferences również i zwraca wartość true z ReceiveSynchronously metody wskazującej, że zaimplementowane są tylko synchroniczne wywołania odbierania.

Określanie komunikatów do fragmentowania

Fragmentujący kanał fragmentuje tylko komunikaty zidentyfikowane za pośrednictwem atrybutu ChunkingBehavior . Klasa ChunkingBehavior implementuje IOperationBehavior i jest implementowana przez wywołanie AddBindingParameter metody . W tej metodzie metoda ChunkingBehavior sprawdza wartość jej AppliesTo właściwości (InMessagelub obu), aby określić, OutMessage które komunikaty powinny być fragmentowane. Następnie pobiera akcję każdego z tych komunikatów (z kolekcji Messages w dniu OperationDescription) i dodaje ją do kolekcji ciągów zawartej w wystąpieniu ChunkingBindingParameterklasy . Następnie dodaje go ChunkingBindingParameter do podanego BindingParameterCollectionelementu .

Jest on BindingParameterCollection przekazywany wewnątrz BindingContext elementu do każdego elementu powiązania w powiązaniu, gdy ten element powiązania kompiluje fabrykę kanałów lub odbiornik kanału. Implementacja ChunkingBindingElementBuildChannelFactory<T> i BuildChannelListener<T> wyciąganie tego ChunkingBindingParameter z BindingContext's BindingParameterCollection. Kolekcja akcji zawartych w obiekcie ChunkingBindingParameter jest następnie przekazywana do ChunkingChannelFactory obiektu lub ChunkingChannelListener, który z kolei przekazuje go do obiektu ChunkingDuplexSessionChannel.

Uruchamianie przykładu

Aby skonfigurować, skompilować i uruchomić przykład

  1. Zainstaluj ASP.NET 4.0 przy użyciu następującego polecenia.

    %windir%\Microsoft.NET\Framework\v4.0.XXXXX\aspnet_regiis.exe /i /enable
    
  2. Upewnij się, że wykonano procedurę instalacji jednorazowej dla przykładów programu Windows Communication Foundation.

  3. Aby skompilować rozwiązanie, postępuj zgodnie z instrukcjami w temacie Building the Windows Communication Foundation Samples (Tworzenie przykładów programu Windows Communication Foundation).

  4. Aby uruchomić przykład w konfiguracji pojedynczej lub między maszynami, postępuj zgodnie z instrukcjami w temacie Uruchamianie przykładów programu Windows Communication Foundation.

  5. Najpierw uruchom Service.exe, a następnie uruchom Client.exe i obejrzyj oba okna konsoli w celu uzyskania danych wyjściowych.

Podczas uruchamiania przykładu oczekiwane są następujące dane wyjściowe.

Klient:

Press enter when service is available

 > Sent chunk 1 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 > Sent chunk 2 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 > Sent chunk 3 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 > Sent chunk 4 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 > Sent chunk 5 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 > Sent chunk 6 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 > Sent chunk 7 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 > Sent chunk 8 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 > Sent chunk 9 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 > Sent chunk 10 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 < Received chunk 1 of message 5b226ad5-c088-4988-b737-6a565e0563dd
 < Received chunk 2 of message 5b226ad5-c088-4988-b737-6a565e0563dd
 < Received chunk 3 of message 5b226ad5-c088-4988-b737-6a565e0563dd
 < Received chunk 4 of message 5b226ad5-c088-4988-b737-6a565e0563dd
 < Received chunk 5 of message 5b226ad5-c088-4988-b737-6a565e0563dd
 < Received chunk 6 of message 5b226ad5-c088-4988-b737-6a565e0563dd
 < Received chunk 7 of message 5b226ad5-c088-4988-b737-6a565e0563dd
 < Received chunk 8 of message 5b226ad5-c088-4988-b737-6a565e0563dd
 < Received chunk 9 of message 5b226ad5-c088-4988-b737-6a565e0563dd
 < Received chunk 10 of message 5b226ad5-c088-4988-b737-6a565e0563dd

Serwer:

Service started, press enter to exit
 < Received chunk 1 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 < Received chunk 2 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 < Received chunk 3 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 < Received chunk 4 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 < Received chunk 5 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 < Received chunk 6 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 < Received chunk 7 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 < Received chunk 8 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 < Received chunk 9 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 < Received chunk 10 of message 867c1fd1-d39e-4be1-bc7b-32066d7ced10
 > Sent chunk 1 of message 5b226ad5-c088-4988-b737-6a565e0563dd
 > Sent chunk 2 of message 5b226ad5-c088-4988-b737-6a565e0563dd
 > Sent chunk 3 of message 5b226ad5-c088-4988-b737-6a565e0563dd
 > Sent chunk 4 of message 5b226ad5-c088-4988-b737-6a565e0563dd
 > Sent chunk 5 of message 5b226ad5-c088-4988-b737-6a565e0563dd
 > Sent chunk 6 of message 5b226ad5-c088-4988-b737-6a565e0563dd
 > Sent chunk 7 of message 5b226ad5-c088-4988-b737-6a565e0563dd
 > Sent chunk 8 of message 5b226ad5-c088-4988-b737-6a565e0563dd
 > Sent chunk 9 of message 5b226ad5-c088-4988-b737-6a565e0563dd
 > Sent chunk 10 of message 5b226ad5-c088-4988-b737-6a565e0563dd