Segmenteringskanal
ChunkingChannel-exemplet visar hur ett anpassat protokoll eller en kanal i lager kan användas för segmentering och dechunking av godtyckligt stora meddelanden.
När du skickar stora meddelanden med Hjälp av Windows Communication Foundation (WCF) är det ofta önskvärt att begränsa mängden minne som används för att buffa dessa meddelanden. En möjlig lösning är att strömma meddelandetexten (förutsatt att huvuddelen av data finns i brödtexten). Vissa protokoll kräver dock buffring av hela meddelandet. Tillförlitliga meddelanden och säkerhet är två exempel. En annan möjlig lösning är att dela upp det stora meddelandet i mindre meddelanden som kallas segment, skicka segmenten ett segment i taget och återskapa det stora meddelandet på mottagarsidan. Själva programmet kan utföra denna segmentering och de-chunking eller använda en anpassad kanal för att göra det.
Segmentering bör alltid endast användas när hela meddelandet som ska skickas har konstruerats. En segmenteringskanal bör alltid ligga under en säkerhetskanal och en tillförlitlig sessionskanal.
Kommentar
Installationsproceduren och bygginstruktionerna för det här exemplet finns i slutet av det här avsnittet.
Segmentering av kanalantaganden och begränsningar
Meddelandestruktur
Segmenteringskanalen förutsätter följande meddelandestruktur för att meddelanden ska segmenteras:
<soap:Envelope>
<!-- headers -->
<soap:Body>
<operationElement>
<paramElement>data to be chunked</paramElement>
</operationElement>
</soap:Body>
</soap:Envelope>
När du använder ServiceModel följer kontraktsåtgärder som har en indataparameter den här formen av meddelande för deras indatameddelande. På samma sätt överensstämmer kontraktsåtgärder som har 1 utdataparameter eller returvärde med den här formen av meddelande för utdatameddelandet. Följande är exempel på sådana åtgärder:
[ServiceContract]
interface ITestService
{
[OperationContract]
Stream EchoStream(Stream stream);
[OperationContract]
Stream DownloadStream();
[OperationContract(IsOneWay = true)]
void UploadStream(Stream stream);
}
Sessioner
Segmenteringskanalen kräver att meddelanden levereras exakt en gång, i ordnad leverans av meddelanden (segment). Det innebär att den underliggande kanalstacken måste vara sessionskänslig. Sessioner kan tillhandahållas via transporten (till exempel TCP-transport) eller av en sessionskänslig protokollkanal (till exempel ReliableSession-kanalen).
Asynkron skicka och ta emot
Asynkrona metoder för att skicka och ta emot implementeras inte i den här versionen av segmenteringskanalexemplet.
Segmenteringsprotokoll
Segmenteringskanalen definierar ett protokoll som anger början och slutet av en serie segment samt sekvensnumret för varje segment. Följande tre exempelmeddelanden visar start-, segment- och slutmeddelanden med kommentarer som beskriver de viktigaste aspekterna av var och en.
Startmeddelande
<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>
Segmentmeddelande
<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>
Slutmeddelande
<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>
Arkitektur för segmenteringskanal
Segmenteringskanalen är en IDuplexSessionChannel
kanal som på hög nivå följer den typiska kanalarkitekturen. Det finns en ChunkingBindingElement
som kan skapa en ChunkingChannelFactory
och en ChunkingChannelListener
. Skapar ChunkingChannelFactory
instanser av ChunkingChannel
när den uppmanas att göra det. ChunkingChannelListener
Skapar instanser av ChunkingChannel
när en ny inre kanal accepteras. Själv ChunkingChannel
ansvarar för att skicka och ta emot meddelanden.
Vid nästa nivå nedåt ChunkingChannel
förlitar du dig på flera komponenter för att implementera segmenteringsprotokollet. På sändningssidan använder kanalen ett anpassat XmlDictionaryWriter namn ChunkingWriter
som utför den faktiska segmenteringen. ChunkingWriter
använder den inre kanalen direkt för att skicka segment. Med hjälp av en anpassad XmlDictionaryWriter
kan vi skicka ut segment när den stora brödtexten i det ursprungliga meddelandet skrivs. Det innebär att vi inte buffras hela det ursprungliga meddelandet.
På mottagarsidan ChunkingChannel
hämtar du meddelanden från den inre kanalen och ger dem till en anpassad XmlDictionaryReader med namnet ChunkingReader
, som återskapar det ursprungliga meddelandet från inkommande segment. ChunkingChannel
omsluter detta ChunkingReader
i en anpassad Message
implementering med namnet ChunkingMessage
och returnerar det här meddelandet till lagret ovan. Den här kombinationen av ChunkingReader
och ChunkingMessage
gör att vi kan dela upp den ursprungliga meddelandetexten eftersom den läss av lagret ovan i stället för att behöva buffras hela den ursprungliga meddelandetexten. ChunkingReader
har en kö där den buffrar inkommande segment upp till ett maximalt konfigurerbart antal buffrade segment. När den här maximala gränsen har nåtts väntar läsaren på att meddelanden ska tömmas från kön av lagret ovan (dvs. genom att bara läsa från den ursprungliga meddelandetexten) eller tills den maximala tidsgränsen för mottagning har uppnåtts.
Segmentering av programmeringsmodell
Tjänstutvecklare kan ange vilka meddelanden som ska segmenteras genom att använda ChunkingBehavior
attributet för åtgärder i kontraktet. Attributet exponerar en AppliesTo
egenskap som gör att utvecklaren kan ange om segmentering ska gälla för indatameddelandet, utdatameddelandet eller båda. I följande exempel visas användningen av ChunkingBehavior
attributet:
[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);
}
Från den här programmeringsmodellen ChunkingBindingElement
kompilerar en lista över åtgärds-URI:er som identifierar meddelanden som ska segmenteras. Åtgärden för varje utgående meddelande jämförs med den här listan för att avgöra om meddelandet ska segmenteras eller skickas direkt.
Implementera sändningsåtgärden
På hög nivå kontrollerar åtgärden Skicka först om det utgående meddelandet måste segmenteras och skickar meddelandet om det inte är det direkt med den inre kanalen.
Om meddelandet måste vara segmenterat skapar Send en ny ChunkingWriter
och anropar WriteBodyContents
det utgående meddelandet som skickar det här ChunkingWriter
. Sedan ChunkingWriter
segmenterar meddelandet (inklusive kopierar ursprungliga meddelanderubriker till startsegmentmeddelandet) och skickar segment med hjälp av den inre kanalen.
Några detaljer som är värda att notera:
Skicka de första anropen
ThrowIfDisposedOrNotOpened
för att se till att ärCommunicationState
öppnad.Sändningen synkroniseras så att endast ett meddelande kan skickas i taget för varje session. Det finns ett
ManualResetEvent
namnsendingDone
som återställs när ett segmenterat meddelande skickas. När slutsegmentmeddelandet har skickats anges den här händelsen. Metoden Skicka väntar tills den här händelsen har angetts innan den försöker skicka det utgående meddelandet.Skicka låser
CommunicationObject.ThisLock
för att förhindra synkroniserade tillståndsändringar när du skickar. Mer information om CommunicationObject tillstånd och tillståndsdator finns i dokumentationenCommunicationObject.Tidsgränsen som skickas till Skicka används som tidsgräns för hela sändningsåtgärden, vilket innefattar att skicka alla segment.
Den anpassade XmlDictionaryWriter designen valdes för att undvika att buffring av hela den ursprungliga meddelandetexten. Om vi skulle få en XmlDictionaryReader på kroppen med hela
message.GetReaderAtBodyContents
kroppen skulle buffrade. I stället har vi en anpassad XmlDictionaryWriter som skickas tillmessage.WriteBodyContents
. När meddelandet anropar WriteBase64 på skrivaren paketar författaren upp segment i meddelanden och skickar dem med hjälp av den inre kanalen. WriteBase64 blockerar tills segmentet skickas.
Implementera mottagningsåtgärden
På hög nivå kontrollerar åtgärden Ta emot först att det inkommande meddelandet inte null
är och att dess åtgärd är ChunkingAction
. Om det inte uppfyller båda kriterierna returneras meddelandet oförändrat från Ta emot. Annars skapar Receive en ny ChunkingReader
och en ny ChunkingMessage
omsluten runt den (genom att anropa GetNewChunkingMessage
). Innan du returnerar den nya ChunkingMessage
använder Receive en trådpooltråd för att köra ReceiveChunkLoop
, som anropar innerChannel.Receive
i en loop och lämnar ut segment tills ChunkingReader
slutsegmentmeddelandet tas emot eller tidsgränsen för mottagningen nås.
Några detaljer som är värda att notera:
Som Skicka, Ta emot första anrop
ThrowIfDisposedOrNotOpened
för att seCommunicationState
till att är Öppnad.Ta emot synkroniseras också så att endast ett meddelande kan tas emot i taget från sessionen. Detta är särskilt viktigt eftersom när ett meddelande om startsegment tas emot förväntas alla efterföljande mottagna meddelanden vara segment i den nya segmentsekvensen tills ett slutsegmentmeddelande tas emot. Det går inte att hämta meddelanden från den inre kanalen förrän alla segment som tillhör meddelandet som för närvarande tas bort från segment tas emot. För att åstadkomma detta använder Receive ett
ManualResetEvent
med namnetcurrentMessageCompleted
, som anges när slutsegmentmeddelandet tas emot och återställs när ett nytt meddelande om startsegment tas emot.Till skillnad från Skicka förhindrar Mottagning inte synkroniserade tillståndsövergångar när du tar emot. Stäng kan till exempel anropas när du tar emot och väntar tills det väntande mottagandet av det ursprungliga meddelandet har slutförts eller det angivna tidsgränsvärdet har uppnåtts.
Tidsgränsen som skickas till Ta emot används som tidsgräns för hela mottagningsåtgärden, vilket inkluderar att ta emot alla segment.
Om det lager som använder meddelandet förbrukar meddelandetexten med en hastighet som är lägre än hastigheten för inkommande segmentmeddelanden, buffrar de inkommande segmenten
ChunkingReader
upp till den gräns som anges avChunkingBindingElement.MaxBufferedChunks
. När den gränsen har nåtts hämtas inga fler segment från det nedre lagret förrän antingen ett buffrat segment förbrukas eller tidsgränsen för att ta emot nås.
CommunicationObject åsidosättningar
OnOpen
OnOpen
anrop innerChannel.Open
för att öppna den inre kanalen.
OnClose
OnClose
anger stopReceive
först för att true
signalera att väntande ReceiveChunkLoop
ska stoppas. Den väntar sedan på receiveStopped
ManualResetEvent, som anges när ReceiveChunkLoop
stoppar. Förutsatt att stoppen ReceiveChunkLoop
inom den angivna tidsgränsen, OnClose
anropar innerChannel.Close
med den återstående tidsgränsen.
OnAbort
OnAbort
anrop innerChannel.Abort
för att avbryta den inre kanalen. Om det finns ett väntande ReceiveChunkLoop
hämtar det ett undantag från det väntande innerChannel.Receive
anropet.
OnFaulted
ChunkingChannel
Kräver inte särskilt beteende när kanalen är fel, så OnFaulted
den är inte åsidosatt.
Implementera Channel Factory
ChunkingChannelFactory
Ansvarar för att skapa instanser av ChunkingDuplexSessionChannel
och för sammanhängande tillståndsövergångar till den inre kanalfabriken.
OnCreateChannel
använder den inre kanalfabriken för att skapa en IDuplexSessionChannel
inre kanal. Sedan skapas en ny ChunkingDuplexSessionChannel
som skickar den här inre kanalen tillsammans med listan över meddelandeåtgärder som ska segmenteras och det maximala antalet segment som ska buffras vid mottagandet. Listan över meddelandeåtgärder som ska segmenteras och det maximala antalet segment som ska bufferas är två parametrar som skickas till ChunkingChannelFactory
i konstruktorn. I avsnittet om ChunkingBindingElement
beskrivs var dessa värden kommer ifrån.
, OnOpen
OnClose
och OnAbort
deras asynkrona motsvarigheter anropar motsvarande tillståndsövergångsmetod på den inre kanalfabriken.
Implementera kanallyssnare
ChunkingChannelListener
Är en omslutning runt en inre kanallyssnare. Dess huvudsakliga funktion, förutom delegatanrop till den inre kanallyssnaren, är att omsluta nya ChunkingDuplexSessionChannels
runt kanaler som accepteras från den inre kanallyssnaren. Detta görs i OnAcceptChannel
och OnEndAcceptChannel
. Den nyskapade ChunkingDuplexSessionChannel
skickas den inre kanalen tillsammans med de andra parametrar som beskrivits tidigare.
Implementera bindningselement och bindning
ChunkingBindingElement
ansvarar för att skapa ChunkingChannelFactory
och ChunkingChannelListener
. Kontrollerar ChunkingBindingElement
om T i<CanBuildChannelFactory
T> och CanBuildChannelListener
<T> är av typen IDuplexSessionChannel
(den enda kanal som stöds av segmenteringskanalen) och att de andra bindningselementen i bindningen stöder den här kanaltypen.
BuildChannelFactory
<T> kontrollerar först att den begärda kanaltypen kan skapas och hämtar sedan en lista över meddelandeåtgärder som ska segmenteras. Mer information finns i följande avsnitt: Sedan skapas en ny ChunkingChannelFactory
som skickar den till den inre kanalfabriken (som returneras från context.BuildInnerChannelFactory<IDuplexSessionChannel>
), listan över meddelandeåtgärder och det maximala antalet segment som ska bufferas. Det maximala antalet segment kommer från en egenskap som kallas MaxBufferedChunks
exponerad av ChunkingBindingElement
.
BuildChannelListener<T>
har en liknande implementering för att skapa ChunkingChannelListener
och skicka den till den inre kanalens lyssnare.
Det finns en exempelbindning som ingår i det här exemplet med namnet TcpChunkingBinding
. Den här bindningen består av två bindningselement: TcpTransportBindingElement
och ChunkingBindingElement
. Förutom att exponera MaxBufferedChunks
egenskapen anger bindningen även några av TcpTransportBindingElement
egenskaperna, till exempel MaxReceivedMessageSize
(anger den till ChunkingUtils.ChunkSize
+ 100 KB byte för rubriker).
TcpChunkingBinding
implementerar IBindingRuntimePreferences
och returnerar också true från ReceiveSynchronously
metoden som anger att endast synkrona Mottagningsanrop implementeras.
Avgöra vilka meddelanden som ska segmenteras
Segmenteringskanalen segmenterar endast de meddelanden som identifieras via attributet ChunkingBehavior
. Klassen ChunkingBehavior
implementerar IOperationBehavior
och implementeras genom att anropa AddBindingParameter
metoden. I den här metoden ChunkingBehavior
undersöker värdet för dess AppliesTo
egenskap (InMessage
eller OutMessage
båda) för att avgöra vilka meddelanden som ska segmenteras. Den hämtar sedan åtgärden för vart och ett av dessa meddelanden (från samlingen OperationDescription
Meddelanden på ) och lägger till den i en strängsamling som finns i en instans av ChunkingBindingParameter
. Sedan läggs detta ChunkingBindingParameter
till i den angivna BindingParameterCollection
.
Detta BindingParameterCollection
skickas inuti BindingContext
till varje bindningselement i bindningen när bindningselementet skapar kanalfabriken eller kanallyssnaren. ' ChunkingBindingElement
s implementering av BuildChannelFactory<T>
och BuildChannelListener<T>
dra detta ChunkingBindingParameter
ur BindingContext'
s BindingParameterCollection
. Samlingen med åtgärder som finns i ChunkingBindingParameter
skickas sedan till ChunkingChannelFactory
eller ChunkingChannelListener
, som i sin tur skickar den till ChunkingDuplexSessionChannel
.
Köra exemplet
Så här konfigurerar du, skapar och kör exemplet
Installera ASP.NET 4.0 med hjälp av följande kommando.
%windir%\Microsoft.NET\Framework\v4.0.XXXXX\aspnet_regiis.exe /i /enable
Kontrollera att du har utfört engångsinstallationsproceduren för Windows Communication Foundation-exempel.
Skapa lösningen genom att följa anvisningarna i Skapa Windows Communication Foundation-exempel.
Om du vill köra exemplet i en konfiguration med en eller flera datorer följer du anvisningarna i Köra Windows Communication Foundation-exempel.
Kör Service.exe först, kör sedan Client.exe och titta på båda konsolfönstren för utdata.
När du kör exemplet förväntas följande utdata.
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