WCF 客户端与服务器之间的中间服务






3.77/5 (9投票s)
本文介绍如何创建负载均衡器,实现 WCF 服务在客户端和 WCF 服务器服务之间的路由。
引言
本文的目的是创建一个 WCF 服务,该服务可以充当负载均衡器或路由器。托管 WCF 服务需要创建服务契约、数据契约并配置终结点、托管服务、生成 Web 服务描述语言 (WSDL)、启用元数据交换点以便客户端可以创建代理。客户端和服务通道应就寻址、绑定和消息过滤达成一致。有时产品需要一些附加功能,如日志记录、路由、负载均衡或引入安全边界。通过对寻址和过滤行为进行一些调整,我们可以轻松引入中间服务并实现这些功能。
背景
读者应了解 WCF 基础知识。
Using the Code
附加的源代码包含以下项目:
TargetServiceContract
:目标 WCF 服务的契约TargetHost
:目标 WCF 服务的宿主控制台应用程序IntermediateServiceContract
:可作为负载均衡器或路由器的服务契约IntermediateServiceHost
:宿主IntermediateServiceContract
的宿主控制台应用程序Client
:消费 WCF 目标服务的客户端项目
在操作签名中使用操作类型并不意味着服务可以处理任何消息。默认情况下,Action 头用于定位特定操作。服务模型会检查传入的消息,并在终结点上查找具有匹配 Action 头的一个操作。如果找不到匹配项,则请求将被拒绝。
在什么情况下 Action 头会与被调用的操作不匹配?一种可能性是在客户端和应用程序服务之间放置了一个中间服务或路由服务。在这种情况下,客户端应用程序不知道中间服务的存在,Action 头指向应用程序服务。
Action 头指示要调用的操作的 URI。它通过将服务契约命名空间与服务契约友好名称和操作名称连接起来生成。
http://Example/IntermediateService/TargetServiceContract/VerifyMessage
ReplyAction 在此基础上附加“Response”。
http://Example/IntermediateService/TargetServiceContract/VerifyMessageResponse
客户端可以通过生成服务代理来生成相同的 Action、ReplyAction 和代理设置。以下是从代理生成的代码。
[System.ServiceModel.OperationContractAttribute(
Action="http://Example/IntermediateService/TargetServiceContract/VerifyMessage",
ReplyAction=
"http://Example/IntermediateService/TargetServiceContract/VerifyMessageResponse")]
string VerifyMessage(string message);
客户端绑定配置将反映物理地址。以下是具有目标服务地址作为物理地址的客户端终结点设置。我们可以通过使用元数据终结点(“https://:8001/TargetService/Mex”)创建服务引用来生成它。
<endpoint address="https://:8001/TargetService"
binding="wsHttpBinding"
bindingConfiguration="wsHttpEndPoint"
contract="TargetServiceClient.TargetServiceContract"
name="wsHttpEndPoint"
behaviorConfiguration="clientBehavior" />
当客户端向服务发送任何消息时,消息将以生成的代理文件中提到的 Action 发送到物理地址。
中间服务
中间服务旨在接收可以指向任何服务 Thus, the 消息,并且必须能够将原始消息转发到适当的服务,而无需更改消息正文。中间服务可能只查看消息头,但原始消息元素应在不作任何更改的情况下转发。
[OperationContract(Name="ProcessMessage",Action="*",ReplyAction="*")]
Message ProcessMessage(Message message);
如果 Action 和 ReplyAction 为 *,则所有消息都将被分派到同一个操作,并且服务契约中只能有一个操作的 Action 和 ReplyAction 为 *。由于 Channel Dispatcher 始终将输入参数作为 Message 类型(在 System.ServiceModel.Channels
中定义)并返回 Message 类型的值,因此中间服务应具有输入参数和返回类型均为 Message 的操作。此操作可以作为任何请求的目标。
将消息转发到原始服务
服务代理通常具有强类型操作,但在中间服务中,我们使用一个通用的代理操作,它可以发送任何类型的输入参数并接收任何类型的响应。由于契约具有非类型化的消息,因此可以将相同的消息发送到服务,并将响应直接发送给客户端。中间服务除了更改 To 地址以将消息发送到适当的服务外,不进行任何其他更改。
将请求发送到中间服务
引入中间服务时,最好是客户端能够使用正确的 To 头发送消息到服务,同时仍将消息发送到中间服务。实现此目的的第一种方法是配置 ClientViaBehavior
,如下所示。这会告诉客户端生成一个 To 头为终结点服务地址的消息,但它会通过中间服务。
<client>
<endpoint address="https://:8002/IntermediateServiceExample"
binding="wsHttpBinding"
bindingConfiguration="wsHttpEndPoint"
contract="TargetServiceClient.TargetServiceContract"
name="wsHttpEndPoint" behaviorConfiguration="clientBehavior">
</endpoint>
</client>
<behaviors>
<endpointBehaviors>
<behavior name="clientBehavior">
<clientVia viaUri="https://:8002/IntermediateServiceExample"/>
</behavior>
</endpointBehaviors>
</behaviors>
另一种实现方法是在目标服务中指定 listenuri
,这样目标服务的逻辑地址就可以是中间服务的地址,而物理地址可以是终结点地址。在这种情况下,当客户端生成代理时,它将使用中间服务的地址作为地址。
<endpoint address="https://:8002/IntermediateServiceExample"
binding="wsHttpBinding"
bindingConfiguration="wsHttpBinding" name="wsHttpEndPoint"
contract="IntermediateServiceExample.ITargetServiceContract"
listenUri="https://:8001/TargetService" />
在这种情况下,服务必须了解中间服务,我认为这是不对的,所以最好将中间服务地址告知客户端,客户端可以使用第一种方法。客户端将消息发送到中间服务,To 头是目标服务地址。Thus, the To 头永远不会匹配路由器的逻辑地址。默认情况下,服务使用 EndpointAddressMessageFilter
来确定消息的 To 头是否与任何已配置的终结点匹配。由于路由器无法预期此方法有效,因此它应安装 MatchAllMessageFilter
。我们可以使用以下过滤器来实现这一点。它会通过所有 To 头消息。
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
AddressFilterMode = AddressFilterMode.Any)]
public class IntermediateServiceManager : IIntermediateServiceContract
现在,当客户端发送任何消息时,它都会通过中间服务。中间服务可以对其进行负载均衡或路由,将请求发送到适当的服务,获取响应,并将相同的响应返回给客户端。由于所有来自客户端的请求和所有来自目标服务的响应都将通过中间服务,我们可以对通过通道的每条消息进行日志记录和跟踪。
历史
- 2008 年 9 月 29 日:首次发布