Sdílet prostřednictvím


Kanál s dělením dat do bloků

Ukázka ChunkingChannel ukazuje, jak se dá vlastní protokol nebo vrstvený kanál použít k vytváření bloků dat a odsunutí libovolných velkých zpráv.

Při odesílání velkých zpráv pomocí technologie Windows Communication Foundation (WCF) je často žádoucí omezit množství paměti používané k ukládání těchto zpráv do vyrovnávací paměti. Jedním z možných řešení je streamování textu zprávy (za předpokladu, že je většina dat v těle). Některé protokoly však vyžadují ukládání do vyrovnávací paměti celé zprávy. Spolehlivé zasílání zpráv a zabezpečení jsou dva takové příklady. Dalším možným řešením je rozdělit velkou zprávu na menší zprávy označované jako bloky dat, odeslat tyto bloky po jednom bloku a rozředět velkou zprávu na přijímající straně. Samotná aplikace může provést toto vytváření bloků dat a odstranění bloků dat nebo k tomu může použít vlastní kanál.

Bloky dat by se měly vždy používat až po vytvoření celé zprávy, která se má odeslat. Blokovací kanál by měl být vždy vrstvený pod kanálem zabezpečení a spolehlivým kanálem relace.

Poznámka:

Postup nastavení a pokyny k sestavení pro tuto ukázku najdete na konci tohoto tématu.

Předpoklady a omezení kanálu bloků dat

Struktura zpráv

Kanál bloků dat předpokládá následující strukturu zpráv, aby zprávy byly blokovány:

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

Při použití modelu ServiceModel operace kontraktu, které mají 1 vstupní parametr, vyhovují tomuto tvaru zprávy pro jejich vstupní zprávu. Podobně operace kontraktů, které mají 1 výstupní parametr nebo návratovou hodnotu, odpovídají tomuto tvaru zprávy pro jejich výstupní zprávu. Tady jsou příklady těchto operací:

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

    [OperationContract]
    Stream DownloadStream();

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

Přednášky

Kanál bloků dat vyžaduje, aby se zprávy doručily přesně jednou v seřazených doručení zpráv (bloků). To znamená, že základní zásobník kanálu musí být relační. Relace mohou být poskytovány přenosem (například přenosem TCP) nebo kanálem protokolu sessionful (například kanál ReliableSession).

Asynchronní odesílání a příjem

Asynchronní metody odesílání a příjmu nejsou v této verzi ukázky kanálu bloků dat implementovány.

Protokol bloků dat

Kanál bloků dat definuje protokol, který označuje začátek a konec řady bloků dat a také pořadové číslo každého bloku dat. Následující tři ukázkové zprávy ukazují počáteční, blokové a koncové zprávy s komentáři, které popisují klíčové aspekty každého.

Úvodní zpráva

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

Zpráva bloku dat

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

Ukončit zprávu

<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 bloků kanálů

Kanál pro vytváření bloků dat je takový IDuplexSessionChannel , že na vysoké úrovni se řídí typickou architekturou kanálu. Existuje, ChunkingBindingElement že může vytvořit a ChunkingChannelFactoryChunkingChannelListener. Vytvoří ChunkingChannelFactory instance ChunkingChannel , kdy se zobrazí výzva. Vytvoří ChunkingChannelListener instance ChunkingChannel , kdy je přijat nový vnitřní kanál. Samotný ChunkingChannel je zodpovědný za odesílání a přijímání zpráv.

Na další úrovni dolů ChunkingChannel spoléhá na několik komponent pro implementaci protokolu bloků dat. Na straně odeslání kanál používá vlastní XmlDictionaryWriter volaný, ChunkingWriter který provede skutečný blok dat. ChunkingWriter používá vnitřní kanál přímo k odesílání bloků dat. Použití vlastního XmlDictionaryWriter kódu nám umožňuje odesílat bloky dat, protože se píše velké tělo původní zprávy. To znamená, že neuvolníme vyrovnávací paměť celé původní zprávy.

Diagram that shows the chunking channel send architecture.

Na straně ChunkingChannel příjmu přetáhne zprávy z vnitřního kanálu a předá je vlastní XmlDictionaryReader volaný ChunkingReader, který rekonstituuje původní zprávu z příchozích bloků dat. ChunkingChannel zabalí toto ChunkingReader do vlastní Message implementace volané ChunkingMessage a vrátí tuto zprávu do vrstvy výše. Tato kombinace ChunkingReader a ChunkingMessage umožňuje de-chunkovat původní text zprávy, protože je přečtena vrstvou výše, a nemusí ukládat do vyrovnávací paměti celý původní text zprávy. ChunkingReader obsahuje frontu, ve které uloží příchozí bloky do vyrovnávací paměti až do maximálního konfigurovatelného počtu bloků dat v vyrovnávací paměti. Když dosáhnete tohoto maximálního limitu, čtenář čeká na vyprázdnění zpráv z fronty vrstvou výše (tj. čtením z původního textu zprávy) nebo až do dosažení maximálního časového limitu příjmu.

Diagram that shows the chunking channel receive architecture.

Vytváření bloků programovacího modelu

Vývojáři služeb můžou určit, které zprávy mají být blokovány, použitím atributu ChunkingBehavior na operace v rámci kontraktu. Atribut zveřejňuje AppliesTo vlastnost, která umožňuje vývojáři určit, zda se bloky dat vztahují na vstupní zprávu, výstupní zprávu nebo obojí. Následující příklad ukazuje použití atributu 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);

}

Z tohoto programovacího modelu ChunkingBindingElement zkompiluje seznam identifikátorů URI akcí, které identifikují zprávy, které mají být blokovány. Akce každé odchozí zprávy se porovná s tímto seznamem a určí, jestli má být zpráva blokována nebo odeslána přímo.

Implementace operace odeslání

Na vysoké úrovni operace Odeslání nejprve zkontroluje, jestli musí být odchozí zpráva blokovaná, a pokud ne, odešle zprávu přímo pomocí vnitřního kanálu.

Pokud zpráva musí být blokována, Send vytvoří novou ChunkingWriter a volání WriteBodyContents odchozí zprávy, která jí tuto ChunkingWriterzprávu předává . Potom ChunkingWriter provede blok zpráv (včetně kopírování původních hlaviček zpráv do počáteční zprávy bloku) a odesílá bloky dat pomocí vnitřního kanálu.

Několik podrobností stojí za zmínku:

  • Odešlete první volání ThrowIfDisposedOrNotOpened , abyste měli jistotu, CommunicationState že je otevřený.

  • Odesílání se synchronizuje, aby se pro každou relaci posílala vždy jenom jedna zpráva. Při odesílání blokované zprávy existuje ManualResetEvent název sendingDone , který se resetuje. Po odeslání zprávy koncového bloku je tato událost nastavena. Metoda Send čeká na nastavení této události, než se pokusí odeslat odchozí zprávu.

  • Odešle zámky CommunicationObject.ThisLock , aby se zabránilo změnám synchronizovaného stavu při odesílání. Další informace o stavech a stavovém počítači najdete v CommunicationObjectCommunicationObject dokumentaci.

  • Časový limit předaný odeslání se použije jako časový limit pro celou operaci odeslání, která zahrnuje odesílání všech bloků dat.

  • XmlDictionaryWriter Vlastní návrh byl zvolen, aby se zabránilo ukládání do vyrovnávací paměti celého textu původní zprávy. Kdybysme se dostali XmlDictionaryReader na tělo pomocí message.GetReaderAtBodyContents celého těla, bylo by do vyrovnávací paměti. Místo toho máme vlastní XmlDictionaryWriter , který se předává message.WriteBodyContents. Když zpráva volá WriteBase64 na zapisovači, zapisovač zabalí bloky do zpráv a odešle je pomocí vnitřního kanálu. WriteBase64 blokuje, dokud se blok nepředá.

Implementace operace příjmu

Na vysoké úrovni operace Přijmout nejprve zkontroluje, zda příchozí zpráva není null a že její akce je .ChunkingAction Pokud nesplňuje obě kritéria, zpráva se vrátí beze změny z funkce Přijmout. Jinak funkce Receive vytvoří novou ChunkingReader a novou ChunkingMessage obálku kolem ní (voláním GetNewChunkingMessage). Před vrácením této nové ChunkingMessagefunkce receive použije vlákno fondu vláken ke spuštění ReceiveChunkLoop, které volá innerChannel.Receive ve smyčce a ruce bloky dat do ChunkingReader doby, než se přijme zpráva koncového bloku dat nebo dojde k vypršení časového limitu příjmu.

Několik podrobností stojí za zmínku:

  • Stejně jako funkce Odeslat přijímat první volání ThrowIfDisposedOrNotOpened , abyste měli jistotu, že CommunicationState je otevřená.

  • Příjem je také synchronizovaný, takže z relace lze přijmout pouze jednu zprávu najednou. To je zvlášť důležité, protože jakmile se přijme počáteční zpráva bloku dat, očekává se, že všechny následné přijaté zprávy budou v této nové sekvenci bloků dat do přijetí zprávy koncového bloku. Příjem nemůže přijímat zprávy z vnitřního kanálu, dokud nebudou přijaty všechny bloky dat, které patří do zprávy, která právě probíhá. K tomuto účelu používá funkce Receive pojmenovanou ManualResetEventcurrentMessageCompleted, která je nastavena při přijetí zprávy koncového bloku a resetování při přijetí nové počáteční zprávy bloku.

  • Na rozdíl od funkce Odeslat, příjem nezabrání přechodům synchronizovaného stavu při přijímání. Při přijímání je například možné volat zavřít a čekat, až se dokončí čekání na přijetí původní zprávy nebo se dosáhne zadané hodnoty časového limitu.

  • Časový limit předaný službě Receive se používá jako časový limit pro celou operaci příjmu, která zahrnuje příjem všech bloků dat.

  • Pokud vrstva, která zprávu spotřebovává, spotřebovává text zprávy rychlostí nižší než rychlost příchozích bloků zpráv, ChunkingReader vyrovnávací paměti tyto příchozí bloky dat až do limitu určeného ChunkingBindingElement.MaxBufferedChunks. Po dosažení limitu se z nižší vrstvy nepřebírají žádné další bloky dat, dokud se nevyužívají bloky dat ve vyrovnávací paměti nebo se nedosáhne časového limitu příjmu.

Přepsání objektu CommunicationObject

OnOpen

OnOpen volání innerChannel.Open pro otevření vnitřního kanálu.

OnClose

OnClose nejprve se nastaví stopReceive tak, aby true signalizoval čekající ReceiveChunkLoop na zastavení. Pak počká na receiveStoppedManualResetEventnastavení , která se nastaví při ReceiveChunkLoop zastavení. Za předpokladu, že se ReceiveChunkLoop zastaví v zadaném časovém limitu, OnClose volání innerChannel.Close se zbývajícím časovým limitem.

Onabort

OnAbort volání innerChannel.Abort pro přerušení vnitřního kanálu. Pokud čeká na vyřízení ReceiveChunkLoop , dojde k výjimce z čekajícího innerChannel.Receive volání.

OnFaulted

Nevyžaduje ChunkingChannel zvláštní chování, pokud je kanál chybný, takže OnFaulted není přepsán.

Implementace objektu pro vytváření kanálů

Zodpovídá ChunkingChannelFactory za vytváření instancí ChunkingDuplexSessionChannel a za kaskádové přechody stavu do objektu pro vytváření vnitřních kanálů.

OnCreateChannel k vytvoření vnitřního kanálu používá továrnu IDuplexSessionChannel vnitřního kanálu. Potom vytvoří nový ChunkingDuplexSessionChannel předaný tento vnitřní kanál spolu se seznamem akcí zpráv, které mají být blokovány, a maximální počet bloků dat, které se mají ukládat do vyrovnávací paměti při příjmu. Seznam akcí zpráv, které mají být blokovány, a maximální počet bloků dat do vyrovnávací paměti jsou dva parametry předané ChunkingChannelFactory v jeho konstruktoru. Tato část ChunkingBindingElement popisuje, odkud tyto hodnoty pocházejí.

, OnOpenOnCloseOnAbort a jejich asynchronní ekvivalenty volají odpovídající metodu přechodu stavu ve vnitřní kanálu továrny.

Implementace naslouchacího procesu kanálu

Jedná se ChunkingChannelListener o obálku kolem naslouchacího procesu vnitřního kanálu. Její hlavní funkcí kromě volání delegáta na tento naslouchací proces vnitřního kanálu je zabalit nové ChunkingDuplexSessionChannels kanály přijaté z naslouchacího procesu vnitřního kanálu. To se provádí v OnAcceptChannel a OnEndAcceptChannel. Nově vytvořený kanál ChunkingDuplexSessionChannel se předává spolu s dalšími parametry, které jsme popsali dříve.

Implementace elementu vazby a vazby

ChunkingBindingElement je zodpovědný za vytvoření a ChunkingChannelFactoryChunkingChannelListener. Kontroluje ChunkingBindingElement , zda T v CanBuildChannelFactory<T> a CanBuildChannelListener<T> je typu IDuplexSessionChannel (jediný kanál podporovaný kanálem bloků dat) a že ostatní prvky vazby v vazbě podporují tento typ kanálu.

BuildChannelFactory<T> nejprve zkontroluje, jestli je možné sestavit požadovaný typ kanálu, a pak získá seznam akcí zpráv, které se mají zapsat do bloku. Další informace najdete v následující části. Potom vytvoří nové ChunkingChannelFactory předání objektu pro vytvoření vnitřního kanálu (jak je vráceno z context.BuildInnerChannelFactory<IDuplexSessionChannel>), seznamu akcí zpráv a maximálního počtu bloků dat do vyrovnávací paměti. Maximální počet bloků dat pochází z vlastnosti označované jako MaxBufferedChunks vystavená objektem ChunkingBindingElement.

BuildChannelListener<T> má podobnou implementaci pro vytvoření ChunkingChannelListener a předání vnitřního kanálu naslouchací proces.

V této ukázce je zahrnuta ukázková vazba s názvem TcpChunkingBinding. Tato vazba se skládá ze dvou prvků vazby: TcpTransportBindingElement a ChunkingBindingElement. Kromě zveřejnění MaxBufferedChunks vlastnosti vazba také nastaví některé vlastnosti TcpTransportBindingElement , například MaxReceivedMessageSize (nastaví ho na ChunkingUtils.ChunkSize + 100 kB pro hlavičky).

TcpChunkingBinding také implementuje IBindingRuntimePreferences a vrací hodnotu true z ReceiveSynchronously metody označující, že jsou implementována pouze synchronní volání receive.

Určení, které zprávy se mají blokovat

Bloky bloků kanálů blokují pouze zprávy identifikované prostřednictvím atributu ChunkingBehavior . Třída ChunkingBehavior implementuje IOperationBehavior a je implementována voláním AddBindingParameter metody. V této metodě ChunkingBehavior prozkoumá hodnotu jeho AppliesTo vlastnosti (InMessagenebo obojí) a určí, OutMessage které zprávy mají být blokovány. Pak získá akci každé z těchto zpráv (z kolekce Messages on OperationDescription) a přidá ji do kolekce řetězců obsažené v instanci ChunkingBindingParameter. Pak se ChunkingBindingParameter přidá do poskytnutého BindingParameterCollectionsouboru .

To BindingParameterCollection se předá uvnitř BindingContext každého elementu vazby v vazbě, když tento element vazby sestaví objekt pro vytváření kanálů nebo naslouchací proces kanálu. Provádění ChunkingBindingElementa BuildChannelListener<T> vytáhnutí tohoto ChunkingBindingParameterBindingContext'z těchto BindingParameterCollectionBuildChannelFactory<T> Kolekce akcí obsažených v tomto objektu ChunkingBindingParameterChunkingChannelFactory se pak předá nebo ChunkingChannelListener, která je následně předána ChunkingDuplexSessionChanneldo .

Spuštění ukázky

Nastavení, sestavení a spuštění ukázky

  1. Pomocí následujícího příkazu nainstalujte ASP.NET 4.0.

    %windir%\Microsoft.NET\Framework\v4.0.XXXXX\aspnet_regiis.exe /i /enable
    
  2. Ujistěte se, že jste pro ukázky windows Communication Foundation provedli jednorázovou instalační proceduru.

  3. Pokud chcete sestavit řešení, postupujte podle pokynů v části Sestavení ukázek Windows Communication Foundation.

  4. Pokud chcete spustit ukázku v konfiguraci s jedním nebo více počítači, postupujte podle pokynů v části Spuštění ukázek windows Communication Foundation.

  5. Nejprve spusťte Service.exe a pak spusťte Client.exe a sledujte výstup v obou oknech konzoly.

Při spuštění ukázky se očekává následující výstup.

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

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