Share via


Transport: WSE 3.0 TCP Interoperability

Download sample

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:

  1. Starting with a TCP socket, create client and server implementations of IDuplexSessionChannel that use DIME Framing to delineate message boundaries.

  2. Create a channel factory that connects to a WSE TCP service and sends framed messages over the client IDuplexSessionChannels.

  3. Create a channel listener to accept incoming TCP connections and produce corresponding channels.

  4. Ensure that any network-specific exceptions are normalized to the appropriate derived class of CommunicationException.

  5. Add a binding element that adds the custom transport to a channel stack. For more information, see Adding a Binding Element.

Creating IDuplexSessionChannel

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

WriteData(encodedBytes);

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 byte[]s. 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:

this.RemoteAddress.ApplyTo(message);

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:

  1. OnAbort -- close the socket ungracefully (hard close).

  2. On[Begin]Close -- close the socket gracefully (soft close).

  3. session.CloseOutputSession -- shutdown the outbound data stream (half close).

Channel Factory

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.

Channel Listener

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

this.listenSocket.Bind(localEndpoint);

this.listenSocket.Listen(10);

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;

binding.Elements.Add(mtomBindingElement);

binding.Elements.Add(new WseTcpTransportBindingElement());

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.

Client:

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.

Server:

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:

    1. 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).

    2. Set the StockService project as the startup project.

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

    4. Press F5 to start the TcpSyncStockService. The service starts in a new console window.

    5. Open this TCP transport sample in Visual Studio.

    6. Update the "hostname" variable in TestCode.cs to match the machine name running the TcpSyncStockService.

    7. Press F5 to start the TCP transport sample.

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

Footer image

Send comments about this topic to Microsoft.
© Microsoft Corporation. All rights reserved.