Transport: WSE 3.0 TCP Interoperability
The WSE 3.0 TCP Interoperability Transport sample demonstrates how to implement a TCP duplex session as a custom Windows Communication Foundation (WCF) transport. It also demonstrates how you can use the extensibility of the channel layer to interface over the wire with existing deployed systems. The following steps show how to build this custom WCF transport:
Starting with a TCP socket, create client and server implementations of IDuplexSessionChannel that use DIME Framing to delineate message boundaries.
Create a channel factory that connects to a WSE TCP service and sends framed messages over the client IDuplexSessionChannels.
Create a channel listener to accept incoming TCP connections and produce corresponding channels.
Ensure that any network-specific exceptions are normalized to the appropriate derived class of CommunicationException.
Add a binding element that adds the custom transport to a channel stack. For more information, see Adding a Binding Element.
The first step in writing the WSE 3.0 TCP Interoperability Transport is to create an implementation of IDuplexSessionChannel on top of a Socket. WseTcpDuplexSessionChannel derives from ChannelBase. The logic of sending a message consists of two main pieces: (1) Encoding the message into bytes, and (2) framing those bytes and sending them on the wire.
ArraySegment<byte> encodedBytes = EncodeMessage(message);
In addition, we take a lock so that our Send() calls preserve the IDuplexSessionChannel in-order guarantee, and so that calls to the underlying socket are synchronized correctly.
WseTcpDuplexSessionChannel uses a MessageEncoder for translating Messages to and from bytes. Because it is a transport, WseTcpDuplexSessionChannel is also responsible for applying the remote address that the channel was configured with. EncodeMessage summarizes the logic for this conversion:
return encoder.WriteMessage(message, maxBufferSize, bufferManager);
Once the Message has been encoded into bytes, it must be transmitted on the wire. This requires a system for defining message boundaries. WSE 3.0 uses a version of DIME as its framing protocol. WriteData summarizes the framing logic to wrap a byte into a set of DIME records.
The logic for receiving messages is very similar. The main complexity is that a socket read can return less bytes than were requested. To receive a message, WseTcpDuplexSessionChannel reads bytes off the wire, decodes the DIME framing, and then uses the MessageEncoder for turning the byte into a Message.
The base WseTcpDuplexSessionChannel assumes that it receives a connected socket. The base class handles socket shutdown. There are three places that interface with socket closure:
OnAbort -- close the socket ungracefully (hard close).
On[Begin]Close -- close the socket gracefully (soft close).
session.CloseOutputSession -- shutdown the outbound data stream (half close).
The next step in writing the TCP transport is to create an implementation of IChannelFactory for client channels.
WseTcpChannelFactory derives from ChannelFactoryBase<IDuplexSessionChannel>. It is a factory that does its work through overriding OnCreateChannel to produce client channels:
protected override IDuplexSessionChannel OnCreateChannel(EndpointAddress remoteAddress, Uri via)
return new ClientWseTcpDuplexSessionChannel(encoderFactory, bufferManager, remoteAddress, via, this);
ClientWseTcpDuplexSessionChannel adds logic to the base WseTcpDuplexSessionChannel to connect to a TCP server at channel.Open time. The hostname must first be resolved to an IP address:
hostEntry = Dns.GetHostEntry(Via.Host);
And then connect to the first available IP address in a loop:
IPAddress address = hostEntry.AddressList[i];
socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(new IPEndPoint(address, port));
As part of the channel contract, any domain-specific exceptions are wrapped, such as SocketException in CommunicationException.
The next step in writing the TCP transport is to create an implementation of IChannelListener for accepting server channels.
WseTcpChannelListener derives from ChannelListenerBase<IDuplexSessionChannel> and overrides On[Begin]Open and On[Begin]Close to control the lifetime of its listen socket. In OnOpen, a socket is created to listen on IP_ANY. More advanced implementations can create a second socket to listen on IPv6 as well. They can also allow the IP address to be specified in the hostname.
IPEndPoint localEndpoint = new IPEndPoint(IPAddress.Any, uri.Port);
this.listenSocket = new Socket(localEndpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
When a new socket is accepted, a server channel is initialized with this socket. All the input and output is already implemented in the base class, so this channel is responsible for initializing the socket.
Adding a Binding Element
Now that the factories and channels are built, they must be exposed to the ServiceModel runtime through a binding. A binding is a collection of binding elements that represents the communication stack associated with a service address. Each element in the stack is represented by a binding element.
In the sample, the binding element is WseTcpTransportBindingElement, which derives from TransportBindingElement. It supports IDuplexSessionChannel and overrides the following methods to build the factories associated with our binding:
public IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
return (IChannelFactory<TChannel>)(object)new WseTcpChannelFactory(this, context);
public IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
return (IChannelListener<TChannel>)(object)new WseTcpChannelListener(this, context);
It also contains members for cloning the binding element and returning our scheme (wse.tcp).
The WSE TCP Test Console
Test code for using this sample transport is available in TestCode.cs. The following instructions show how to set up the WSE TcpSyncStockService sample.
The test code creates a custom binding that uses MTOM as the encoding and WseTcpTransport as the transport. It also sets up the addressing version to be conformant with WSE 3.0:
CustomBinding binding = new CustomBinding();
MtomMessageEncodingBindingElement mtomBindingElement = new MtomMessageEncodingBindingElement();
mtomBindingElement.MessageVersion = MessageVersion.Soap11WSAddressingAugust2004;
It consists of two tests—one test sets up a typed client using code generated from the WSE 3.0 WSDL. The second test uses WCF as both the client and the server by sending messages directly on top of the channel APIs.
When running the sample, the following output is expected.
Calling soap://stockservice.contoso.com/wse/samples/2003/06/TcpSyncStockService Symbol: FABRIKAM Name: Fabrikam, Inc. Last Price: 120 Symbol: CONTOSO Name: Contoso Corp. Last Price: 50.07 Press enter. Received Action: http://SayHello Received Body: to you. Hello to you. Press enter. Received Action: http://NotHello Received Body: to me. Press enter.
Listening for messages at soap://stockservice.contoso.com/wse/samples/2003/06/TcpSyncStockService Press any key to exit when done... Request received. Symbols: FABRIKAM CONTOSO
To set up, build, and run the sample
To run this sample you must have WSE 3.0 installed and the WSE TcpSyncStockService sample. You can download WSE 3.0 from MSDN. Once you install WSE 3.0, do the following steps:
Open the TcpSyncStockService in Visual Studio (Note, the TcpSyncStockService sample is installed with WSE 3.0. It is not part of this sample's code).
Set the StockService project as the startup project.
Open StockService.cs in the StockService project and comment out the [Policy] attribute on the StockService class. This disables security from the sample. While WCF can interoperate with WSE 3.0 secure endpoints, we are disabling security to keep this sample focused on the custom TCP transport.
Press F5 to start the TcpSyncStockService. The service starts in a new console window.
Open this TCP transport sample in Visual Studio.
Update the "hostname" variable in TestCode.cs to match the machine name running the TcpSyncStockService.
Press F5 to start the TCP transport sample.
The TCP transport test client starts in a new console. The client requests stock quotes from the service and then displays the results in its console window.
© Microsoft Corporation. All rights reserved.