当使用对等传输信道时会有一个常见的错误,就是认为它支持在一个对等网状网络间定向通信。消息定向意味着一条消息可以在一个对等网状网络中通过跨越一个网状网络将其传播到目的端(这就是路由的原理)来发送到特定节点。使用对等传输信道是不可能的。这限制了可以创建的对等应用程序的类型,因为所有的消息通信都假设消息将会被发送到每个节点。然而,只需要知道一点怎么做且付出一点努力,这些限制部分可以被淘汰。
有很多使用定向消息的方式,包括一对一以及多对一类型通信。一对一通信就是在一个网状网络中向一个特定节点发送一条消息的能力。多对一就是一个节点向一次请求的发起方反馈的能力。一对一消息要求使用路由技术来通过对等网状网络将消息转发到它的目的端。这种解决方案一般通过它们的受欢迎程度使用路由上邻近节点指数排名来解决到目的端的请求。不幸的是,复杂的解决方案和时间阻止我们完全使用这种形式的消息定向技术。相反,我们将关注更容易一些的定向消息格式,比如多对一场景。
多对一允许一个节点向原始请求方发送一个回调消息。可以使用两种解决方案来实现这个场景。第一个场景是通过网状网络向原始请求方发送一个回调消息。这类似于一对一解决方案。第二种解决方案要求请求方向可以接受回调消息的地址发送消息。这个方案更容易实现且很容易使用现有WCF架构实现。这个方案涉及了创建一个相反的传输信道。它包含了两个单向传输信道并将它们联合起来使用,请求消息在一个信道上传递回调消息在另外一个不同的信道上接收。在我们的解决方案中,当使用TCP传输信道接收回调消息时我们将使用对等传输信道来发送消息。我们也将在一个形状可以改变的信道上使用CompositeDuplexBindingElement绑定元素来允许在复杂传输信道上传输复杂消息。列表12.12显示了用来创建非对称传输的CompositeDuplexBindingElement绑定元素。
列表12.12 CompositeDuplexBindingElement
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel.Channels; namespace EssentialWCF.PeerApplication.Bindings { public class CompositeTransportBindingElement<TChannelBinding, TListenerBinding> : TransportBindingElement where TChannelBinding : Binding, new() where TListenerBinding : Binding, new() { TChannelBinding channelBinding; TListenerBinding listenerBinding; public CompositeTransportBindingElement (TChannelBinding channelBinding, TListenerBinding listenerBinding) { this.channelBinding = channelBinding; this.listenerBinding = listenerBinding; } public CompositeTransportBindingElement( CompositeTransportBindingElement<TChannelBinding, TListenerBinding> other) : base(other) { this.channelBinding = (TChannelBinding)other.channelBinding; this.listenerBinding = (TListenerBinding)other.listenerBinding; } public TChannelBinding ChannelBinding { get { return this.channelBinding; } } public TListenerBinding ListenerBinding { get { return this.listenerBinding; } } public override bool CanBuildChannelFactory<TChannel>( BindingContext context) { ThrowIfContextIsNull(context); return channelBinding.CanBuildChannelFactory<TChannel>( context.BindingParameters); } public override bool CanBuildChannelListener<TChannel>( BindingContext context) { ThrowIfContextIsNull(context); return listenerBinding.CanBuildChannelListener<TChannel>( context.BindingParameters); } public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context) { ThrowIfContextIsNull(context); return channelBinding.BuildChannelFactory<TChannel>( context.BindingParameters); } public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context) { ThrowIfContextIsNull(context); return listenerBinding.BuildChannelListener<TChannel>( context.BindingParameters); } public override string Scheme { get { return listenerBinding.Scheme; } } public override BindingElement Clone() { return new CompositeTransportBindingElement<TChannelBinding, TListenerBinding>(this); } public override T GetProperty<T>(BindingContext context) { ThrowIfContextIsNull(context); T result = this.channelBinding.GetProperty<T>(context.BindingParameters); if (result != default(T)) { return result; } result = this.listenerBinding.GetProperty<T>(context.BindingParameters); if (result != default(T)) { return result; } return context.GetInnerProperty<T>(); } private void ThrowIfContextIsNull(BindingContext context) { if (context == null) { throw new Exception("current context is null."); } } } }
仅使用CompositeDuplexBindingElement不会实现这个解决方案。我么必须创建一个连接几个绑定元素的绑定来创建相反的传输绑定。指出客户端和服务端不使用同样的绑定是重要的。绑定必须是非对称的因为传输时非对称的。列表12.13显示这些不同的绑定是如何被创建的。
列表12.13 使用CompositeDuplexBindingElement的自定义绑定(注)
最后一点是WCF内部绑定不允许自动。寻址。有两种方法可以处理这个。最好的方案是使用一个自定义绑定或者一个消息检测器来处理寻址。这将允许寻址在信道栈内部合适地发生,是最佳方案。这个简单且容易的方案是使用代码手动处理寻址,下一步我们将描述这个。列表12.14显示了需要处理寻址的客户端代码。手动寻址可以通过为InnerChannel.LocalAddress设置OutgoingMessageHeaders.ReplyTo位置。本地地址是客户端在监听来接收回调消息的地址。
列表12.14 在客户端手动寻址
列表12.15 显示了服务端使用的获取IncomingMessageHeader.ReplyTo地址并将其赋值给OutgoingMessageHeaders.To位置的同样类型的技术。这需要使用回调契约将消息发回给客户端。
列表12.15 在服务端手动寻址
有一个相反的使用回拉的方案,客户端必须初始化一个服务来接收回调消息。WCF信道结构将实例化一个监听器来接收这些请求。然而,我们在使用一个单独的通信信道,这意味着客户端必须可以直接访问来接收回调消息。这限制了那些在网络设备后面的客户端的使用,比如防火墙或者NAT路由器。可以通过使用IPv6寻址并开启TCP传输信道的teredo特性来打破这个限制。Teredo是一种IPv6网络地址转义/转换(NAT)技术,允许IPv6传输隧道跨越一个或者多个NAT路由器来访问一个IPv6网络上的主机。列表12.16显示了如何使用在配置文件中的一个自定义绑定来开启Teredo.
列表12.16 使用自定义绑定开启Teredo
大多数可以买到的家用路由器使用NAT技术来让多台计算机分享一个互联网连接。为了让TCP传输信道使用Teredo它必须也在计算机层次可以使用。图片12.15显示了如何在命令行使用netsh命令来开启Teredo.
图片12.15 使用NetSh 开启Teredo
Teredo 是一个在计算机内全局的设置,因此允许任何运行在客户端计算机上的开启IPv6的服务暴露出来。这包含了诸如远程桌面和因特网信息服务(IIS)等。由于这个,使用Teredo由于安全原因可能是非必须的而且可能需要替代方案。最后一件要提及的事情是Teredo依赖于中心节点-Teredo服务器。这个服务器可以寄宿在互联网上或者一个公司内部。微软提供在teredo.ipv6.microsoft.com 提供一个公共服务器。想要了解更多关于Teredo的信息,访问www.microsoft.com/technet/network/ipv6/teredo.mspx以及http://en.wikipedia.org/wiki/Teredo_tunneling.
另外可以用来解决在防火墙和NAT路由器后面的客户端的问题是使用一个允许计算机在使用一个称作延迟服务器的中央服务器来交换信息的延迟服务。这个思想是延迟服务器在互联网上对所有计算机可用并能支持消息交换。这允许开发人员向互联网暴露服务即便服务在一个NAT路由器或者一个防火墙后面。这个方案要求很多努力并要求了解更多书本上没有的细节。然而,微软正在做一系列产品和技术来帮助使用这个方案。BizTalk服务是微软提供的一个新的互联网服务,它用来提供身份认证和链接服务。一个连接服务是一个在公共范围内可用的延迟服务。在写这本书的时候,BizTalk服务正在试验并在上市前还需花费一些时间。它们将最终成为微软正在努力的一部分,代码名字为Oslo. Oslo是一个对微软很多产品的连接来提供创建模型驱动和服务开启应用的更好的方式。想要了解更多关于BizTalk服务和Oslo,访问http://labs.biztalk.net或者http://www.microsoft.com/soa/products/oslo.aspx.