Segmenteringskanaal
Het chunkingChannel-voorbeeld laat zien hoe een aangepast protocol of gelaagd kanaal kan worden gebruikt om segmentering en dechunking van willekeurige grote berichten uit te voeren.
Bij het verzenden van grote berichten met WCF (Windows Communication Foundation) is het vaak wenselijk om de hoeveelheid geheugen te beperken die wordt gebruikt om deze berichten te bufferen. Een mogelijke oplossing is het streamen van de berichttekst (ervan uitgaande dat het grootste deel van de gegevens zich in de hoofdtekst bevindt). Voor sommige protocollen is echter buffering van het hele bericht vereist. Betrouwbare berichten en beveiliging zijn twee voorbeelden. Een andere mogelijke oplossing is om het grote bericht op te delen in kleinere berichten die chunks worden genoemd, deze segmenten één segment tegelijk te verzenden en het grote bericht aan de ontvangstzijde opnieuw in te delen. De toepassing zelf kan dit segmenteren en de segmenteren ongedaan maken of het kan een aangepast kanaal gebruiken om dit te doen.
Segmentering moet altijd alleen worden gebruikt nadat het hele bericht dat moet worden verzonden, is samengesteld. Een segmenteringskanaal moet altijd worden gelaagd onder een beveiligingskanaal en een betrouwbaar sessiekanaal.
Notitie
De installatieprocedure en build-instructies voor dit voorbeeld bevinden zich aan het einde van dit onderwerp.
Veronderstellingen en beperkingen van segmenteringskanaal
Berichtstructuur
In het segmenteringskanaal wordt ervan uitgegaan dat de volgende berichtstructuur voor berichten wordt gesegmenteerd:
<soap:Envelope>
<!-- headers -->
<soap:Body>
<operationElement>
<paramElement>data to be chunked</paramElement>
</operationElement>
</soap:Body>
</soap:Envelope>
Wanneer u het ServiceModel gebruikt, voldoen contractbewerkingen met één invoerparameter aan deze vorm van bericht voor hun invoerbericht. Op dezelfde manier voldoen contractbewerkingen met één uitvoerparameter of retourwaarde aan deze vorm van bericht voor hun uitvoerbericht. Hier volgen voorbeelden van dergelijke bewerkingen:
[ServiceContract]
interface ITestService
{
[OperationContract]
Stream EchoStream(Stream stream);
[OperationContract]
Stream DownloadStream();
[OperationContract(IsOneWay = true)]
void UploadStream(Stream stream);
}
Sessies
Voor het segmenteringskanaal moeten berichten exact één keer worden afgeleverd, bij geordende bezorging van berichten (segmenten). Dit betekent dat de onderliggende kanaalstack sessievol moet zijn. Sessies kunnen worden geleverd door het transport (bijvoorbeeld TCP-transport) of door een sessionful protocolkanaal (bijvoorbeeld ReliableSession-kanaal).
Asynchroon verzenden en ontvangen
Asynchrone methoden voor verzenden en ontvangen worden niet geïmplementeerd in deze versie van het segmenteringskanaalvoorbeeld.
Segmenteringsprotocol
Het segmenteringskanaal definieert een protocol dat het begin en einde van een reeks segmenten aangeeft, evenals het volgnummer van elk segment. In de volgende drie voorbeeldberichten ziet u de begin-, segment- en eindberichten met opmerkingen die de belangrijkste aspecten van elk bericht beschrijven.
Bericht starten
<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>
Segmentbericht
<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>
Eindebericht
<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>
Segmenteringskanaalarchitectuur
Het segmenteringskanaal is een IDuplexSessionChannel
kanaal dat op hoog niveau de typische kanaalarchitectuur volgt. Er is een ChunkingBindingElement
die een ChunkingChannelFactory
en een ChunkingChannelListener
. Er ChunkingChannelFactory
worden exemplaren gemaakt van ChunkingChannel
wanneer dit wordt gevraagd. Er ChunkingChannelListener
worden exemplaren gemaakt van ChunkingChannel
wanneer een nieuw binnenkanaal wordt geaccepteerd. Het ChunkingChannel
zelf is verantwoordelijk voor het verzenden en ontvangen van berichten.
Op het volgende niveau omlaag ChunkingChannel
is het afhankelijk van verschillende onderdelen om het segmenteringsprotocol te implementeren. Aan de verzendzijde gebruikt het kanaal een aangepaste XmlDictionaryWriter aangeroepen naam ChunkingWriter
die de werkelijke segmentering doet. ChunkingWriter
gebruikt het binnenste kanaal rechtstreeks om segmenten te verzenden. Met behulp van een aangepaste XmlDictionaryWriter
functie kunnen we segmenten verzenden naarmate de grote hoofdtekst van het oorspronkelijke bericht wordt geschreven. Dit betekent dat we het hele oorspronkelijke bericht niet bufferen.
Aan de ontvangstzijde ChunkingChannel
haalt u berichten van het binnenste kanaal op en geeft u ze een aangepaste XmlDictionaryReader naam ChunkingReader
, die het oorspronkelijke bericht van de binnenkomende segmenten reconstitueert. ChunkingChannel
verpakt dit ChunkingReader
in een aangepaste Message
implementatie die wordt aangeroepen ChunkingMessage
en retourneert dit bericht naar de bovenstaande laag. Met deze combinatie van ChunkingReader
en ChunkingMessage
kunnen we de oorspronkelijke berichttekst opsplitsen, omdat deze wordt gelezen door de bovenstaande laag in plaats van dat we de volledige oorspronkelijke berichttekst moeten bufferen. ChunkingReader
heeft een wachtrij waarin binnenkomende segmenten worden gebufferd tot een maximaal configureerbaar aantal gebufferde segmenten. Wanneer deze maximale limiet is bereikt, wacht de lezer totdat berichten uit de wachtrij worden verwijderd door de bovenstaande laag (dat wil gezegd door gewoon te lezen vanuit de oorspronkelijke berichttekst) of totdat de maximale time-out voor ontvangst is bereikt.
Segmenteren van programmeermodel
Serviceontwikkelaars kunnen opgeven welke berichten moeten worden gesegmenteerd door het ChunkingBehavior
kenmerk toe te passen op bewerkingen binnen het contract. Met het kenmerk wordt een AppliesTo
eigenschap weergegeven waarmee de ontwikkelaar kan opgeven of segmentering van toepassing is op het invoerbericht, het uitvoerbericht of beide. In het volgende voorbeeld ziet u het gebruik van ChunkingBehavior
het kenmerk:
[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);
}
Vanuit dit programmeermodel ChunkingBindingElement
wordt een lijst met actie-URI's gecompileerd die berichten identificeren die moeten worden gesegmenteerd. De actie van elk uitgaand bericht wordt vergeleken met deze lijst om te bepalen of het bericht moet worden gesegmenteerd of rechtstreeks moet worden verzonden.
De verzendbewerking implementeren
Op hoog niveau controleert de verzendbewerking eerst of het uitgaande bericht moet worden gesegmenteerd en, indien niet, het bericht rechtstreeks via het binnenste kanaal wordt verzonden.
Als het bericht moet worden gesegmenteerd, wordt er een nieuw ChunkingWriter
bericht gemaakt en WriteBodyContents
wordt het uitgaande bericht doorgegeven ChunkingWriter
. Vervolgens ChunkingWriter
wordt het segmenteren van het bericht (inclusief het kopiëren van oorspronkelijke berichtkoppen naar het beginsegmentbericht) en worden segmenten verzonden met behulp van het binnenste kanaal.
Enkele details die u moet noteren:
Verzend eerste oproepen
ThrowIfDisposedOrNotOpened
om ervoor te zorgen dat deCommunicationState
app wordt geopend.Verzenden wordt gesynchroniseerd, zodat er slechts één bericht tegelijk kan worden verzonden voor elke sessie. Er is een
ManualResetEvent
naamsendingDone
die opnieuw wordt ingesteld wanneer een gesegmenteerd bericht wordt verzonden. Zodra het eindsegmentbericht is verzonden, wordt deze gebeurtenis ingesteld. De verzendmethode wacht tot deze gebeurtenis is ingesteld voordat het uitgaande bericht wordt verzonden.Verzenden vergrendelt de
CommunicationObject.ThisLock
gesynchroniseerde statuswijzigingen tijdens het verzenden. Raadpleeg de CommunicationObject documentatie voor meer informatie over CommunicationObject statussen en statuscomputers.De time-out die is doorgegeven aan Verzenden, wordt gebruikt als de time-out voor de hele verzendbewerking, waaronder het verzenden van alle segmenten.
Het aangepaste XmlDictionaryWriter ontwerp is gekozen om te voorkomen dat de volledige oorspronkelijke berichttekst wordt gebufferd. Als we een XmlDictionaryReader op het lichaam zouden krijgen met behulp van
message.GetReaderAtBodyContents
het hele lichaam zou bufferen. In plaats daarvan hebben we een aangepaste XmlDictionaryWriter die wordt doorgegeven aanmessage.WriteBodyContents
. Terwijl het bericht WriteBase64 aanroept op de schrijver, verpakt de schrijver segmenten in berichten en verzendt deze met behulp van het binnenste kanaal. WriteBase64-blokken totdat het segment wordt verzonden.
De ontvangstbewerking implementeren
Op hoog niveau controleert de ontvangstbewerking eerst of het binnenkomende bericht niet null
is en of de actie ervan de ChunkingAction
. Als het niet aan beide criteria voldoet, wordt het bericht ongewijzigd geretourneerd van Ontvangen. Anders wordt in Receive een nieuwe ChunkingReader
en een nieuwe ChunkingMessage
omhulsel gemaakt (door aan te roepen GetNewChunkingMessage
). Voordat u die nieuwe ChunkingMessage
retourneert, gebruikt Receive een threadpoolthread om uit te voeren ReceiveChunkLoop
, die aanroept innerChannel.Receive
in een lus en segmenten afgeeft aan het ChunkingReader
eindsegmentbericht wordt ontvangen of de time-out voor ontvangst wordt bereikt.
Enkele details die u moet noteren:
Net als Verzenden, eerste oproepen
ThrowIfDisposedOrNotOpened
ontvangen om ervoor te zorgen dat deCommunicationState
wordt geopend.Ontvangen wordt ook gesynchroniseerd, zodat slechts één bericht tegelijk kan worden ontvangen vanuit de sessie. Dit is vooral belangrijk omdat zodra een beginsegmentbericht is ontvangen, alle volgende ontvangen berichten naar verwachting chunks binnen deze nieuwe segmentreeks zijn totdat een eindsegmentbericht wordt ontvangen. Ontvangen kan geen berichten ophalen uit het binnenste kanaal totdat alle segmenten die deel uitmaken van het bericht dat momenteel wordt gedesegmenteerd, zijn ontvangen. Hiervoor gebruikt Ontvangen een
ManualResetEvent
benoemde naamcurrentMessageCompleted
, die wordt ingesteld wanneer het eindsegmentbericht wordt ontvangen en opnieuw wordt ingesteld wanneer een nieuw beginsegmentbericht wordt ontvangen.In tegenstelling tot Verzenden voorkomt Ontvangen niet dat gesynchroniseerde statusovergangen tijdens ontvangst worden voorkomen. Sluiten kan bijvoorbeeld worden aangeroepen tijdens ontvangst en wacht totdat het ontvangen van het oorspronkelijke bericht is voltooid of de opgegeven time-outwaarde is bereikt.
De time-out die aan Ontvangst is doorgegeven, wordt gebruikt als time-out voor de volledige ontvangstbewerking, waaronder het ontvangen van alle segmenten.
Als de laag die het bericht verbruikt, de berichttekst met een snelheid lager is dan de snelheid van binnenkomende segmentberichten, buffert de
ChunkingReader
binnenkomende segmenten tot de limiet die is opgegeven doorChunkingBindingElement.MaxBufferedChunks
. Zodra deze limiet is bereikt, worden er geen segmenten meer uit de onderste laag gehaald totdat een gebufferd segment wordt verbruikt of de ontvangsttime-out wordt bereikt.
Onderdrukkingen van CommunicationObject
OnOpen
OnOpen
aanroepen innerChannel.Open
om het binnenste kanaal te openen.
OnClose
OnClose
wordt eerst ingesteld stopReceive
om aan te true
geven dat de in behandeling zijnde ReceiveChunkLoop
stop moet worden uitgevoerd. Het wacht vervolgens op de receiveStopped
ManualResetEvent, die is ingesteld wanneer ReceiveChunkLoop
stopt. Uitgaande van de stops binnen de opgegeven time-out, OnClose
worden aanroepen ReceiveChunkLoop
innerChannel.Close
met de resterende time-out uitgevoerd.
OnAbort
OnAbort
roept innerChannel.Abort
om het binnenste kanaal af te breken. Als er een in behandeling ReceiveChunkLoop
is, krijgt deze een uitzondering van de oproep die in behandeling is innerChannel.Receive
.
OnFaulted
Er ChunkingChannel
is geen speciaal gedrag vereist wanneer er een fout op het kanaal wordt uitgevoerd, dus OnFaulted
niet wordt overschreven.
Channel Factory implementeren
Het ChunkingChannelFactory
is verantwoordelijk voor het maken van exemplaren van ChunkingDuplexSessionChannel
en voor trapsgewijze statusovergangen naar de fabriek van het binnenkanaal.
OnCreateChannel
maakt gebruik van de fabriek van het binnenkanaal om een IDuplexSessionChannel
binnenste kanaal te maken. Vervolgens wordt er een nieuw ChunkingDuplexSessionChannel
doorgegeven aan dit binnenkanaal, samen met de lijst met berichtacties die moeten worden gesegmenteerd en het maximum aantal segmenten dat bij ontvangst moet worden gebufferd. De lijst met berichtacties die moeten worden gesegmenteerd en het maximum aantal segmenten dat moet worden gebufferd, zijn twee parameters die in de constructor worden doorgegeven ChunkingChannelFactory
. In de sectie over ChunkingBindingElement
deze waarden wordt beschreven waar deze waarden vandaan komen.
De OnOpen
, OnClose
en OnAbort
hun asynchrone equivalenten roepen de bijbehorende statusovergangsmethode aan op de inner channel factory.
Kanaallistener implementeren
Het ChunkingChannelListener
is een wrapper rond een inner channel listener. De belangrijkste functie, naast het delegeren van aanroepen naar die interne kanaallistener, is om nieuwe ChunkingDuplexSessionChannels
rond kanalen te verpakken die door de listener van het binnenste kanaal worden geaccepteerd. Dit gebeurt in OnAcceptChannel
en OnEndAcceptChannel
. Het zojuist gemaakte ChunkingDuplexSessionChannel
kanaal wordt doorgegeven, samen met de andere parameters die eerder zijn beschreven.
Bindingselement en binding implementeren
ChunkingBindingElement
is verantwoordelijk voor het maken van de ChunkingChannelFactory
en ChunkingChannelListener
. De ChunkingBindingElement
controles of T in CanBuildChannelFactory
<T> en CanBuildChannelListener
<T> van het type IDuplexSessionChannel
is (het enige kanaal dat wordt ondersteund door het segmenteringskanaal) en dat de andere bindingselementen in de binding dit kanaaltype ondersteunen.
BuildChannelFactory
<T> controleert eerst of het aangevraagde kanaaltype kan worden gebouwd en krijgt vervolgens een lijst met berichtacties die moeten worden gesegmenteerd. Zie de volgende onderwerpen voor meer informatie. Vervolgens wordt er een nieuw ChunkingChannelFactory
doorgegeven aan de binnenkanaalfactory (zoals geretourneerd), context.BuildInnerChannelFactory<IDuplexSessionChannel>
de lijst met berichtacties en het maximum aantal segmenten dat moet worden gebufferd. Het maximum aantal segmenten is afkomstig van een eigenschap die wordt aangeroepen MaxBufferedChunks
door de ChunkingBindingElement
.
BuildChannelListener<T>
heeft een vergelijkbare implementatie voor het maken ChunkingChannelListener
en doorgeven van de interne kanaallistener.
Er is een voorbeeldbinding opgenomen in dit voorbeeld met de naam TcpChunkingBinding
. Deze binding bestaat uit twee bindingselementen: TcpTransportBindingElement
en ChunkingBindingElement
. Naast het beschikbaar maken van de MaxBufferedChunks
eigenschap, stelt de binding ook enkele van de TcpTransportBindingElement
eigenschappen in, zoals MaxReceivedMessageSize
(stelt deze in op ChunkingUtils.ChunkSize
+ 100 KB-bytes voor headers).
TcpChunkingBinding
implementeert en retourneert IBindingRuntimePreferences
true van de ReceiveSynchronously
methode die aangeeft dat alleen de synchrone ontvangstaanroepen worden geïmplementeerd.
Bepalen welke berichten moeten worden gesegmenteerde
Het segmenteringskanaal segmenteert alleen de berichten die worden geïdentificeerd via het ChunkingBehavior
kenmerk. De ChunkingBehavior
klasse implementeert IOperationBehavior
en wordt geïmplementeerd door de AddBindingParameter
methode aan te roepen. In deze methode onderzoekt de ChunkingBehavior
waarde van AppliesTo
de eigenschap (InMessage
OutMessage
of beide) om te bepalen welke berichten moeten worden gesegmenteerd. Vervolgens wordt de actie van elk van deze berichten (uit de verzameling Berichten op OperationDescription
) opgehaald en toegevoegd aan een tekenreeksverzameling in een exemplaar van ChunkingBindingParameter
. Vervolgens wordt dit ChunkingBindingParameter
toegevoegd aan de opgegeven BindingParameterCollection
.
Dit BindingParameterCollection
wordt doorgegeven aan BindingContext
elk bindingselement in de binding wanneer dat bindingselement de kanaalfactory of de kanaallistener bouwt. De ChunkingBindingElement
implementatie van BuildChannelFactory<T>
en BuildChannelListener<T>
haal dit ChunkingBindingParameter
uit de BindingContext'
s BindingParameterCollection
. De verzameling acties in de groep ChunkingBindingParameter
wordt vervolgens doorgegeven aan de ChunkingChannelFactory
of ChunkingChannelListener
, die op zijn beurt doorgeeft aan de ChunkingDuplexSessionChannel
.
Het voorbeeld uitvoeren
Het voorbeeld instellen, compileren en uitvoeren
Installeer ASP.NET 4.0 met de volgende opdracht.
%windir%\Microsoft.NET\Framework\v4.0.XXXXX\aspnet_regiis.exe /i /enable
Zorg ervoor dat u de eenmalige installatieprocedure voor de Windows Communication Foundation-voorbeelden hebt uitgevoerd.
Volg de instructies in Het bouwen van de Windows Communication Foundation-voorbeelden om de oplossing te bouwen.
Als u het voorbeeld wilt uitvoeren in een configuratie met één of meerdere computers, volgt u de instructies in Het uitvoeren van de Windows Communication Foundation-voorbeelden.
Voer eerst Service.exe uit en voer vervolgens Client.exe uit en bekijk beide consolevensters voor uitvoer.
Bij het uitvoeren van het voorbeeld wordt de volgende uitvoer verwacht.
Client:
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
Server:
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