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.
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.
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 ChunkingWriter
komunikat . 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 domessage.WriteBodyContents
elementu . 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 ChunkingMessage
elementu funkcja Odbieraj używa wątku do wykonania ReceiveChunkLoop
wą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ę, żeCommunicationState
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
nazwycurrentMessageCompleted
, 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 przezChunkingBindingElement.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
, OnClose
OnAbort
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 (InMessage
lub 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 ChunkingBindingParameter
klasy . Następnie dodaje go ChunkingBindingParameter
do podanego BindingParameterCollection
elementu .
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 ChunkingBindingElement
BuildChannelFactory<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
Zainstaluj ASP.NET 4.0 przy użyciu następującego polecenia.
%windir%\Microsoft.NET\Framework\v4.0.XXXXX\aspnet_regiis.exe /i /enable
Upewnij się, że wykonano procedurę instalacji jednorazowej dla przykładów programu Windows Communication Foundation.
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).
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.
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