Bagikan melalui


Saluran Pemotongan

Sampel ChunkingChannel menunjukkan bagaimana protokol kustom atau saluran berlapis dapat digunakan untuk melakukan pemotongan dan pembatalan pemotongan atas pesan berukuran besar secara sewenang-wenang.

Saat mengirim pesan berukuran besar menggunakan Windows Communication Foundation (WCF), pembatasan jumlah memori yang digunakan untuk buffer pesan tersebut sering kali diharapkan. Salah satu solusi yang memungkinkan adalah mengalirkan isi pesan (dengan asumsi bahwa sebagian besar data ada di dalam isi). Namun, beberapa protokol memerlukan buffering seluruh pesan. Olahpesan dan keamanan yang andal adalah dua contoh tersebut. Solusi lain yang memungkinkan adalah membagi pesan berukuran besar menjadi pesan dengan ukuran yang lebih kecil atau disebut juga potongan, mengirim potongan tersebut dalam satu waktu, dan menyusun ulang pesan berukuran besar di sisi penerima. Aplikasi tersebut dapat melakukan pemotongan dan pembatalan pemotongan atau dapat menggunakan saluran kustom untuk melakukannya.

Pemotongan harus selalu digunakan hanya setelah seluruh pesan yang akan dikirim telah dibangun. Saluran pemotongan harus selalu dilapisi saluran keamanan dan saluran sesi yang andal.

Catatan

Prosedur penyiapan dan petunjuk pembuatan untuk sampel ini terdapat di akhir topik ini.

Asumsi dan Batasan Saluran Pemotongan

Struktur Pesan

Saluran pemotongan mengasumsikan struktur pesan berikut untuk pesan yang akan dipotong:

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

Saat menggunakan ServiceModel, operasi kontak yang memiliki 1 parameter input mematuhi bentuk pesan ini untuk pesan input mereka. Operasi kontrak yang memiliki 1 parameter output atau nilai pengembalian juga mematuhi bentuk pesan ini untuk pesan output mereka. Berikut adalah contoh operasi tersebut:

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

    [OperationContract]
    Stream DownloadStream();

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

Sesi

Saluran pemotongan mengharuskan pesan untuk dikirim hanya sekali, dalam pengiriman pesan yang terurut (potongan). Artinya tumpukan saluran yang mendasarinya harus memiliki sesi. Sesi dapat disediakan oleh transportasi (misalnya, transportasi TCP) atau oleh saluran protokol sesi (misalnya, saluran ReliableSession).

Pengiriman dan Penerimaan Asinkron

Metode pengiriman dan penerimaan asinkron tidak diterapkan dalam versi sampel saluran pemotongan ini.

Protokol Pemotongan

Saluran pemotongan menentukan protokol yang menunjukkan awal dan akhir serangkaian potongan serta nomor urut setiap potongan. Tiga pesan contoh berikut menunjukkan pesan awal, potongan, dan akhir beserta komentar yang menjelaskan aspek utama masing-masing pesan.

Pesan Awal

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

Pesan Potongan

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

Pesan Akhir

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

Arsitektur Saluran Pemotongan

Saluran pemotongan adalah IDuplexSessionChannel yang pada tingkat tinggi mengikuti arsitektur saluran yang umum. Ada ChunkingBindingElement yang dapat membangun ChunkingChannelFactory dan ChunkingChannelListener. ChunkingChannelFactory membuat instans ChunkingChannel ketika diminta. ChunkingChannelListener membuat instans ChunkingChannel saat saluran dalam baru diterima. ChunkingChannel sendiri bertanggung jawab untuk mengirim dan menerima pesan.

Pada tingkat berikutnya ke bawah, ChunkingChannel bergantung pada beberapa komponen untuk menerapkan protokol pemotongan. Di sisi pengiriman, saluran menggunakan kustom XmlDictionaryWriter yang disebut ChunkingWriter yang melakukan pemotongan aktual. ChunkingWriter menggunakan saluran dalam secara langsung untuk mengirim potongan. Menggunakan kustom XmlDictionaryWriter memungkinkan kita mengirim potongan selagi isi pesan asli yang berukuran besar sedang ditulis. Hal ini berarti kami tidak melakukan buffer di seluruh pesan asli.

Diagram that shows the chunking channel send architecture.

Di sisi penerima, ChunkingChannel menarik pesan dari saluran dalam dan menyerahkannya ke kustom XmlDictionaryReader yang disebut ChunkingReader, yang menyusun ulang pesan asli dari potongan yang masuk. ChunkingChannel membungkus ChunkingReader ini dalam implementasi kustom Message yang disebut ChunkingMessage dan mengembalikan pesan ini ke lapisan di atas. Kombinasi ChunkingReader dan ChunkingMessage ini memungkinkan kita membatalkan potongan isi pesan asli selagi dibaca oleh lapisan di atas alih-alih harus melakukan buffer pada seluruh isi pesan asli. ChunkingReader memiliki antrean di mana ia melakukan buffer terhadap potongan masuk hingga jumlah maksimum potongan buffer yang dapat dikonfigurasi. Ketika batas maksimum ini tercapai, pembaca menunggu pesan dikosongkan dari antrean oleh lapisan di atas (hanya dengan membaca isi pesan asli) atau sampai batas waktu penerimaan maksimum tercapai.

Diagram that shows the chunking channel receive architecture.

Model Pemrograman Pemotongan

Pengembang layanan dapat menentukan pesan yang akan dipotong dengan menerapkan atribut ChunkingBehavior ke operasi dalam kontrak. Atribut mengekspos properti AppliesTo yang memungkinkan pengembang menentukan apakah pemotongan berlaku untuk pesan input, pesan output, atau keduanya. Contoh berikut menunjukkan kegunaan atribut 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);

}

Dari model pemrograman ini, ChunkingBindingElement mengompilasi daftar URI tindakan yang mengidentifikasi pesan yang akan dipotong. Tindakan setiap pesan keluar dibandingkan dengan daftar ini untuk menentukan apakah pesan harus dipotong atau dikirim secara langsung.

Menerapkan Operasi Kirim

Pada tingkat tinggi, Operasi Kirim terlebih dahulu memeriksa apakah pesan keluar harus dipotong, jika tidak, pesan akan dikirim secara langsung menggunakan saluran dalam.

Jika pesan harus dipotong, Operasi Kirim membuat ChunkingWriter baru dan memanggil WriteBodyContents pada pesan keluar dan memberinya ChunkingWriter. Kemudian ChunkingWriter melakukan pemotongan pesan (termasuk menyalin header pesan asli ke potongan pesan awal) dan mengirim potongan menggunakan saluran dalam.

Beberapa detail yang perlu diperhatikan:

  • Kirim pertama-tama memanggil ThrowIfDisposedOrNotOpened untuk memastikan CommunicationState dibuka.

  • Pengiriman disinkronkan sehingga hanya satu pesan yang dapat dikirim pada satu waktu untuk setiap sesi. Ada ManualResetEvent dengan nama sendingDone yang diatur ulang ketika pesan yang dipotong sedang dikirim. Setelah pesan potongan akhir dikirim, peristiwa ini diatur. Metode Kirim menunggu acara ini diatur sebelum mencoba mengirim pesan keluar.

  • Kirim mengunci CommunicationObject.ThisLock untuk mencegah perubahan status yang disinkronkan saat mengirim. Lihat dokumentasi CommunicationObject untuk informasi selengkapnya tentang status CommunicationObject dan mesin status.

  • Batas waktu yang diteruskan ke Kirim digunakan sebagai batas waktu untuk seluruh operasi pengiriman yang mencakup pengiriman semua potongan.

  • Desain kustom XmlDictionaryWriter dipilih untuk menghindari buffering seluruh isi pesan asli. Jika kita akan mendapatkan XmlDictionaryReader pada isi menggunakan message.GetReaderAtBodyContents seluruh isi akan di-buffer. Sebaliknya, kita memiliki kustom XmlDictionaryWriter yang diteruskan ke message.WriteBodyContents. Saat pesan memanggil WriteBase64 pada penulis, penulis mengemas potongan ke dalam pesan dan mengirimkannya menggunakan saluran dalam. WriteBase64 melakukan pemblokiran hingga potongan dikirim.

Menerapkan Operasi Menerima

Pada tingkat tinggi, operasi Terima terlebih dahulu memeriksa bahwa pesan masuk bukan null dan bahwa tindakannya adalah ChunkingAction. Jika tidak memenuhi kedua kriteria, pesan dikembalikan dalam keadaan tidak berubah dari Terima. Jika tidak, Terima membuat ChunkingReader baru dan ChunkingMessage baru dibungkus di sekitarnya (dengan memanggil GetNewChunkingMessage). Sebelum mengembalikan ChunkingMessage yang baru, Terima menggunakan utas threadpool untuk mengeksekusi ReceiveChunkLoop, yang memanggil innerChannel.Receive dalam perulangan dan menyerahkan potongan ke ChunkingReader hingga pesan potongan akhir diterima atau telah sampai pada batas waktu terima.

Beberapa detail yang perlu diperhatikan:

  • Sama halnya dengan Kirim, Terima pertama-tama memanggil ThrowIfDisposedOrNotOpened untuk memastikan CommunicationState dibuka.

  • Terima juga disinkronkan sehingga dalam satu waktu hanya satu pesan yang dapat diterima. Sinkronisasi ini sangat penting karena setelah mulai potong pesan diterima, semua pesan yang diterima berikutnya diharapkan dipotong dalam urutan potongan baru ini hingga pesan potongan akhir diterima. Terima tidak dapat menarik pesan dari saluran dalam hingga semua potongan yang merupakan milik pesan yang sedang dibatalkan potongannya diterima. Untuk mencapai hal ini, Terima menggunakan ManualResetEvent bernama currentMessageCompleted, yang diatur ketika pesan potongan akhir diterima dan diatur ulang saat pesan potongan awal baru diterima.

  • Lain halnya dengan Kirim, Terima tidak mencegah transisi status yang disinkronkan saat menerima. Misalnya, Tutup dapat dipanggil ketika menerima dan menunggu hingga penerimaan pesan asli yang tertunda selesai atau nilai batas waktu yang ditentukan tercapai.

  • Batas waktu yang diteruskan ke Terima digunakan sebagai batas waktu untuk seluruh operasi terima yang meliputi penerimaan semua potongan.

  • Jika lapisan yang mengonsumsi pesan mengonsumsi isi pesat pada tingkat yang lebih rendah daripada pesan potongan yang masuk, ChunkingReader mem-buffer potongan masuk tersebut ke batas yang ditentukan oleh ChunkingBindingElement.MaxBufferedChunks. Setelah batas tersebut tercapai, tidak akan ada lagi potongan yang ditarik dari lapisan yang lebih rendah sampai potongan yang di-buffer dikonsumsi atau batas waktu terima tercapai.

Penimpaan CommunicationObject

OnOpen

OnOpen memanggil innerChannel.Open untuk membuka saluran dalam.

OnClose

OnClose pertama-tema mengatur stopReceive ke true untuk memberi signal kepada ReceiveChunkLoop yang tertunda agar berhenti. Lalu ia akan menunggu receiveStoppedManualResetEvent, yang diatur ketika ReceiveChunkLoop berhenti. Dengan asumsi ReceiveChunkLoop berhenti dalam batas waktu yang ditentukan, OnClose memanggil innerChannel.Close dengan batas waktu yang tersisa.

OnAbort

OnAbort memanggil innerChannel.Abort untuk membatalkan saluran dalam. Jika terdapat ReceiveChunkLoop yang tertunda, pengecualian akan didapat dari panggilan innerChannel.Receive yang tertunda.

OnFaulted

ChunkingChannel tidak memerlukan perilaku khusus ketika saluran rusak, sehingga OnFaulted tidak ditimpa.

Menerapkan Pabrik Saluran

ChunkingChannelFactory bertanggung jawab untuk membuat instans ChunkingDuplexSessionChannel dan untuk transisi status bertingkat ke pabrik saluran dalam.

OnCreateChannel menggunakan pabrik saluran dalam untuk membuat saluran dalam IDuplexSessionChannel. Ia kemudian membuat ChunkingDuplexSessionChannel baru yang meneruskan saluran dalam kepadanya beserta dengan daftar tindakan pesan yang akan dipotong dan jumlah maksimum potongan yang akan di-buffer saat diterima. Daftar tindakan pesan yang akan dipotong dan jumlah maksimal potongan yang akan di-buffer adalah dua parameter yang diteruskan ke ChunkingChannelFactory dalam konstruktornya. Bagian pada ChunkingBindingElement menjelaskan asal nilai-nilai ini.

OnOpen, OnClose, OnAbort serta padanan asinkronnya memanggil metode transisi status yang sesuai pada pabrik saluran dalam.

Menerapkan Pendengar Saluran

ChunkingChannelListener adalah pembungkus di sekitar pendengar saluran dalam. Fungsi utamanya pembungkus ini adalah mendelegasikan panggilan ke pendengar saluran dalam tersebut serta membungkus saluran baru ChunkingDuplexSessionChannels yang diterima dari pendengar saluran dalam. Fungsi tersebut dilakukan di OnAcceptChannel dan OnEndAcceptChannel. Saluran dalam bersama dengan parameter lain yang dijelaskan sebelumnya diteruskan ke ChunkingDuplexSessionChannel yang baru dibuat.

Menerapkan Elemen Pengikatan dan Pengikatan

ChunkingBindingElement bertanggung jawab membuat ChunkingChannelFactory dan ChunkingChannelListener. ChunkingBindingElement memeriksa apakah T di CanBuildChannelFactory<T> dan CanBuildChannelListener<T> merupakan jenis IDuplexSessionChannel (satu-satunya saluran yang didukung oleh saluran pemotongan) dan bahwa elemen pengikatan lainnya dalam pengikatan mendukung jenis saluran ini.

BuildChannelFactory<T> pertama-tama memeriksa bahwa jenis saluran yang diminta dapat dibangun dan kemudian mendapatkan daftar tindakan pesan yang akan dipotong. Untuk informasi selengkapnya, lihat bagian berikut. Ia kemudian membuat ChunkingChannelFactory yang baru dan meneruskan pabrik saluran dalam (seperti yang dikembalikan dari context.BuildInnerChannelFactory<IDuplexSessionChannel>), daftar tindakan pesan, dan jumlah maksimum potongan ke buffer. Jumlah maksimum potongan berasal dari properti yang disebut MaxBufferedChunks diekspos oleh ChunkingBindingElement.

BuildChannelListener<T> memiliki implementasi serupa untuk membuat ChunkingChannelListener dan meneruskannya ke pendengar saluran dalam.

Ada contoh pengikatan yang disertakan dalam sampel bernama TcpChunkingBinding. Pengikatan ini terdiri dari dua elemen pengikatan: TcpTransportBindingElement dan ChunkingBindingElement. Selain mengekspos properti MaxBufferedChunks, pengikatan juga mengatur beberapa properti TcpTransportBindingElement seperti MaxReceivedMessageSize (mengaturnya menjadi ChunkingUtils.ChunkSize + 100KB byte untuk header).

TcpChunkingBinding juga mengimplementasikan IBindingRuntimePreferences dan mengembalikan true dari metode ReceiveSynchronously yang menunjukkan bahwa hanya panggilan Terima sinkron yang diimplementasikan.

Menentukan Pesan yang akan Dipotong

Saluran pemotongan hanya memotong pesan yang diidentifikasi melalui atribut ChunkingBehavior. Kelas ChunkingBehavior mengimplementasikan IOperationBehavior dan diimplementasikan dengan memanggil metode AddBindingParameter. Dalam metode ini, ChunkingBehavior memeriksa nilai properti AppliesTo (InMessage, OutMessage atau keduanya) untuk menentukan pesan yang harus dipotong. Kemudian mendapatkan tindakan dari masing-masing pesan tersebut (dari koleksi Pesan di OperationDescription) dan menambahkannya ke koleksi string yang terkandung dalam instans ChunkingBindingParameter. Kemudian menambahkan ChunkingBindingParameter ke BindingParameterCollection yang disediakan.

BindingParameterCollection ini diteruskan di dalam BindingContext ke setiap elemen pengikatan dalam pengikatan ketika elemen pengikatan tersebut membangun pabrik saluran atau pendengar saluran. Implementasi ChunkingBindingElement dari BuildChannelFactory<T> dan BuildChannelListener<T> menarik ChunkingBindingParameter dari BindingContext'BindingParameterCollection. Kumpulan tindakan yang terkandung dalam ChunkingBindingParameter kemudian diteruskan ke ChunkingChannelFactory atau ChunkingChannelListener, yang pada gilirannya meneruskannya ke ChunkingDuplexSessionChannel.

Menjalankan sampel

Untuk menyiapkan, membangun, dan menjalankan sampel

  1. Pasang ASP.NET 4.0 menggunakan perintah berikut.

    %windir%\Microsoft.NET\Framework\v4.0.XXXXX\aspnet_regiis.exe /i /enable
    
  2. Pastikan Anda telah melakukan Prosedur Penyiapan Satu Kali untuk Sampel Windows Communication Foundation.

  3. Untuk membangun solusi, ikuti instruksi dalam Membangun Sampel Windows Communication Foundation.

  4. Untuk menjalankan sampel dalam konfigurasi satu atau lintas komputer, ikuti instruksi pada Menjalankan Sampel WCF.

  5. Jalankan Service.exe terlebih dahulu, lalu jalankan Client.exe dan tonton kedua jendela konsol untuk output.

Saat menjalankan sampel, output berikut diharapkan.

Klien:

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