This article is now a part of the Windows SDK.
Today's post is a short article about the mechanics of enabling transport streaming. This is a complement of other articles that describe how streaming works. Like the post on transport quotas, the formatting may be a little weird because I'm pasting these in from Word and it's HTML tag soup. Update: same content, less soup.
How to: Enabling Streaming
Windows Communication Foundation (WCF) transports support two modes for transferring messages. Buffered transfers hold the entire message in a memory buffer until the transfer is complete. Streamed transfers only buffer the headers and expose the message body as a stream, from which smaller portions can be read at a time. Streamed transfers can improve the scalability of a service by eliminating the need for large memory buffers. Whether changing the transfer mode will improve scalability depends on the size of the messages being transferred. Large message sizes favor using streamed transfers.
By default, the HTTP, TCP/IP and named pipe transports use buffered transfers. This document describes how to switch these transports from a buffered to streamed transfer mode and the consequences of doing so.
Enabling Streamed Transfers
Selecting between buffered and streamed transfer modes is done on the binding element of the transport. The binding element has a TransferMode property that can be set to Buffered, Streamed, StreamedRequest, or StreamedResponse. Setting the transfer mode to Streamed enables streaming communication in both directions. Setting the transfer mode to StreamedRequest or StreamedResponse enables streaming communication in the indicated direction only.
The BasicHTTPBinding exposes the TransferMode property on the binding. For other transports, you will need to create a custom binding to set the transfer mode.
The following code snippet shows setting the TransferMode property to streaming through code:
TcpTransportBindingElement transport = new TcpTransportBindingElement();transport.TransferMode = TransferMode.Streamed;BinaryMessageEncodingBindingElement encoder = new BinaryMessageEncodingBindingElement();CustomBinding binding = new CustomBinding(encoder, transport);
The following code snippet shows setting the TransferMode property to streaming through configuration:
<customBinding> <binding name="streamingBinding"> <binaryMessageEncoding /> <tcpTransport transferMode=”Streamed” /> </binding></customBinding>
The decision to use either buffered or streamed transfers is a local decision of the endpoint. For HTTP transports, the transfer mode does not propagate across a connection, or to proxy servers and other intermediaries. Setting the transfer mode is not reflected in the description of the service interface. After generating a proxy to a service, you will need to edit the configuration file for services intended to be used with streamed transfers to set the mode. For TCP and named pipe transports, the transfer mode is propagated as a policy assertion.
Restrictions of Streamed Transfers
Using the streamed transfer mode causes the runtime to enforce additional restrictions.
Differences Between Buffered and Streamed Transfers
Changing the transfer mode from buffered to streamed also changes the native channel shape of the TCP and named pipe transports. For buffered transfers, the native channel shape is IDuplexSessionChannel. For streamed transfers, the native channels are IRequestChannel and IReplyChannel. This means that sessionful service contracts do not compose with transport streaming.
Next time: Five Pitfalls of the Channel Model, Part 1
Over the last two days we've looked at building a simple client and server that directly use the channel model. You can check out both the code and what gets sent over the network in those articles. I concluded yesterday with the question, what if you don't want people to be able to simple read your messages off of the network? Well, the answer is that we can provide confidentiality through our security mechanisms.
Security is orthogonal to most of the channel model. In fact, we can conceal the messages going between our client and server just by adding an additional binding element. If you're using one of the standard bindings, most already have security enabled by default. Let's look at how this changes our binding section
WindowsStreamSecurityBindingElement security = new WindowsStreamSecurityBindingElement(); security.ProtectionLevel = ProtectionLevel.EncryptAndSign; TextMessageEncodingBindingElement encoder = new TextMessageEncodingBindingElement(); TcpTransportBindingElement transport = new TcpTransportBindingElement(); transport.TransferMode = TransferMode.Streamed; CustomBinding binding = new CustomBinding(security, encoder, transport);
Remember, the code for handling channels and messages is not being changed. We're simply placing a binding element for security into our channel stack. Let's look at what goes over the network now. If you're running across two machines, you'll need matching accounts on both ends for this to work.
IP 192.168.0.3.3765 > 192.168.0.2.5555: tcp 36 0x0000: 4500 004c 25dc 4000 8006 537a c0a8 0003 E..L%.@...Sz.... 0x0010: c0a8 0002 0eb5 15b3 3766 407a 3d49 e40c ........7f@z=I.. 0x0020: 5018 ffff 51ee 0000 0001 0001 0102 1b6e P...Q..........n 0x0030: 6574 2e74 6370 3a2f 2f31 3932 2e31 3638 et.tcp://192.168 0x0040: 2e30 2e32 3a35 3535 352f 0303 .0.2:5555/.. IP 192.168.0.3.3765 > 192.168.0.2.5555: tcp 23 0x0000: 4500 003f 25dd 4000 8006 5386 c0a8 0003 E..?%.@...S..... 0x0010: c0a8 0002 0eb5 15b3 3766 409e 3d49 e40c ........7f@.=I.. 0x0020: 5018 ffff e281 0000 0915 6170 706c 6963 P.........applic 0x0030: 6174 696f 6e2f 6e65 676f 7469 6174 65 ation/negotiate
At this point the two sides perform the NTLM tango. Not pictured.
IP 192.168.0.3.3765 > 192.168.0.2.5555: tcp 280 0x0000: 4500 0140 25e5 4000 8006 527d c0a8 0003 E..@%.@...R}.... 0x0010: c0a8 0002 0eb5 15b3 3766 41c1 3d49 e4dc ........7fA.=I.. 0x0020: 5018 ff2f 9246 0000 1401 0000 0100 0000 P../.F.......... 0x0030: b42e 39bf d71a 9504 0200 0000 9308 ced0 ..9............. 0x0040: 3fa4 7604 1849 abc8 ed2b 4322 f60b e7f1 ?.v..I...+C".... 0x0050: 71af b8d8 b0d3 e5c7 64e5 cd50 d636 dc77 q.......d..P.6.w 0x0060: 671f 2252 c8c4 0901 ebb1 63fd 5f5d c107 g."R......c._].. 0x0070: fb66 ff30 5630 bd52 e28e 86b2 7444 e2ec .f.0V0.R....tD.. 0x0080: 6498 1d8e e3e4 cc89 dee2 0041 8113 d766 d..........A...f 0x0090: 1e78 8acd 9475 b467 6726 2767 b4ca 7371 .x...u.gg&'g..sq 0x00a0: cb95 8255 9523 4fa6 3137 c4cd f822 d399 ...U.#O.17...".. 0x00b0: 8b7a 80e5 2d37 b601 4f94 d4b5 1965 457c .z..-7..O....eE| 0x00c0: 15c0 0dfa 95ec 6949 4d4e 89c2 b6d5 c727 ......iIMN.....' 0x00d0: 38ca ec72 9906 d5b3 bd85 cf4a 90e6 d42a 8..r.......J...* 0x00e0: aee1 5f47 bbb7 e64c c639 24e1 ba97 fa38 .._G...L.9$....8 0x00f0: 3e92 d5e2 e7d7 942d 04fd c04e 8239 d7a9 >......-...N.9.. 0x0100: d658 5dd6 1c26 17a0 bbca 3c04 4768 4a9a .X]..&....<.GhJ. 0x0110: 647a 57ed 53e6 bfc6 3ee4 00a0 649e 6e2f dzW.S...>...d.n/ 0x0120: 71a9 1651 3990 615b fe32 35ed 7c5a ab4c q..Q9.a[.25.|Z.L 0x0130: a103 65b4 e221 7b39 abde 2303 4d88 f612 ..e..!{9..#.M... IP 192.168.0.2.5555 > 192.168.0.3.3765: tcp 217 0x0000: 4500 0101 3507 4000 8006 439a c0a8 0002 E...5.@...C..... 0x0010: c0a8 0003 15b3 0eb5 3d49 e4f1 3766 42da ........=I..7fB. 0x0020: 5018 fda0 8249 0000 d500 0000 0100 0000 P....I.......... 0x0030: a46c ae8f 2337 5721 0200 0000 68c0 0e39 .l..#7W!....h..9 0x0040: c4b0 017a c110 210a f5f8 815f e5e0 49e0 ...z..!...._..I. 0x0050: 51fe eab2 0c85 0e2e ab51 4048 541f 090a Q........Q@HT... 0x0060: f7a2 ac7d 44b6 818f d3ac 8359 c9c8 7f9b ...}D......Y.... 0x0070: 7fc0 0f60 74ce 1e00 8501 b537 2c95 8041 ...`t......7,..A 0x0080: e74a b409 4756 757d ec74 6fbe 53e5 a908 .J..GVu}.to.S... 0x0090: 66cf a5cb f3ae b097 8eb0 bd7f 0fe6 be79 f..............y 0x00a0: 2076 9438 3024 78ee 1beb b5b8 2f5b a339 .v.80$x...../[.9 0x00b0: bfd6 63f8 0d54 28f8 d219 7c9a 1246 ea11 ..c..T(...|..F.. 0x00c0: 0408 b04f 0410 37ad bf54 6b6a 5886 f707 ...O..7..TkjX... 0x00d0: c14b 193b 3153 0644 5613 bdc6 7199 e219 .K.;1S.DV...q... 0x00e0: 4e76 b6d2 6050 f8f5 1a6f 4945 36e5 030d Nv..`P...oIE6... 0x00f0: 6b56 5572 dc05 947a bcb4 d638 9a79 b12c kVUr...z...8.y., 0x0100: 68 h
You'll just have to believe me that those two segments contain the same messages that we saw yesterday. The whole point after all is that we didn't want people to be able to read the messages on the wire. You can see that the sizes of the messages match up though, with an additional 20 bytes of overhead. Try modifying the client and server with the new binding to prove that the programs really still work exactly the same except for what appears on the network.
Next time: How to: Enabling Streaming
Writing a server that directly uses the channel model is not much more difficult than writing a client application. You'll need to grab a copy of yesterday's client application if you want someone that can to talk to your server.
Very few people are actually going to write a server application using the channel model. This is what happens behind the scenes when you call new ServiceHost(typeof(MyWebService)).
using System; using System.ServiceModel; using System.ServiceModel.Channels; class Server { static void Main(string[] args) { TextMessageEncodingBindingElement encoder = new TextMessageEncodingBindingElement(); TcpTransportBindingElement transport = new TcpTransportBindingElement(); transport.TransferMode = TransferMode.Streamed; CustomBinding binding = new CustomBinding(encoder, transport); Uri address = new Uri("net.tcp://localhost:5555/"); IChannelListener<IReplyChannel> listener = binding.BuildChannelListener<IReplyChannel>(address); listener.Open(); while (listener.WaitForChannel(TimeSpan.FromMinutes(5))) { IReplyChannel channel = listener.AcceptChannel(); channel.Open(); IRequestContext context = channel.ReceiveRequest(); Message request = context.RequestMessage; Console.Out.WriteLine(request.Headers.Action); if (request.Headers.Action == "hello") { Message reply = Message.CreateMessage(listener.MessageVersion, "wcf"); context.Reply(reply); } request.Close(); context.Close(); channel.Close(); } listener.Close(); } }
The client and server have to use compatible binding so we don't have a lot of choice there. The procedure for the rest of the server is that we
Now, let's look at what gets sent over the wire when we have both the client and server in place.
IP 192.168.0.3.3756 > 192.168.0.2.5555: tcp 0 0x0000: 4500 0030 2215 4000 8006 575d c0a8 0003 E..0".@...W].... 0x0010: c0a8 0002 0eac 15b3 7ee6 0cbe 0000 0000 ........~....... 0x0020: 7002 ffff 51c6 0000 0204 05b4 0101 0402 p...Q........... IP 192.168.0.2.5555 > 192.168.0.3.3756: tcp 0 0x0000: 4500 0030 347c 4000 8006 44f6 c0a8 0002 E..04|@...D..... 0x0010: c0a8 0003 15b3 0eac 02ec 2eb3 7ee6 0cbf ............~... 0x0020: 7012 ffff 2016 0000 0204 05b4 0101 0402 p...............
We have life on both sides of the connection! From now on, I'm going to just call out the segments with content and skip the rest of the TCP traffic that makes up a conversation.
IP 192.168.0.3.3756 > 192.168.0.2.5555: tcp 37 0x0000: 4500 004d 2217 4000 8006 573e c0a8 0003 E..M".@...W>.... 0x0010: c0a8 0002 0eac 15b3 7ee6 0cbf 02ec 2eb4 ........~....... 0x0020: 5018 ffff 21e7 0000 0001 0001 0102 1b6e P...!..........n 0x0030: 6574 2e74 6370 3a2f 2f31 3932 2e31 3638 et.tcp://192.168 0x0040: 2e30 2e32 3a35 3535 352f 0303 0c .0.2:5555/... IP 192.168.0.3.3756 > 192.168.0.2.5555: tcp 260 0x0000: 4500 012c 221a 4000 8006 565c c0a8 0003 E..,".@...V\.... 0x0010: c0a8 0002 0eac 15b3 7ee6 0ce5 02ec 2eb5 ........~....... 0x0020: 5018 fffe f365 0000 3c73 3a45 6e76 656c P....e..<s:Envel 0x0030: 6f70 6520 786d 6c6e 733a 733d 2268 7474 ope.xmlns:s="htt 0x0040: 703a 2f2f 7777 772e 7733 2e6f 7267 2f32 p://www.w3.org/2 0x0050: 3030 332f 3035 2f73 6f61 702d 656e 7665 003/05/soap-enve 0x0060: 6c6f 7065 2220 786d 6c6e 733a 613d 2268 lope".xmlns:a="h 0x0070: 7474 703a 2f2f 7777 772e 7733 2e6f 7267 ttp://www.w3.org 0x0080: 2f32 3030 352f 3038 2f61 6464 7265 7373 /2005/08/address 0x0090: 696e 6722 3e3c 733a 4865 6164 6572 3e3c ing"><s:Header>< 0x00a0: 613a 4163 7469 6f6e 2073 3a6d 7573 7455 a:Action.s:mustU 0x00b0: 6e64 6572 7374 616e 643d 2231 223e 6865 nderstand="1">he 0x00c0: 6c6c 6f3c 2f61 3a41 6374 696f 6e3e 3c61 llo</a:Action><a 0x00d0: 3a54 6f20 733a 6d75 7374 556e 6465 7273 :To.s:mustUnders 0x00e0: 7461 6e64 3d22 3122 3e6e 6574 2e74 6370 tand="1">net.tcp 0x00f0: 3a2f 2f31 3932 2e31 3638 2e30 2e32 3a35 ://192.168.0.2:5 0x0100: 3535 352f 3c2f 613a 546f 3e3c 2f73 3a48 555/</a:To></s:H 0x0110: 6561 6465 723e 3c73 3a42 6f64 792f 3e3c eader><s:Body/>< 0x0120: 2f73 3a45 6e76 656c 6f70 653e /s:Envelope> IP 192.168.0.2.5555 > 192.168.0.3.3756: tcp 197 0x0000: 4500 00ed 3481 4000 8006 4434 c0a8 0002 E...4.@...D4.... 0x0010: c0a8 0003 15b3 0eac 02ec 2eb6 7ee6 0dea ............~... 0x0020: 5018 fed5 8235 0000 3c73 3a45 6e76 656c P....5..<s:Envel 0x0030: 6f70 6520 786d 6c6e 733a 733d 2268 7474 ope.xmlns:s="htt 0x0040: 703a 2f2f 7777 772e 7733 2e6f 7267 2f32 p://www.w3.org/2 0x0050: 3030 332f 3035 2f73 6f61 702d 656e 7665 003/05/soap-enve 0x0060: 6c6f 7065 2220 786d 6c6e 733a 613d 2268 lope".xmlns:a="h 0x0070: 7474 703a 2f2f 7777 772e 7733 2e6f 7267 ttp://www.w3.org 0x0080: 2f32 3030 352f 3038 2f61 6464 7265 7373 /2005/08/address 0x0090: 696e 6722 3e3c 733a 4865 6164 6572 3e3c ing"><s:Header>< 0x00a0: 613a 4163 7469 6f6e 2073 3a6d 7573 7455 a:Action.s:mustU 0x00b0: 6e64 6572 7374 616e 643d 2231 223e 7763 nderstand="1">wc 0x00c0: 663c 2f61 3a41 6374 696f 6e3e 3c2f 733a f</a:Action></s: 0x00d0: 4865 6164 6572 3e3c 733a 426f 6479 2f3e Header><s:Body/> 0x00e0: 3c2f 733a 456e 7665 6c6f 7065 3e </s:Envelope>
You can learn a lot by looking at what happens under the covers. The first segment you see contains our destination address. Then, you can see the SOAP message that the client sends. Finally, you can see the SOAP message that the server responds with. What if you don't want people to be able to look at your messages like this? That's the subject for tomorrow's conclusion to the series.
Next time: WCF Hello World, Part 3: Enabling Security
During the past month, we've taken a tour of the most important parts of the channel model. We haven't really seen channels in action yet though. This series of articles is a quick introduction to directly using the channel model to send and receive messages. If you want to try these programs out, you'll need the February CTP of WCF.
Very few people are actually going to write a client application using the channel model. This is what happens behind the scenes when you call new ChannelFactory<IMyWebService>().
When I run these examples, I use two machines so that I can capture the packets going over the network. You can run the programs on a single machine by just changing the client address to localhost. In any case, you'll probably need to change the address to something besides the value I'm using unless you happen to have the same local network setup.
using System; using System.ServiceModel; using System.ServiceModel.Channels; class Client { static void Main(string[] args) { TextMessageEncodingBindingElement encoder = new TextMessageEncodingBindingElement(); TcpTransportBindingElement transport = new TcpTransportBindingElement(); transport.TransferMode = TransferMode.Streamed; CustomBinding binding = new CustomBinding(encoder, transport); IChannelFactory<IRequestChannel> factory = binding.BuildChannelFactory<IRequestChannel>(); factory.Open(); EndpointAddress address = new EndpointAddress("net.tcp://192.168.0.2:5555/"); IRequestChannel channel = factory.CreateChannel(address); channel.Open(); Message request = Message.CreateMessage(factory.MessageVersion, "hello"); Message reply = channel.Request(request); Console.Out.WriteLine(reply.Headers.Action); reply.Close(); channel.Close(); factory.Close(); } }
This is pretty much the minimal client application possible. We're going to send a message with an action but no body to some other machine using TCP/IP. I picked a text encoding so that we can look at the messages as they are being sent. I also turned streaming on so that the request-reply channel shape is being used.
The procedure is that we
All of these concepts have appeared in past articles.
Although this client obviously isn't going to do much without a server, let's take a look at what gets sent over the wire if we run it.
IP 192.168.0.3.3755 > 192.168.0.2.5555: tcp 0 0x0000: 4500 0030 1c40 4000 8006 5d32 c0a8 0003 E..0.@@...]2.... 0x0010: c0a8 0002 0eab 15b3 c360 0a62 0000 0000 .........`.b.... 0x0020: 7002 ffff 0fa9 0000 0204 05b4 0101 0402 p............... IP 192.168.0.3.3755 > 192.168.0.2.5555: tcp 0 0x0000: 4500 0030 1c64 4000 8006 5d0e c0a8 0003 E..0.d@...]..... 0x0010: c0a8 0002 0eab 15b3 c360 0a62 0000 0000 .........`.b.... 0x0020: 7002 ffff 0fa9 0000 0204 05b4 0101 0402 p............... IP 192.168.0.3.3755 > 192.168.0.2.5555: tcp 0 0x0000: 4500 0030 1cb7 4000 8006 5cbb c0a8 0003 E..0..@...\..... 0x0010: c0a8 0002 0eab 15b3 c360 0a62 0000 0000 .........`.b.... 0x0020: 7002 ffff 0fa9 0000 0204 05b4 0101 0402 p...............
This is a trace of the TCP traffic arriving at the destination computer. Each of these segments represents a connection attempt to the server. We can knock, but nobody is home to answer. Let's write a listener program now so that the client can do more than just time out.
Next time: WCF Hello World, Part 2: Building a Server
We often take for granted that we can send a message off to any computer in the world and that it will get there eventually with very little intervention on our part. Well, most of the time it gets there. One of the overlooked players in this process is the addressing of every computer on the network. The current standard for addressing comes from version 4 of the Internet Protocol, also known as IPv4 or simply IP. There are new standards that attempt to fix some problems with IPv4, mainly the lack of available addresses. However, we've been "imminently about to run out of IPv4 addresses" every year for the last 10 years, which is roughly half of IPv4's lifetime.
An IPv4 address is a 32-bit number that is used both for identifying computers and routing messages. The most common way of writing an address is as a dotted decimal. The address is first broken up into four 8-bit chunks. Each chunk is then converted to a decimal number and separated from the previous chunk in writing by a dot. This is where addresses that look like 192.168.0.1 come from.
The original addressing scheme divided the available space into a number of classes depending on the first few bits of the address. These were called class A, B, and C addresses, and classes were used for performing routing. Inside these classes are several blocks of reserved addresses that cannot be used for computers connected to the Internet. The address in the previous paragraph is one of those reserved addresses. It's a private network address, which means that anyone can use it for their purposes, but there's no way to talk to a computer with such an address without going through some intermediary. Other commonly seen reserved addresses include the loopback addresses, which all have 127 as the first chunk. Anything sent to one of these addresses doesn't actually go anywhere. Instead, a message sent to a loopback address just gets sent back to the same computer. Several years later, it became possible to route messages independently of class, making the original class system obsolete.
The reason I wrote today's post though is to talk about one of the lesser-known kinds of addresses. All of the previously mentioned addresses refer to a single computer, called a unicast address. There are two other groups of addresses, sometimes called class D and E. Class D addresses run from 224.0.0.0 to 239.255.255.255. These are called multicast addresses because they are a way to send a single message to multiple computers. Multicasting is done using the concepts of groups. Computers join a multicast group associated with one of the addresses to indicate that they want to receive a copy of each packet intended for the group. Using multicasting cuts down on the total required bandwidth because many of the hops will only transmit one copy of the message regardless of how many computers are ultimately listening.
Most network applications can only be used with unicast addresses. There's no real support for multicasting in WCF, although you can write your own multicast transport as long as you don't use any layered channels that assume that there's only one computer on the other end. Reliable messaging and security do not work well with multicasting. I've recently been looking at ways to make WCF more multicast friendly. Although I don't know if this work will ever appear in the product, it's been fun exploring this different world of networking.
Next time: WCF Hello World, Part 1: Building a Client
I decided to use today's post to plug the outstanding new "Atlas" release. "Atlas" is a development platform for building AJAX-style web applications in a slick and efficient manner. Obviously, since WCF is the communication technology of choice, there is an excellent integration story here as well. Steve Maine down the hall has been working ceaselessly to make sure that "Atlas" and WCF are seamless. Now that this release is out, it will be good to have him back again after he recovers from all the wild partying at Mix '06.
By the way, I've had considerable code name envy of some of these other projects since Indigo received it's official naming. Aside from the goofy quotation marks, "Atlas" is a respectable name to be associated with. We get to be a part of
and now the more recent
It's actually getting longer. That's just not fair.
Continuing from yesterday's article about IChannelFactory, today we're looking at the server side of the equation for creating channels. Before we dive into the code for IChannelListener, let's go over a few of the differences between listeners and factories.
Factories, and the channels that they create, have an immediate mode of interaction. As long as you're idle, factories and sending channels typically aren't doing any work. When you push work onto a factory, it then starts processing that work. In contrast, listeners and receiving channels have a queuing mode of interaction. Some outside force triggers the creation of messages and connections. You can't force an event to happen. Some mechanism, ranging from a simple polling loop to a highly efficient asynchronous callback system, pools events into a queue. I would not recommend going with the polling approach so don't say later that I didn't warn you about that. From time-to-time, you need to go and take an event off of that queue.
Beyond the consequence of listeners being more complicated than factories, listeners also have a different concept of addressing. On the factory, the address of the channel is specified at the time of channel creation. On the listener, channel creation time is way too late to be specifying where to listen for connections. Properties like the location are set at listener creation time and propagate to the channels created from that listener.
One last difference is in the relationship between managers and their channels. If you close a channel listener, all of its listening channels continue to work. If you close a channel factory, all of its channels get closed as well. There's some motivation here from the difference in usage patterns between listeners and factories, but this behavior is something that's still in the process of being finalized.
If all this asymmetry feels confusing, take the time to picture how an event producer works in contrast with how an event consumer works. When you're done picturing, let's dive into the code.
public interface IChannelListener : IChannelManager, ICommunicationObject, IDisposable { Identity Identity { get; } Uri Uri { get; } IAsyncResult BeginWaitForChannel(TimeSpan timeout, AsyncCallback callback, object state); bool EndWaitForChannel(IAsyncResult result); bool WaitForChannel(TimeSpan timeout); } public interface IChannelListener<TChannel> : IChannelListener where TChannel : class, IChannel { TChannel AcceptChannel(); TChannel AcceptChannel(TimeSpan timeout); IAsyncResult BeginAcceptChannel(AsyncCallback callback, object state); IAsyncResult BeginAcceptChannel(TimeSpan timeout, AsyncCallback callback, object state); TChannel EndAcceptChannel(IAsyncResult result); }
By this point, there should be nothing puzzling in these interfaces, except possibly what this creature called Identity does. That story is for another day. As a quick exercise, compare the methods on IChannelListener<IInputChannel> with the methods on IInputChannel. The channel listener is a meta-input channel that receives input channels in the same way that input channels receive messages. If conceptual purity was more important than shipping software, we could spend the time making the two classes have exactly the same set of methods. It doesn't make as much sense to do the same thing with IChannelFactory<IOutputChannel> and IOutputChannel, but if you wanted to stretch things a little, you could.
Next time: What's the Difference Between Close and Abort?
When we last left the IChannel interface, there was a brief introduction of the concept of a channel manager. A channel manager creates channels either for the client or server. For those of you familiar with BSD sockets, we have the IChannelListener interface for producing channels that passively open and the IChanelFactory interface for producing channels that actively open. For those of you not familiar with BSD sockets, it would be good to learn more about them because WCF mimics socket behavior in a lot of respects, but a channel listener produces channels that wait for a connection to occur and a channel factory produces channels that go out and create connections. Both of these interfaces derive from the base interface, IChannelManager.
public interface IChannelManager : ICommunicationObject { MessageVersion MessageVersion { get; } string Scheme { get; } T GetProperty<T>() where T : class; }
Let's start by looking at the easier of the two sides, the client. On the client side, when you create a channel, you want to go out and establish a connection with some existing server. You have to know where and how you're connecting, and this is only a limited time offer to make a connection. If the server doesn't respond for some reason, then you want to just give up and return an error. This makes the IChannelFactory case very simple because the only behavior we need is to be able to go off and create a connection. Here are the two interfaces to describe that behavior. Yes, there are two interfaces for just one concept because of reasons you probably don't care about most of the time.
public interface IChannelFactory : IChannelManager, ICommunicationObject, IDisposable { } public interface IChannelFactory<TChannel> : IChannelFactory { TChannel CreateChannel(EndpointAddress to); TChannel CreateChannel(EndpointAddress to, Uri via); }
The generic version is the one you want so pretend like that's all that exists if you can to make your life simpler.
As you can see, there's not a lot of conceptual overhead in creating a channel. You always have to provide a destination address and you're given the option of providing a via. Jump back to this quick description of the via for a refresher if you're unsure why you need both of these methods. Note that all this is doing is creating your channel. The actual act of opening the channel or sending data is all done through the IChannel interface.
Tomorrow, we'll look at IChannelListener, which is the twin to IChannelFactory.
Next time: Creating Channels for Listening
The final player in the drama between IRequestChannel and IReplyChannel is the link connecting requests to replies. As we saw last time, when a server receives a request, it doesn't have direct access to the message that the client sent. Instead, the message is wrapped up inside an IRequestContext, which contains both the message and something that looks a lot like a channel for sending the response.
public interface IRequestContext : IDisposable{ Message RequestMessage { get; } void Abort(); IAsyncResult BeginReply(Message message, AsyncCallback callback, object state); IAsyncResult BeginReply(Message message, TimeSpan timeout, AsyncCallback callback, object state); void Close(); void Close(TimeSpan timeout); void EndReply(IAsyncResult result); void Reply(Message message); void Reply(Message message, TimeSpan timeout);}
The RequestMessage property gives you access to the client request for processing. However, there are some tricky semantics in play here. In WCF, resources like messages and channels need to be closed when you're done using them. Every one of these objects should have the concept of an owner assigned to it so that it's clear who is responsible for ultimately calling Close. When you get the message out of a request context, you become the owner of that message and need to close it when you're done. Exposing the message as a property really doesn't communicate this responsibility. Try to remember that you need to treat a message coming from a request context just like you treat a message coming directly from a channel.
The remaining methods of IRequestContext should be very familiar to you by now. They're a subset of the standard methods on an IOutputChannel along with a few of the necessary methods from ICommunicationObject. Think of a request context as a short-lived channel where you first read one message, write one message, and then close the channel.
Next time: Creating Channels for Talking
In the past day or two I've had people mention that posts sometimes aren't showing up in their aggregator. I've checked the RSS and ATOM feeds, and they seem to be as correct as they normally are. I also get update notifications in Outlook and IE just fine. The only thing I can think of is that it's the muddle of tags in the transport quota article, but I can't find anything wrong there as well. If you're having problems with the feeds or think you know what's gone wrong, let me know.
I realize that the people having this problem probably aren't reading this right now.
After a short break, let's continue looking at the request-reply message pattern. In the previous article in this series, we saw that the IRequestChannel interface for the client side of the pattern was almost identical to the IOutputChannel interface for the client side of the one-way message and duplex patterns. I hinted that there was also a strange similarity between IReplyChannel and the IInputChannel interface for the server side. Today, we'll see that similarity but introduce the slight twist of the pattern.
The key aspect of the request-reply message pattern is that it correlates the messages between the client and server. For every message that the client sends, the server must send back exactly one message in reply. The client can't send another request until the first request is finished, and the server can only communicate with the client in response to a request.
On the client side, we saw that this correlation appears in the welding together of a Send and Receive method into a single Request method. Let's look at the equivalent part of the server side.
public interface IReplyChannel : IChannel, ICommunicationObject, IDisposable { EndpointAddress LocalAddress { get; } IAsyncResult BeginReceiveRequest(AsyncCallback callback, object state); IAsyncResult BeginReceiveRequest(TimeSpan timeout, AsyncCallback callback, object state); IAsyncResult BeginTryReceiveRequest(TimeSpan timeout, AsyncCallback callback, object state); IAsyncResult BeginWaitForRequest(TimeSpan timeout, AsyncCallback callback, object state); IRequestContext EndReceiveRequest(IAsyncResult result); bool EndTryReceiveRequest(IAsyncResult result, out IRequestContext context); bool EndWaitForRequest(IAsyncResult result); IRequestContext ReceiveRequest(); IRequestContext ReceiveRequest(TimeSpan timeout); bool TryReceiveRequest(TimeSpan timeout, out IRequestContext context); bool WaitForRequest(TimeSpan timeout); }
There's certainly a striking resemblance looking back at IInputChannel. We have the same three varieties of Receive, and the same flavors for synchronous/asynchronous and explicit/default timeouts. However, all of our messages have been replaced by instances of an IRequestContext interface. This is the equivalent server side correlation of the input and output messages. Conceptually, this is a much bigger leap than just changing the methods around because we've introduced this correlation abstraction that acts a lot like a channel. The request context not only encompasses the message, but it's also the mechanism to use for sending the reply.
We'll wrap up this flurry of posts about channel shapes on Monday by examining this channel that isn't, IRequestContext.
Next time: It Makes the WWW Go Round, Part 3: IRequestContext
From time to time I wonder who comes to read this blog and why, but those kinds of questions are very hard to answer from the meager server data I can view. Such is life when you're on a hosted service, although life is not bad at blogs.msdn.com. Some small fraction of the total viewership actually leads to a referer being collected, which I can look at. Word keeps trying to correct the spelling of that word to referrer, an assistance technology sadly missing at the time the HTTP standard was written. In any case, it's somewhat interesting to look at the referrals from search engines that include the search terms in the URL.
Evidently, quite a few people are interested in knowing what the maximum size can be for a SOAP message. While Google thinks this blogs holds the answer, that will be much more true after today's post.
Technically speaking, the maximum, MAXIMUM size of a SOAP message that we can process in WCF is 9,223,372,036,854,775,807 bytes. You'll have to knock off a few hundred bytes to account for the headers. Those are included in that limit along with the message body.
There is the small caveat that that is really only the limit for messages sent using streamed transfers. If you're using buffered transfer, then we have to allocate some kind of backing store for the message. Most implementations are going to use an array for that store, limiting the total possible message size to 2,147,483,647 bytes. Custom channel authors can go crazy here although I think it's unlikely that many will choose to take advantage of this extensibility point.
However, even if you happen to have that much memory, that allocation isn't going to succeed on a 32-bit platform. There's a bit less than 2 GB of addressable space in your process and some of that will be taken or fragmented into smaller pieces. Actually, you're doing pretty good most of the time if you can allocate an array of a few hundred megabytes in size with a 32-bit operating system.
All of that of course is contingent on you permitting your program to receive messages of such sizes. The transport quota settings overview I posted a few days ago talks about the MaxReceivedMessageSize quota and how to set it. Unless you increase the size of that setting, you'll never see a message larger than the default maximum of 64 KB. We don't limit the maximum size of outgoing message, although it is very possible that someone on the other end of the connection will take exception if you choose to send oversized messages.
The final WCF message exchange pattern that I'm going to look at is the request-reply version of two-way communication. You may be familiar with the request-reply model as the native way to think about HTTP communication.
Request-reply connections can communicate in both directions, but one of the sides is always the understood speaker. Initially, the client gets to speak by making a request. Then, the server gets to speak by making a response. Unless the client asks for something, the server can't send it any data. And, if the client is in the process of making one request, it can't make another until the first request completes. There's a correlation between the messages sent between the two sides.
The client side is implemented by the IRequestChannel interface, which if you were paying attention yesterday, looks almost identical to the IOutputChannel interface. This should not be unexpected if you've been looking at the channel shape pictures included in each of these posts.
public interface IRequestChannel : IChannel, ICommunicationObject, IDisposable { EndpointAddress RemoteAddress { get; } Uri Via { get; } IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state); IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state); Message EndRequest(IAsyncResult result); Message Request(Message message); Message Request(Message message, TimeSpan timeout); }
The key difference between IRequestChannel and IOutputChannel (besides the fact that the methods are named Request instead of Send) is that the Request method both sends a message and returns a message back. This is the enforcement of the request-reply pattern in action. We've already paired up your request with the reply to make it look like the two operations occurred as an indivisible whole. On the client side, this all looks very nice.
Tomorrow, we'll look at the server side where there's still the flavor of IInputChannel, but the request-reply pattern is much more apparent.
Next time: It Makes the WWW Go Round, Part 2: IReplyChannel
Yesterday, we got a look at the reader side of the one-way communication pattern, which is implemented using IInputChannel. Today, we're looking at the writer side of that pattern.
IOutputChannel bears a striking resemblance to IInputChannel but with only one of the three varieties of methods that we saw previously. I won't repeat all of the references again, so go back to yesterday's post if you need a copy.
public interface IOutputChannel : IChannel, ICommunicationObject, IDisposable { EndpointAddress RemoteAddress { get; } Uri Via { get; } IAsyncResult BeginSend(Message message, AsyncCallback callback, object state); IAsyncResult BeginSend(Message message, TimeSpan timeout, AsyncCallback callback, object state); void EndSend(IAsyncResult result); void Send(Message message); void Send(Message message, TimeSpan timeout); }
We just have the imperative variety of Send because it doesn't really make sense to TryToSend a message or to WaitToSend a message. That's a little asymmetry between sending and receiving, but there's no reason to put those extra methods in if they don't do anything useful. We do offer both synchronous and asynchronous flavors of Send, as well as explicit timeout and default timeout flavors. I say this a lot, but I'll say it again: use the version with explicit timeouts.
One other difference that you might have noticed is that in addition to the RemoteAddress, which is where messages sent through this channel will end up, is that there is a Via property. The via is the first hop that a message should take on its way to the remote address. In most standard Internet scenarios, the via is the same as the final address. You only need to change this when you're doing some kind of manual routing. I'll have more to say about endpoints and vias in the future.
And remember, as a totally free bonus, by combining an IInputChannel and IOutputChannel, we've produced an IDuplexChannel that implements one of our two-way communication patterns. Here's that diagram again.
It really is as simple as the picture shows.
Next time: It Makes the WWW Go Round, Part 1: IRequestChannel
Last week I introduced the different kinds of channel shapes that are available with WCF. This week I'm going to take a look at the interfaces for working with those channel shapes in code. Today and tomorrow are going to be the one-way channel shape interfaces, IInputChannel and IOutputChannel. You get the two-way duplex channel shape entirely for free. A duplex channel is just the combination of an input and output channel with no additional methods. The two-way request-reply channel shape will take up another few days.
Here's the one-way channel shape illustration again from the channel shape article. Today, we'll look at the reader side, which is represented by IInputChannel.
Let's jump right in to look at the interface. As a reminder, we've already had some exposure to IChannel, ICommunicationObject, and the Message class if you need a reference.
public interface IInputChannel : IChannel, ICommunicationObject, IDisposable { EndpointAddress LocalAddress { get; } IAsyncResult BeginReceive(AsyncCallback callback, object state); IAsyncResult BeginReceive(TimeSpan timeout, AsyncCallback callback, object state); IAsyncResult BeginTryReceive(TimeSpan timeout, AsyncCallback callback, object state); IAsyncResult BeginWaitForMessage(TimeSpan timeout, AsyncCallback callback, object state); Message EndReceive(IAsyncResult result); bool EndTryReceive(IAsyncResult result, out Message message); bool EndWaitForMessage(IAsyncResult result); Message Receive(); Message Receive(TimeSpan timeout); bool TryReceive(TimeSpan timeout, out Message message); bool WaitForMessage(TimeSpan timeout); }
Aside from the LocalAddress property, which tells us the location this channel corresponds to, the methods are all variations on the idea of receiving a message. For the most part, you'll be using the Receive(timeout) variety, in either its asynchronous or synchronous flavor. Go back to the overview of working with timeouts if you're wondering why you should be specifying a timeout.
The other two varieties are useful in specialized situations. Use TryReceive when you think it's likely that a message will not arrive within the allotted time. TryReceive will give you back a boolean return value that tells you whether a message arrived and gives you the message itself back as an out parameter. Use WaitForMessage when you want to prime the channel for another reader to pull the message out with no waiting. Like TryReceive, WaitForMessage lets you know whether a message arrived using the return value. Both TryReceive and WaitForMessage come in synchronous and asynchronous flavors as well.
Next time: ...Can Also Come Out (IOutputChannel)
The results are in and it looks like I'll be going full steam ahead with the plan to post totally unedited and uncensored prerelease SDK documentation for your trial consumption. I'd like to remind everyone that this is basically straight from my word processor to you, so there may very well be errors grammatical, technical, and otherwise. The formatting may be a little weird because I'm pasting these in from Word and it's HTML tag soup. Today's topic is the mysterious world of transport quotas.
Transport quotas are a policy mechanism for deciding when a connection is consuming excessive resources. A quota is a hard limit that prevents the use of additional resources once the quota value is exceeded. The purpose of transport quotas is to prevent Denial of Service (DOS) attacks, either malicious or unintentional.
Windows Communication Foundation (WCF) transports have default quota values that are based on a conservative allocation of resources. These default quota values should be suitable for development environments and small installation scenarios. Service administrators should review transport quotas and tune individual quota values if an installation is running out of resources or if connections are being limited despite the availability of additional resources.
WCF transports have three types of quotas: timeouts, memory allocation limits, and collection size limits.
· Timeouts are a mitigation of DOS attacks that rely on tying up resources for an extended period of time.
· Memory allocation limits prevent a single connection from exhausting system memory and denying service to other connections.
· Collection size limits bound the consumption of resources that indirectly allocate memory or are in limited supply.
This section describes the transport quotas available for the standard WCF transports: HTTP(S), TCP/IP, and named pipes. Custom transports can expose their own configurable quotas that are not included in this list. You will need to consult the documentation for a custom transport to find out about its quotas.
Name
Type
Minimum Value
Default Value
Description
CloseTimeout
TimeSpan
0
1 min
Maximum time to wait for a connection to close before the transport will raise an exception.
ConnectionBufferSize
Integer
8 KB
Size in bytes of the transmit and receive buffers of the underlying transport. Increasing this buffer size can improve throughput when sending large messages.
ConnectionLeaseTimeout
5 min
Maximum lifetime of an active pooled connection. After the specified time elapses, the connection will close once the current request is serviced.
This setting only applies to pooled connections.
IdleTimeout
2 min
Maximum time a pooled connection can remain idle before being closed.
ListenBacklog
10
Maximum number of unserviced connections that can queue at an endpoint before additional connections are denied.
MaxBufferPoolSize
Long
512 KB
Maximum amount in bytes of memory that the transport will devote pooling reusable message buffers. When the pool cannot supply a message buffer, a new buffer is allocated for temporary use.
Installations that create many channel factories or listeners can allocate large amounts of memory for buffer pools. Reducing this buffer size can greatly reduce memory usage in this scenario.
MaxBufferSize
1
64 KB
Maximum size in bytes of a buffer used for streaming data. If this transport quota is not set or the transport is not using streaming, then the quota value is the same as the smaller of the MaxReceivedMessageSize quota value and Integer.MaxValue.
MaxInboundConnections
Maximum number of incoming connections that can be serviced. Increasing this collection size can improve scalability for large installations.
Connection features such as message security can cause a client to open more than one connection. Service administrators should account for these additional connections when setting this quota value.
Connections waiting to complete a transfer operation can occupy a connection slot for an extended period of time. Reducing the timeouts for send and receive operations can free up connection slots quicker by disconnecting slow and idle clients.
MaxOutboundConnectionsPerEndpoint
Maximum number of outgoing connections that can be associated with a particular endpoint.
MaxOutputDelay
200 ms
Maximum time to wait after a send operation for batching additional messages in a single operation. Messages are sent earlier if the buffer of the underlying transport becomes full. Sending additional messages does not reset the delay period.
MaxPendingAccepts
Maximum number of channels that the listener can have waiting to be accepted.
There is an interval of time between a channel completing an accept and the next channel beginning to wait to be accepted. Increasing this collection size can prevent clients that connect during this interval from being dropped.
MaxReceivedMessageSize
Maximum size in bytes of a received message, including headers, before the transport will raise an exception.
OpenTimeout
Maximum time to wait for a connection to be established before the transport will raise an exception.
ReceiveTimeout
Maximum time to wait for a read operation to complete before the transport will raise an exception.
SendTimeout
Maximum time to wait for a write operation to complete before the transport will raise an exception.
The transport quotas MaxInboundConnections and MaxOutboundConnectionsPerEndpoint are combined into a single transport quota called MaxConnections when set through the binding or configuration. Only the binding element allows setting these quota values individually. The MaxConnections transport quota has the same minimum and default values.
Transport quotas are set through the transport binding element, the transport binding, application configuration, or host policy. This document doesn’t cover setting transports through host policy. You will need to consult the documentation for the underlying transport to discover the settings for host policy quotas. The Microsoft Knowledge Base describes quota settings for the Http.sys driver and for TCP/IP networking.
Other types of quotas apply indirectly to transports. The message encoder that the transport uses to transform a message into bytes can have its own quota settings. However, these quotas are independent of the type of transport being used.
Setting transport quotas through the binding element offers the greatest flexibility in controlling the behavior of the transport. The default timeouts for Close, Open, Receive, and Send operations are taken from the binding when a channel is built.
HTTP
TCP/IP
Named Pipe
X
Setting transport quotas through the binding offers a simplified set of quotas to choose from while still giving access to the most common quota values.
2
1. The MaxBufferSize transport quota is only available on the BasicHttp binding. The WSHttp bindings are for scenarios that do not support streamed transport modes.
2. The transport quotas MaxInboundConnections and MaxOutboundConnectionsPerEndpoint are combined into a single transport quota called MaxConnections.
Application configuration can set the same transport quotas as directly accessing properties on a binding. In configuration files, the name of a transport quota always starts with a lowercase letter. For example, the CloseTimeout property on a binding corresponds to the closeTimeout setting in configuration and the MaxConnections property on a binding corresponds to the maxConnections setting in configuration.
Next time: What Goes In... (IInputChannel)
Last week I announced a newly introduced bug in our newly introduced REST support that was causing some pain to this scenario. I thought it was worthwhile to go into a little more detail about what the bug was and how it happened.
In previous versions of WCF, we had a setting for the HTTP transport to promote interoperability between systems that used WS-Addressing and systems that didn't. The setting was a boolean called MapAddressingHeadersToHttpHeaders. If you turned this setting on, we would start converting between using wsa headers and putting the equivalent information into the HTTP layer. In particular, we'd map the wsa:To header to the HTTP Request URI.
In the latest release of WCF, we repurposed MapAddressingHeadersToHttpHeaders to make REST easier. For normal SOAP messages, it's really not a valid scenario to be sending around messages with an empty body, so we convert those to null. However, when you're programming to the HTTP layer, you might be able to convey some meaning with just header information, such as the status code. In that case, you might not have a message body to send. As we saw in the Message class yesterday though, things like the status live in a properties object that hangs off of the message itself. If you get a null message back, you're out of luck.
We turned MapAddressingHeadersToHttpHeaders into the enumeration HttpMappingMode to solve the problem. The name changed because this setting is more than about addressing now, and the boolean became an enumeration because we need a third choice to indicate whether your messages are going to be SOAP or POX. The normal case became HttpMappingMode.SoapWithWSAddressing. The case where we do address mapping became HttpMappingMode.Soap. And, the POX case where we send through messages even if they have no body became HttpMappingMode.AnyXml. There were two problems with this.
The first problem is that the release documentation just talked about the new AnyXml mode and didn't explain where MapAddressingHeadersToHttpHeaders went. This was actually my fault. I was evidently so excited about this new feature that I missed that little detail. I've already updated the documentation for when we make future releases.
The second problem is that using HttpMappingMode.AnyXml on the server side is broken. This happened because of the way we turned the boolean into an enumeration. A boolean indicates two possible meanings, say A and B. Testing if the boolean indicates A is the same as testing if the boolean does not indicate B. When we have three meanings, A, B, and C, this doesn't work any more. We had a test in the mapping of the addressing header that checked for HttpMappingMode.Soap when really we meant to check for anything except HttpMappingMode.SoapWithWSAddressing. You can see how this came from the straight translation of the boolean test. The result is that the address gets mangled for HttpMappingMode.AnyXml, and it's essentially impossible to reach the server. As mentioned before, the workaround for now is to use HttpMappingMode.Soap on the server side.
Next time: Making Sense of Transport Quotas
When I talk about lowercase-m messages in the channel model, mentally you should translate these to the uppercase-M Message class. A message is the fundamental data type for channels. Input channels return a Message from receive operations and output channels take a Message for send operations. In the channel stack, protocol channels will have the concept of a message on both the top and bottom, while a transport channel has a message on one side and the network on the other.
At first sight of our Message base class, don't be frightened by the huge number of methods. Instead, concentrate on the few concepts that the message abstraction exposes.
public abstract class Message : IDisposable { protected Message(); public abstract MessageHeaders Headers { get; } protected bool IsDisposed { get; } public virtual bool IsEmpty { get; } public virtual bool IsFault { get; } public abstract MessageProperties Properties { get; } public MessageState State { get; } public abstract MessageVersion Version { get; } public void Close(); public MessageBuffer CreateBufferedCopy(int maxBufferSize); public static Message CreateMessage(MessageVersion version, string action); public static Message CreateMessage(System.Xml.XmlReader envelopeReader, int maxSizeOfHeaders); public static Message CreateMessage(XmlDictionaryReader envelopeReader, int maxSizeOfHeaders); public static Message CreateMessage(MessageVersion version, MessageFault fault, string action); public static Message CreateMessage(MessageVersion version, string action, BodyWriter body); public static Message CreateMessage(MessageVersion version, string action, object body); public static Message CreateMessage(MessageVersion version, string action, System.Xml.XmlReader body); public static Message CreateMessage(MessageVersion version, string action, XmlDictionaryReader body); public static Message CreateMessage(System.Xml.XmlReader envelopeReader, int maxSizeOfHeaders, MessageVersion version); public static Message CreateMessage(XmlDictionaryReader envelopeReader, int maxSizeOfHeaders, MessageVersion version); public static Message CreateMessage(MessageVersion version, FaultCode faultCode, string reason, string action); public static Message CreateMessage(MessageVersion version, string action, object body, XmlObjectSerializer serializer); public static Message CreateMessage(MessageVersion version, FaultCode faultCode, string reason, object detail, string action); public T GetBody<T>(); public T GetBody<T>(XmlObjectSerializer serializer); public string GetBodyAttribute(string localName, string ns); public XmlDictionaryReader GetReaderAtBodyContents(); protected virtual void OnBodyToString(XmlDictionaryWriter writer); protected virtual void OnClose(); protected virtual MessageBuffer OnCreateBufferedCopy(int maxBufferSize); protected virtual string OnGetBodyAttribute(string localName, string ns); protected virtual XmlDictionaryReader OnGetReaderAtBodyContents(); protected abstract void OnWriteBodyContents(XmlDictionaryWriter writer); protected virtual void OnWriteMessage(XmlDictionaryWriter writer); protected virtual void OnWriteStartBody(XmlDictionaryWriter writer); protected virtual void OnWriteStartEnvelope(XmlDictionaryWriter writer); protected virtual void OnWriteStartHeaders(XmlDictionaryWriter writer); public override string ToString(); public void WriteBody(System.Xml.XmlWriter writer); public void WriteBody(XmlDictionaryWriter writer); public void WriteBodyContents(XmlDictionaryWriter writer); public void WriteMessage(System.Xml.XmlWriter writer); public void WriteMessage(XmlDictionaryWriter writer); public void WriteStartBody(System.Xml.XmlWriter writer); public void WriteStartBody(XmlDictionaryWriter writer); public void WriteStartEnvelope(XmlDictionaryWriter writer); }
That's probably more methods than all the previous classes we've looked at combined! Let's start consolidating these into concepts.
The interesting part of a SOAP message is generally the body, which is normally where your data will live. We have a bunch of CreateMessage overloads for generating messages from existing data, GetBody and GetReaderAtBodyContents methods for cracking open a message, and CreateBufferedCopy and write methods for extruding the message to other consumers. For extenders of the Message class, we also have a bunch of hooks for overriding behavior during the message consumer's requests. There's some asymmetry between treating messages as a blob in memory and treating messages as a stream, but these four buckets have just covered almost all of the methods.
Then, there are the parts of the message that float outside the body. We have a version property to describe the SOAP and addressing standards that this message conforms to. Like channels, messages have a lifecycle for resource tracking, so we have a state property that tracks that lifecycle and a Close method for cleaning up. Finally, we have headers and properties that provide information about the message.
There is certainly going to be some learning curve for becoming comfortable using WCF messages. A good way to ease into it is to focus on the body at first since that is likely to have a direct connection with what your application is doing. The three methods to think about are CreateMessage, GetBody, and Close. Learn about the rest of the concepts when you need them. For example, if you need to work with the message action, which is a header, go learn about that property, but don't feel like you have to understand everything about headers before building an application.
Next time: A POX on Us, Redux
The WCF channel model has three built-in archetypes for communication patterns. These archetypes are the one-way pattern, unmatched two-way pattern, and matched two-way pattern. Along with the archetypes are five interfaces, called channel shapes, that describe the send and receive methods for each pattern. There is one interface for the client and server side of each pattern, except for unmatched two-way where the two endpoints have the same behavior.
In the one-way communication pattern, there is a unidirectional stream of messages between the client and server. One end of the pair will have an IInputChannel for receiving messages and the other end will have an IOutputChannel for sending messages. In a queueing system, you might have writers that put messages into the queue and a reader that processes messages from the queue. Such a system would use the one-way pattern to indicate that there's no way to flow messages through the queue in the other direction.
In the unmatched two-way communication pattern, there's essentially a pair of one-way patterns welded together. Each side has an input and output channel, joined together by an IDuplexChannel, that they can use independently. Once you've established a bidirectional TCP connection, both sides of the connection can read and write messages at any time.
Finally, in the matched two-way communication pattern, there is still an input and output channel on both sides, but there's a semantic restriction on when messages can be sent. The client can send requests to the server, through an IRequestChannel, and the server can send a reply back the client, through an IReplyChannel. The client and server have to maintain this pairing because only one side is expected to talk on the channel at a time. This is the model that HTTP uses for communication.
We've got some other obligations to take care of this week, but we'll spend next week looking at each of these interfaces in detail.
Next time: Get the Message
I hate bad documentation. Right now though, I'm busy writing up some topics about WCF channels in my spare time for the Windows SDK. After a few weeks of doing this, it's starting to get pretty tough deciding whether things are just right, neither too little nor too much description, so that your questions are answered and the answers are easy to find.
And that's when I thought, "Maybe actual customers would like to read some of this and say what needs to be better?"
I'm willing to spend one day a week, every week or two, blogifying some of the very early documentation topics. This would be prerelease documentation, with even less warrantee than the standard documentation. There would be only a minimum of technical review and no editorial work. Batteries not included. Use only as directed. May be too intense for some viewers. If condition worsens, discontinue use and consult a physician.
Let me know what you think about this plan. Post a comment, use the Email link in the sidebar to send a message, or just contact me directly at nallen@microsoft.com.
I've been giving a tour around the WCF channel stack without ever really explaining what channels are. Today, tomorrow, and for even more days yet to come, we'll spelunk into the heart of channels.
A channel is simply a conduit through which messages flow. Every message going into or out of a WCF application passes through a channel. Along the way, channels can do all kinds of exciting things to your message in order to provide services. You can write channels that observe messages passing through and log information. You can write channels that apply policy to decide what messages can go through. You can write channels that fold, spindle, and mutilate messages if you want. You can do pretty much anything with a channel, and then you can package up an interesting collection of channels into a binding that describes a sequence of message transforming operations.
All of this power comes from this one little interface:
public interface IChannel : ICommunicationObject { IChannelManager Manager { get; } T GetProperty<T>() where T : class; }
How uninteresting! Where are all the methods for sending and receiving data? As we'll see tomorrow, channels come in a variety of shapes that describe their message passing modes. It's only once you select a particular shape for a channel that you have semantics like input and output. However, we do have a channel manager property that sounds possibly interesting.
Hmm, not much there either. The channel manager is what creates channels, but just like for plain old sockets, there's different behavior depending on whether you're creating client or server channels. On the base interface is just a few methods for describing what goes through the channel. We'll come back to channel managers in a few days after looking at some of the channel shapes.
As a side note, both the channel and channel manager have the same mysterious GetProperty method that we saw on the binding and binding element. Let's say that one of your channels is interested in knowing whether something in the channel stack supports the IFooable interface. IFooable may mean something to the channel for security, management, policy, or things like that. With this giant channel stack, and possibly channels internally delegating to other channels, how are you ever going to find out whether a message will be IFooable'd along the way? Well, you'll simply call GetProperty with IFooable in place of T! The role of GetProperty is to basically search for the needle in your channel stack.
Next time: Windows Communication Foundation Channel Shapes
In the recent CTP we added a new feature to WCF so that we work better with POX/REST scenarios. On the HttpTransportBindingElement there's a knob called MappingMode that lets you configure how we'll process HTTP headers. By default, if you receive a message over HTTP with an empty body, we discard the message, headers and all, since a SOAP message with no content isn't interesting. That's not what you want for REST, where you can extract interesting information from the response headers of the message even if there's no body. We added the ability to set MappingMode to AnyXml in the February CTP, which preserves messages without a body for your inspection.
Good so far? Now, the reason I'm writing this post.
Unfortunately, there's a bug in this new feature. Using AnyXml on the client side works fine. Using AnyXml on the server side causes clients to be dropped with HTTP 500 Internal Server Error messages.
That's bad.
The workaround is to use AnyXml on the client side if you need it, but for now, use the Soap mapping mode on the server side.
We will definitely fix this bug prior to release so that the right way to build a RESTful service is to use AnyXml on both the client and server.
Back to the subject of bindings, a binding is what ties together the description of a channel stack. A binding contains a collection of binding elements that correspond to protocol channels, transport channels, and message encoders. In the binding, there can be any number of binding elements for protocol channels but one and only one binding element for each the transport and message encoder. Actually, as I mentioned earlier, you can sometimes get away with not specifying the message encoder and instead let the transport decide the encoding for you. But as I also mentioned earlier, you probably should just go ahead and specify the message encoder explicitly.
The definition of a binding is quite a bit more complicated than the classes we've been looking at so far.
public abstract class Binding : IDefaultCommunicationTimeouts { protected Binding(); protected Binding(string name, string ns); public TimeSpan CloseTimeout { get; set; } public string Name { get; set; } public string Namespace { get; set; } public TimeSpan OpenTimeout { get; set; } public TimeSpan ReceiveTimeout { get; set; } public abstract string Scheme { get; } public TimeSpan SendTimeout { get; set; } public virtual IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingParameterCollection parameters); public IChannelFactory<TChannel> BuildChannelFactory<TChannel>(params object[] parameters); public virtual bool CanBuildChannelFactory<TChannel>(BindingParameterCollection parameters); public bool CanBuildChannelFactory<TChannel>(params object[] parameters); public abstract BindingElementCollection CreateBindingElements(); public T GetProperty<T>(BindingParameterCollection parameters) where T : class; }
I've talked about timeouts in the past and I'll talk about them again in the future, but for now, timeouts don't matter because we aren't actually sending data or making connections at this point. Instead, I want to draw your attention to the Name, Namespace, and Scheme properties. We've split the specification of the user name for the binding apart from the specification of the protocol name. If you want to add more HTTP bindings, you can name them whatever you want and set all of their schemes to "http". There's no inherent application or machine dispatch based on scheme, which means you avoid the common problem of being unable to register additional handlers for well-known protocols. You can also work with multiple versions of a binding side-by-side easily by giving each version a different name.
We've seen the BuildChannelFactory, CanBuildChannelFactory, and GetProperty methods before in binding elements. The parameters are different because we haven't built a binding context at this point. That happens during the handoff from binding to binding element. The only new method is CreateBindingElements which generates the binding elements for this binding. You probably guessed that from the name. Changing one collection of binding elements should not affect other users of the binding, so this collection is typically cloned off of a master copy.
We're making fast progress towards covering the basics of the channel model. What's left is mostly looking at channels themselves and how to build them.
Next time: And Starring IChannel
Last week I had the opportunity to visit one of the local usability labs here and watch a real live person attempt to use WCF in a project. It's always a nervous experience the first time you're watching someone do a particular trial because you really have no idea what's going to happen. That's the point of doing usability experiments after all. After a few times through though, you start noticing a lot of commonality between participants. I don't know what the exact right number of times to run a usability trial is, but five to ten times seems fairly popular, and I can believe that it doesn't take many people before you mostly get duplicate experiences. A few major usability problems during a focused study are really going to swamp out any other issues.
If you've never seen a usability study before, check out this Channel 9 video that gives an inside look into a session.
Assuming that the participant isn't totally flummoxed, you learn a lot about the product by watching what they do wrong on the way to getting a correct answer. There's usually a pretty good reason why someone does the wrong thing, and that's because it seemed like the right thing to do at the time. It's even better if you can generalize those experiences to look for similar situations with that particular problem.
One issue I saw in the lab was that if the product suggests multiple ways of solving a problem, the best solution better be first and extremely prominent. We had an error message that gave multiple solution methods, but the solution that you really should use was the last one listed. Even though we had other clues to use that particular solution method, there was a lot of time lost due to the misleading error message. I'll be on the lookout for other error messages that suggest doing either A, B, or C to make sure this doesn't happen anywhere else.
Steven Clarke has blogged about usability labs in the past, so if you're interested you should go read some of his entries about how the labs are setup and run.
This was another one of those interruptions of your regularly scheduled post. Tomorrow I'll conclude the miniseries on bindings, and next week we start looking deeper into what it means to be a channel.