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.
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.
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 memastikanCommunicationState
dibuka.Pengiriman disinkronkan sehingga hanya satu pesan yang dapat dikirim pada satu waktu untuk setiap sesi. Ada
ManualResetEvent
dengan namasendingDone
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 kemessage.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 memastikanCommunicationState
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
bernamacurrentMessageCompleted
, 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 olehChunkingBindingElement.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. Kemudian menunggu receiveStopped
ManualResetEvent, 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
Pasang ASP.NET 4.0 menggunakan perintah berikut.
%windir%\Microsoft.NET\Framework\v4.0.XXXXX\aspnet_regiis.exe /i /enable
Pastikan Anda telah melakukan Prosedur Penyiapan Satu Kali untuk Sampel Windows Communication Foundation.
Untuk membangun solusi, ikuti instruksi dalam Membangun Sampel Windows Communication Foundation.
Untuk menjalankan sampel dalam konfigurasi satu atau lintas komputer, ikuti instruksi pada Menjalankan Sampel WCF.
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