基础知识
使用 The.NET 服务总线
Juval Loy
内容
派上用场的中继服务
中继服务地址
服务总线绑定
单向中继绑定
事件中继绑定
作为一个事件集线器中继服务
正向查找
.NET 服务总线认为是新的 Windows Azure 群计算计划最访问、 功能强大,且有用的一条。.NET 服务总线旨在解决某些难题的连接问题时, 它还提供了引人注目的解决方案可伸缩性、 可用性和安全问题了通过降低技术进入障碍,并使主流和普遍用途是一个高级的通信方案。具体来说,.NET 服务总线解决挑战的 Internet 连接。事实是 Internet 连接是很困难。通常,服务是防火墙 (软件和硬件防火墙) 后负载平衡器后 ; 其地址是动态和可以解析本地网络上只和不能转换为外部地址。虚拟化与跨计算机维护传输会话连接添加新的维度。这种情况下是 图 1 中描述的。
图 1 Internet 连接的质询
此外,由于很通常 inadvisable 向呼叫者从 Internet 中打开 Intranet 企业将通常求助于使用外围网络,但又会提高的复杂性的部署,和管理多个组一起未经授权的调用的不需要的通信的客户端凭据。commonplace 的解决方案的 Internet 连接从动态 DNS 静态 IP 通常是麻烦,实时不更新但具有可伸缩性和吞吐量问题,可能不安全除非构建的专家。
方面有回调到客户端服务问题就复杂周围实际上一个镜像 图 1 的客户端的所有连接问题。和还,回调事件,对等的调用是通常的许多应用程序从使用者计算机承载交互通信,共享的媒体的游戏的组成部分和操作上为漫游业务计算机使用到成熟跨 Intranet 企业到业务应用程序之类的临时连接。
派上用场的中继服务
Internet 连接问题,解决方案是简单,因为它是那么难直接连接到服务的客户端,则可以避免执行的 (至少开始),而使用中继服务。中继服务是驻留在云模型的工作是帮助中继客户端调用服务的该连接服务。这样一个中继解决方案会要求客户端和服务 Intranet 来允许连接到该的群,但由于群构成同时在客户端和服务的非特定区域,在大多数环境将允许 Internet 时调用。
图 2显示中继服务的工作方式。
首先,同时服务和客户端必须建立连接并验证中继服务。到目前为止中继也记录在服务是和如何最佳它调用回。当客户端调用中继服务时,中继服务转发对服务调用 (客户端消息)。
图 2 在操作中继服务
序列简单而,则在实践中,它会涉及大量复杂的网络编程、 消息和标准 know-how、 安全专业技术,等。类解决方案只是超出了大多数应用程序的访问。这是完全在 Microsoft.NET 服务总线为了填充的间隔。它是一种现成的中继服务,存放和管理在 Microsoft 数据中心。.NET 服务总线充当外围网络,在该的群中提供一个位置来管理客户端和服务的凭据。.NET 服务总线是服务的前端 ;,它封装和隔离恶意的调用方在 Internet 上存在处理服务,并且是负责 repelling 各种攻击的拒绝服务时隐藏实际的服务的标识和 True 位置重播攻击中,。
连接到常规 Windows Communication Foundation (WCF) 服务,和使用中继,主要区别服务围绕承载。在中继的情况下该服务必须连接到.NET 服务总线,进行自我,验证并从中继服务侦听调用,客户端发送它的请求之前。这意味着您可以必须显式启动该主机或用作主机,并且不能受益承载 Windows Activation Service (WAS) 的 NT 服务 (或 IIS) WAS 后会启动主机仅在第一个请求到来在之后,将永远不会发生因为主机已无法连接到.NET 服务总线首先。
.NET 服务总线支持 WCF 友好编程模型通过提供一组专用的绑定和行为。通过,一般说来除外对于编程模型的几个细微 twists,中继服务的使用是比处理其他任何 WCF 服务没有不同。.NET 服务总线支持核心 WCF 功能的可靠消息传送、 邮件安全性和传输安全性。它不支持交易记录从客户端服务的传播在中继服务尽管,可能会在将来的版本中更改。单纯的服务端点之外.NET 服务总线还支持两种附加的相关编程模型: 消息队列和消息路由。我将地址队列和组合使另一组重要的设计方案,超出了 Internet 连接,因为在以后的专栏中的路由。
中继服务地址
打开帐户与.NET 服务总线之后, 您需要使用.NET 服务总线 Administration Web 站点创建新的解决方案 (请参见 图 3 )。(您进一步任何之前,注意附带此列和即将发布的列的所有演示都使用 CSharpConsoleApp 作为占位符解决方案名称。若要运行此演示,将要使用在.NET 服务总线创建您自己的帐户和 CSharpConsoleApp 替换您的解决方案名称并提供您的解决方案凭据。若要创建一个帐户,请转到 servicebus.Windows。网络)
图 3 创建解决方案
解决方案相当于一个计算机或域名常规的网络地址并且将在执行任何可用的 URI 符合字符串。
像其他任何 WCF 服务每个中继的服务必须具有唯一的地址。地址的格式始终是基址跟任意数量的可选的 URI:
[base address]/[optional URI]/.../[optional URI]
基本地址的格式是始终以传输架构,然后跟服务总线地址解决方案名称:
[schema]://[solution]/[service bus address]
服务总线地址是:
servicebus.windows.net
架构的格式是 sb、 http 或 https。 以下是几个可能的地址:
sb://MySolution/servicebus.windows.net
sb://MySolution/servicebus.windows.net/MyService
sb://MySolution/servicebus.windows.net/MyService/MyEndpoint
sb://MySolution/servicebus.windows.net/AnythingYouLike
http://MySolution/servicebus.windows.net
https://MySolution/servicebus.windows.net/MySolution/AnythingYouLike
一旦您的服务连接到中继服务,并开始侦听其地址,没有其他服务可以侦听范围在您的服务 URI 的其他 URI。
服务总线绑定
.NET 服务总线提供中继邮件,多个绑定,尚未我重点此处四个主绑定都 TCP 中继绑定、 WS 中继绑定、 单向的中继绑定和事件中继绑定 (特别是,NetTcpRelayBinding、 WSHttpRelayBinding、 NetOnewayRelayBinding 和 NetEventRelayBinding)。
TCP 中继绑定是选择的在大多数情况下,涉及的绑定中继 Internet 连接。 它最小化服务和中继服务开销时产生最佳的性能和吞吐量。 它支持请求答复操作、 单向操作和都通过中继服务的甚至双面打印回调。 对于架构,TCP 中继始终使用 sb:
<endpoint
address = "sb://MySolution/servicebus.windows.net/..." binding = "netTcpRelayBinding" contract = "..."/>
TCP 中继绑定需要 TCP 端口 808 (或 828) 使用传输安全性时处于打开状态调用的签出,某些在大多数环境并支持。 TCP 中继绑定提供了不受限制的邮件大小 (或至少与常规的 TCP 绑定最配置的邮件大小,)。 始终绑定,TCP 中继保持传输会话,以便与某个会话的完整服务调用进行相同的代理服务器通道上始终最终达到相同的服务实例。 但是,此绑定使用 TCP 二进制编码它并不互操作,它假定另一端也使用 TCP 中继绑定。
TCP 中继绑定提供了三种连接模式: 中继,直接和混合。 在 图 4 所示配置使用 TcpRelayConnectionMode 枚举和 ConnectionMode 属性的 NetTcpRelayBindingBase,连接模式。
图 4 的 TCP 中继连接模式
public enum TcpRelayConnectionMode
{
Relayed,
Direct,
Hybrid
}
public abstract class NetTcpRelayBindingBase : Binding,...
{
public TcpRelayConnectionMode ConnectionMode
{get;set;}
//More members
}
public class NetTcpRelayBinding : NetTcpRelayBindingBase
{...}
配置 TcpRelayConnectionMode.relayed 对该服务的所有调用始终都转到中继。 中继的连接是 TCP 中继绑定的默认模式。
配置 TcpRelayConnectionMode.direct 时该服务首次连接到中继服务并验证本身 (第 1 步在 图 5 ),然后客户端连接进行身份验证自身 ( 图 5 中的第 2 步)。 但是,到目前为止中继服务将升级连接到客户端和服务之间的直接连接的通知客户端如何访问服务直接 ( 图 5 中的步骤 3)。 使用的是在位置,客户端可以继续调用服务直接 ( 图 5 中的第 4 步)。
图 5 直接的 TCP 连接
中继服务将尝试升级到最直接的连接可能连接 ; 这就是如果客户端和服务属于同一个 Intranet 的它将提供客户端"好"的坐标 ; 也是如此,如果它们在同一台计算机上。
如果无法直接连接 (通常如果中继服务无法正确识别服务地址),在放弃连接。 对于直接的连接服务需要也打开 (这可能涉及一次性用户提示的) 的计算机上的端口 819。
在 TCP 中继绑定配置 TcpRelayConnectionMode.hybrid,通信将启动为通过中继服务的中继。 将如果客户端和服务之间的直接连接可能,中继服务将提升其直接连接。 否则,会保留为中继。 中继服务可以将提升为直接即使为邮件中继而不会任何丢失的数据。 通过 TCP 中继绑定,则混合模式应为首选的连接模式。 但是,它具有一个缺点: 直接和在混合模式则需要绑定到使用消息安全,并且如您将看到在本专栏的下一期,邮件的安全性要求额外的配置和设置。 虽然这是简单的步骤,他们阻止混合作为工作选项默认情况下。
我提到已经,但值得突出显示 TCP 中继绑定支持双面打印的回调通过中继的事实。 设置双面打印的调用和访问该回调引用与相同常规 TCP 绑定。 目前,TCP 中继绑定是仅中继绑定支持双面打印的回调 (请参见 图 6 )。
图 6 双面 TCP 中继回调
WS 中继绑定发送,并通过 HTTP 或 HTTPS 接收可互操作的 WCF 消息。 像常规 WS 绑定使用文本编码默认,并使用消息安全性或可靠消息传送时, 它将维护中继传输会话。 该方案用于该地址是 http 或 https。
<endpoint address = "http://MySolution/servicebus.windows.net/..." binding = "wsHttpRelayBinding" contract = "..."/>
为玩功能和使用,该的 WS 中继绑定是 TCP 中继绑定一样,除非它支持仅中继连接。 应使用此绑定使用 TCP 中继绑定在 TCP 端口被阻挡或一个显式需要进行互操作。
单向中继绑定
单向中继绑定允许客户端发送到缓冲区中继服务,而不是服务本身,维护的其消息,和更高版本已的中继服务尝试将邮件传递到该服务。 若要控制.NET 服务总线上的负载,发送的邮件被限制为 64KB (此限制可能会更改以后)。 来自该服务没有应答有可能,并单向中继绑定验证的端点的合同的所有操作都已都定义为单向操作的实际上。 对于的同时 TCP 和 WS 中继绑定,客户端调用中继服务、 服务本身必须能侦听或客户端会收到一个 EndpointNotFoundExcpetion 异常时只为与非中继的 WCF (这是这样即使操作定义为单向)。 在单向中继绑定的情况下客户端可以发出无关的服务的调用,有可能甚至无法服务在侦听。 因此,服务的状态未知时应选择单向中继绑定。 在的缺点是传递的客户端有其邮件不保证也不是传递的有任何保证的调度到该服务的调用的顺序。 此外,由于固有断开连接的性质单向中继绑定,有是永远不会传输会话。 只为与 TCP WS 中继绑定,可以有只有一个服务,监视该中继的地址,而可以是任意数量的客户端调用该服务。 对于该的架构单向中继始终使用 sb:
<endpoint address = "sb://MySolution/servicebus.windows.net/..." binding = "netOnewayrelayBinding" contract = "..."/>
事件中继绑定
事件中继绑定是一种浅但重要的特例,单向中继绑定的:
public class NetEventRelayBinding : NetOnewayRelayBinding
{...}
它允许任意数量的服务监视中继服务中相同的 URI。 一旦客户端向中继发送邮件,所有监视的服务收到它。 给定的事实有是客户端,数没有限制这实际上提供了 N:M 通信其中 N 和 M 可以是任意自然数字包括零,与 UDP 多址广播。 由于在特殊化是在服务端,客户端可以使用单向中继绑定或事件中继绑定,而服务必须使用事件中继绑定。 此外,与其他任何中继绑定不同您还可以同时侦听嵌套的 URI 的服务。 与单向中继绑定没有订单的邮件或本身传递的保证。
图 7 事件中继服务
到目前为止的使用事件中继绑定规范的情况是事件发布和为 图 7 中的显示的订阅。 在客户端现在称为发布者,调用中继服务,现在将事件传递到任意数量的服务,的事件集线器充当现在称为订阅服务器。
作为一个事件集线器中继服务
设计中显示 图 7 具有通用 publish-subscribe 图案的外观 (有关详细信息如何实现完整的发布订阅,请参阅我文章" WCF 基础: 内容时所需了解关于单向的调用,回调和事件"). 现实,事件中继绑定是缩进提供运行服务的事件提供仅一个轻量、 现成群辅助事件分发解决方案。 更强大的功能的添加和删除订阅服务器、 事件筛选和事件的管理支持队列 (以启用排队的发布者和队列的订阅服务器) 所有需要使用队列和路由器我将讨论在即将进行的列。
如果您只是想将事件发布到运行订阅服务器对中继服务,然后将中继服务视为您事件中心就足够了。 但是,您会很快遇到缺乏对离散的操作级别事件的支持。 事件视为服务端点或更是具体地说合同。 没有要订阅该的合同的特定操作但能其他服务能。 这意味着该订阅服务仍收到的事件,它可能不关心只是因为它有匹配的终结点。 例如,考虑 IMyEvents 约定:
[ServiceContract]
interface IMyEvents
{
[OperationContract(IsOneWay = true)]
void OnEvent1();
[OperationContract(IsOneWay = true)]
void OnEvent2(int number);
[OperationContract(IsOneWay = true)]
void OnEvent3(int number,string text);
}
如果订阅服务器上定义终结点,如以下代码中的,则在订阅服务器将,当然,收到对终结点的所有调用。
<endpoint
address = "sb://MySolution/servicebus.windows.net/IMyEvents"
binding = "netEventRelayBinding"
contract = "IMyEvents"
/>
如果订阅服务器 cares 仅关于接收调用 OnEvent2 操作,它必须仍然暴露事件中继绑定的终结点、 接收 OnEvent2,对但还 OnEvent1 和 OnEvent3 接收所有不需要的通讯以及在内部将其过滤。 这是直接结果合同 (或终结点) 的订阅级别和不在独立的操作级别。
管理操作级别的事件,唯一方法是将 URI 映射到操作,不终结点。 图 8 显示了这样的终结点的发布者视图。
图 8 定义操作级别的事件
<endpoint name = "OnEvent1"
address = "sb://MySolution/servicebus.windows.net/IMyEvents/OnEvent1"
binding = "netOnewayBinding"
contract = "IMyEvents"
/>
<endpoint name = "OnEvent2"
address = "sb://MySolution/servicebus.windows.net/IMyEvents/OnEvent2"
binding = "netOnewayBinding"
contract = "IMyEvents"
/>
<endpoint name = "OnEvent3"
address = "sb://MySolution/servicebus.windows.net/IMyEvents/OnEvent3"
binding = "netOnewayBinding"
contract = "IMyEvents"
/>
而发布客户端可以轻松地使用精确终结点以触发一个特定的事件在订阅服务器端设置它是不简单。 首先,想要允许订阅和取消单个事件而不影响正在进行的其他事件处理。 第二个,订阅服务器可能无法打开使用相同的主机,因为您会获得 Nothing 的所有终结点 ; 它仍将所有不需要的事件。 管理操作级别事件唯一的方法是将多个主机为所有针对相同的服务类型的该合同中的操作中。 每个主机将打开一个终结点对应于特定事件 (操作)。 要订阅或取消订阅特定事件在运行时,必须打开或关闭相应的主机。 因为不能依赖列表在宿主配置文件 (这将只是使打开所有终结点的所有主机) 中终结点,您必须将以编程方式添加每个所需的终结点到特定的主机。 如 图 9 所示在伪代码中。 图 9 中的该代码设置暴露给 图 8 中发布者终结点。
图 9 Equating 事件的操作
class MySubscriber : IMyEvents
{...}
ServiceHost hostEvent1;
ServiceHost hostEvent2;
ServiceHost hostEvent3;
Binding binding = new NetEventRelayBinding();
Uri baseAddress = new Uri("sb://MySolution/servicebus.windows.net/IMyEvents/");
//Subscribing to all events:
hostEvent1 = new ServiceHost(typeof(MySubscriber),baseAddress);
hostEvent1.AddServiceEndpoint(typeof(IMyEvents),binding,"OnEvent1");
hostEvent1.Open();
hostEvent2 = new ServiceHost(typeof(MySubscriber),baseAddress);
hostEvent2.AddServiceEndpoint(typeof(IMyEvents),binding,"OnEvent2");
hostEvent2.Open();
hostEvent3 = new ServiceHost(typeof(MySubscriber),baseAddress);
hostEvent3.AddServiceEndpoint(typeof(IMyEvents),binding,"OnEvent3");
hostEvent3.Open();
//Unsubscribe event 2:
hostEvent2.Close();
但是,这样一种方法是令人乏味的工作,重复和容易出现的错误。 它是在还紧密耦合事件协定。 要简化,自动化,和分离我编写了帮助器主机 EventRelayHost 图 10 所示此编程模型。 EventRelayHost 所有的构造函数需要至少一个要使用的基本地址 (与常规主机文章地址是可选)。
图 10 EventRelayHost
public class EventRelayHost
{
public EventRelayHost(Type serviceType,string baseAddress);
public EventRelayHost(Type serviceType,string[] baseAddresses);
public void SetBinding(NetEventRelayBinding binding);
public void SetBinding(string bindingConfigName)
public void Subscribe();
public void Subscribe(Type contractType);
public void Subscribe(Type contractType,string operation)
public void Unsubscribe();
public void Unsubscribe(Type contractType);
public void Unsubscribe(Type contractType,string operation);
public void Abort();
//More members
}
您还可以使用通过 SetBinding 方法使用绑定提供 EventRelayHost。 EventRelayHost 将默认为 NetEventRelayBinding 一个普通实例中。 订阅和取消订阅的方法操作很多与 Open 和 Close 方法,常规的主机的一样。 您可以订阅取消订阅) 服务类型支持的所有服务合同上的所有事件、 特定服务合同上的所有事件或要 (或特定服务合同在特定事件操作。 最后,可以调用中止中止正在处理的所有事件,并 ungracefully 关闭所有主机。 您可以看到 EventRelayHost 图 11 中的实现。
图 11 实现 EventRelayHost
//Partial listing without error handling and security
public class EventRelayHost
{
Dictionary<Type,Dictionary<string,ServiceHost>> m_Hosts =
new Dictionary<Type,Dictionary<string,
ServiceHost>>();
Type m_SericeType;
string[] m_BaseAddresses;
public void Subscribe(Type contractType,string operation)
{
m_Hosts[contractType] = new Dictionary<string,ServiceHost>();
List<Uri> baseAddressesList = new List<Uri>();
foreach(string address in m_BaseAddresses)
{
baseAddressesList.Add(new Uri(address + contractType));
}
m_Hosts[contractType][operation] =
new ServiceHost(m_SericeType,
baseAddressesList.ToArray());
NetEventRelayBinding binding = new NetEventRelayBinding();
m_Hosts[contractType][operation].
AddServiceEndpoint(contractType,
binding,operation);
m_Hosts[contractType][operation].Open();
}
public void Unsubscribe(Type contractType,string operation)
{
m_Hosts[contractType][operation].Close();
}
//More members
}
EventRelayHost 维护每个事件的服务合同的映射键入一个专用的服务主机映射为每个操作的另一个词典的词典。 在订阅方法打开承载的并添加为每个基本地址终结点。 订阅会附加为每个终结点的地址约定类型和操作名称。 没有配置文件不需要使用 EventRelayHost 时, 并且 图 11 中的该代码会减小为。
EventRelayHost host = new EventRelayHost(typeof(MyService), "sb://MySolution/servicebus.windows.net/...");
host.Subscribe();
...
host.Unsubscribe(typeof(IMyEvents),"OnEvent2");
虽然 EventRelayHost 简化在订阅服务器端,客户端现在必须维护作为事件终结点的多个代理服务器。 为了实现我编写 EventRelayClientBase <t>,声明如下所示 图 12 。
图 12 EventRelayClientBase <t>
public abstract class EventRelayClientBase<T> : IDisposable where T : class
{
public event EventHandler Closed;
public event EventHandler Opened;
public CommunicationState State
{get;}
public EventRelayClientBase(string solutionBaseAddress);
public EventRelayClientBase(string solutionBaseAddress,
NetEventRelayBinding binding)
public void Open();
public void Close();
public void Abort();
public T Channel
{get;}
//More members
}
若要将 EventRelayClientBase <T> 只是派生其像常规非ClientBase<T <T>,与我在 图 13 .
使用 EventRelayClientBase <t> 图 13
class MyEventsProxy : EventRelayClientBase<IMyEvents>,IMyEvents
{
public MyEventsProxy(string baseAddress) : base(baseAddress)
{}
public void OnEvent1()
{
Channel.OnEvent1();
}
public void OnEvent2(int number)
{
Channel.OnEvent2(number);
}
public void OnEvent3(int number,string text)
{
Channel.OnEvent3(number,text);
}
}
EventRelayClientBase <t> 提供 quasi-support ICommunicationObject 的状态,和状态更改通知。 与在正常的代理不同 EventRelayClientBase <t> 需要订阅服务器的基本地址。 向该地址,它会附加合同名称和操作名称与打开 EventRelayHost 终结点。 与该的 EventRelayHost 没有 client-side 的配置文件的必要。 虽然 EventRelayClientBase <t> 看起来作为一个代理客户端,但实际上是代理服务器的聚合,它维护每个终结点在订阅服务器端的代理服务器。
如 图 14 所示,EventRelayClientBase <t> 已映射到 T 的实例的操作的词典,事件服务作为类型参数提供的合同。 客户端在打开 EventRelayClientBase <t> 时,它使用反射获取所有事件操作的集合。 对于每个操作,它将会附加到基本的地址约定名称和操作名称 (用于生成端点地址),然后使用创建匹配的代理的通道工厂。
图 14 实现 EventRelayClientBase <t>
//Partial listing without error handling and state management
public abstract class EventRelayClientBase<T> : IDisposable where T : class
{
readonly string BaseAddress;
Dictionary<string,T> m_Proxies = new Dictionary<string,T>();
public CommunicationState State
{get;private set;}
public void Open()
{
Binding binding = new NetOnewayRelayBinding();
MethodInfo[] methods = typeof(T).GetMethods(...);
foreach(MethodInfo method in methods)
{
EndpointAddress address =
EndpointAddress(new Uri(BaseAddress+typeof(T) +
method.Name));
ChannelFactory<T> factory = new ChannelFactory<T>(binding,
address);
m_Proxies[method.Name] = factory.CreateChannel();
ICommunicationObject proxy = m_Proxies[method.Name]
as ICommunicationObject;
proxy.Open();
}
State = CommunicationState.Opened;
}
public T Channel
{
get
{
if(State != CommunicationState.Opened)
{
Open();
}
StackFrame frame = new StackFrame(1);
return m_Proxies[frame.GetMethod().Name];
}
}
public void Close()
{
foreach(ICommunicationObject proxy in m_Proxies.Values)
{
proxy.Close();
}
}
public void Abort()
{
foreach(ICommunicationObject proxy in m_Proxies.Values)
{
proxy.Abort();
}
}
//More members
}
通过访问堆栈帧、 获取调用的操作的名称和使用的键从词典中查找正确的代理时,该通道属性将返回正确的代理。 关闭 EventRelayClientBase <t> 关闭它管理的所有代理服务器。
正向查找
WCF 还提供了 BasicHttpRelayBinding、 WebHttpRelayBinding、 WS2007HttpRelayBinding 和 WS2007FederationHttpRelayBinding。 它们的名称表示,这些绑定是常规的 WCF 绑定的中继的等效项。 此外,WCF 中提供了 NetTcpContextRelayBinding、 BasicHttpRelayContextBinding 和 WebHttpRelayContextBinding 等。 所有派生其对应这些上下文绑定中继绑定,但添加对上下文协议的支持。
安全也是一个问题。 客户端和服务都应自身验证中继服务并防止客户端转移上的邮件服务。 我将讨论服务总线安全性,以及在激动人心队列和路由在我的下一列中。
将您的问题和提出的意见发送至 mmnet30@Microsoft.com.
Juval Lowy 是 IDesign 提供 WCF 培训和体系结构咨询的一个软件架构师。 他最新的著作是 Programming WCF Services 2nd Edition (O ' Reilly,2008)。 他还是 Microsoft 区域总监,在硅谷的。 与在 Juval www.idesign。 net.