2.3.5 案例演示:通过tcpTracer进行消息的路由
本节一开始介绍了SOA中的服务在不同的情况下担当着不同的角色:服务的提供者、服务的消费者和中介服务。实际上很多路由工具在本质上都起着中介服务的作用。对于WCF来说,最典型的工具就是tcpTracer。
对于希望对WCF的消息交换有深层次了解的读者来说,tcpTracer绝对是一个不可多得的好工具。将tcpTracer置于服务和服务代理之间,tcpTracer会帮助我们进行消息的截获和转发。分析截获的消息有助于我们更加深刻地理解WCF的消息交换机制。
从本质上讲,tcpTracer是一个路由器。当它启动的时候,需要设置两个端口:原端口(Source port)和目的端口(Destination port),然后tcpTracer就会在原端口进行网络监听。一旦请求抵达,它会截获整个请求的消息,并将整个消息显示到消息面板上。随后,tcpTracer会将该消息原封不动地转发给目的端口。在另一方面,从目的端口发送给原端口的消息,也同样被tcpTracer截获、显示和转发。接下来演示如何通过tcpTracer在WCF中进行消息的路由,步骤如下。
步骤一 创建一个简单的WCF应用
为了演示tcpTracer在WCF中的应用,须要先创建一个简单的WCF服务应用,为此我们创建一个简单计算服务的例子。这个例子将在后续的章节中频繁使用,这里先对该应用的结构作一个详细介绍,后续章节中就不再重复介绍了。
整个应用采用如图2-10所示的4层结构:Contracts、Services、Hosting和Clients。
图2-10 消息路由案例程序结构
● Contracts:Class library项目,定义所有的契约,包括服务契约、数据契约、消息契约及错误契约,此项目同时被其他3个项目引用;
● Services:Class library项目,实现了在Contracts中定义的服务契约;
● Hosting:控制台(Console)项目,同时引用Contracts和Services,实现对定义在Services项目的服务寄宿;
● Clients:控制台项目,引用Contracts,模拟服务的调用者。
1.服务契约:ICalculator
using System.ServiceModel; namespace Artech.TcpTraceDemo.Contracts { [ServiceContract(NameSpace=”http://wwww.artech.com/”] public interface ICalculator { [OperationContract] double Add(double x, double y); } }
2.服务实现:CalculatorService
using Artech.TcpTraceDemo.Contracts; namespace Artech.TcpTraceDemo.Services { public class CalculatorService:ICalculator { public double Add(double x, double y) { return x + y; } } }
3.服务寄宿(代码):Program
using System; using System.ServiceModel; using Artech.TcpTraceDemo.Services; namespace Artech.TcpTraceDemo.Hosting { class Program { static void Main(string[] args) { using (ServiceHost serviceHost = new ServiceHost(typeof(CalculatorService))) { serviceHost.Open(); Console.Read(); } } } }
4.服务寄宿(配置):App.config
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <bindings> <customBinding> <binding name="SimpleBinding"> <textMessageEncoding /> <httpTransport /> </binding> </customBinding> </bindings> <services> <service name="Artech.TcpTraceDemo.Services.CalculatorService"> <endpoint address="http://127.0.0.1:9999/CalculatorService" binding="customBinding" bindingConfiguration= "SimpleBinding" contract="Artech.TcpTraceDemo.Contracts. ICalculator"/> </service> </services> </system.serviceModel> </configuration>
注:由于本例仅仅是模拟消息的路由,只须要绑定提供的传输和编码功能,所以这里使用了自定义绑定,并且添加两个BindElement:HttpTransport和TextMessageEncoding。
5.客户端(代码):Program
using System.ServiceModel; using Artech.TcpTraceDemo.Contracts; using System; namespace Artech.TcpTraceDemo.Clients { class Program { static void Main(string[] args) { using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("CalculatorService")) { ICalculator calculator = channelFactory.CreateChannel(); using (calculator as IDisposable) { Console.WriteLine("x + y = {2} where x = {0} and y = {1}",1,2,calculator.Add(1,2)); } } Console.Read(); } } }
6.客户端(配置):App.config
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <bindings> <customBinding> <binding name="SimpleBinding"> <textMessageEncoding /> <httpTransport /> </binding> </customBinding> </bindings> <client> <endpoint address="http://127.0.0.1:9999/CalculatorService" binding="customBinding" bindingConfiguration="SimpleBinding" contract="Artech.TcpTraceDemo.Contracts.ICalculator" name="CalculatorService" /> </client> </system.serviceModel> </configuration>
步骤二 通过ClientViaBehavior实现基于tcpTracer的消息路由
对于创建的WCF服务来说,整个服务访问只涉及两方:服务和服务的调用者。服务的调用者将请求消息直接发送到服务端,计算结果也以回复消息的形式直接返回给服务的调用者。现在须要将tcpTracer作为一个路由器引入到两者之间。我们通过ClientViaBehavior这样一个终结点行为(EndpointBehavior)实现这样的功能。
具体的原理如图2-11所示:将tcpTracer的原端口(Source port)和目的端口(Destination port)设置成8888和9999(CalculatorService地址所在的端口)。若通过ClientViaBehavior端口设成8888(tcpTracer监听端口),那么客户端会将消息发送给tcpTracer监听端口,消息就会被其截获。最终tcpTracer将截获的消息转发到服务真正的目的地址。服务的回复消息也会沿着相同的路由返回。
图2-11 基于ClientViaBehavior的消息路由
注:对于消息发送方来说,SOAP消息的To报头对应的地址由发送端的终结点地址(逻辑地址)决定。
基于上面的实现原理,须要修改客户端的配置,在<system.serviceModel>/<behaviors>/<endpointBehaviors>添加ClientViaBehavior,将viaUri的端口指定为8888:http://127.0.0.1:8888/CalculatorService。并将该EndpointBehavior应用到终结点中。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <behaviors> <endpointBehaviors> <behavior name="clientViaBehavior"> <clientVia viaUri= "http://127.0.0.1:8888/CalculatorService" /> </behavior> </endpointBehaviors> </behaviors> <bindings> <customBinding> <binding name="SimpleBinding"> <textMessageEncoding /> <httpTransport /> </binding>
</customBinding> </bindings> <client> <endpoint address="http://127.0.0.1:9999/CalculatorService" behaviorConfiguration="clientViaBehavior" binding="customBinding" bindingConfiguration="SimpleBinding" contract="Artech.TcpTraceDemo.Contracts.ICalculator" name="CalculatorService" /> </client> </system.serviceModel> </configuration>
现在启动tcpTracer,将Listen On Port#和Destination Port #设置为8888和9999,如图2-12所示。
图2-12 tcpTracer的端口设置
接下来,分别启动服务寄宿和服务访问的控制台应用程序,请求消息和回复消息将会显示到tcpTracer的消息显示面板中,如图2-13所示。
图2-13 tcpTracer截获的消息
其中显示在上面文本框中的请求消息的内容如下,可以看出它是一个HttpRequest消息,以SOAP消息作为HttpRequest消息的主体(body)。
POST /CalculatorService HTTP/1.1 Content-Type: application/soap+xml; charset=utf-8
VsDebuggerCausalityData: uIDPo2sY41w6xm1DgtOSzZT5+0EAAAAAXVfsUhiXVUmLsNq- 6tAEl+rUZZUmtRERFvB6DbqcWQtcACQAA Host: 127.0.0.1:8888 Content-Length: 526 Expect: 100-continue Connection: Keep-Alive <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"> <s:Header> <a:Action s:mustUnderstand="1">http://tempuri.org/ICalculator/ Add</a:Action> <a:MessageID>urn:uuid:a63ec626-a350-4390-84c6-fb34be4ff208 </a:MessageID> <a:ReplyTo> <a:Address>http://www.w3.org/2005/08/addressing/ anonymous</a:Address> </a:ReplyTo> <a:To s:mustUnderstand="1">http://127.0.0.1:9999/ CalculatorService</a:To> </s:Header> <s:Body> <Add xmlns="http://tempuri.org/"> <x>1</x> <y>2</y> </Add> </s:Body> </s:Envelope>
相应地,在下面文本框中的回复消息是一个HttpResponse消息,主体部分仍然是一个SOAP消息,内容如下。
HTTP/1.1 100 Continue HTTP/1.1 200 OK Content-Length: 394 Content-Type: application/soap+xml; charset=utf-8 Server: Microsoft-HTTPAPI/2.0 Date: Sat, 13 Sep 2008 17:29:37 GMT <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"> <s:Header> <a:Action s:mustUnderstand="1">http://tempuri.org/ICalculator/ AddResponse</a:Action> <a:RelatesTo>urn:uuid:a63ec626-a350-4390-84c6-fb34be4ff208 </a:RelatesTo> </s:Header> <s:Body> <AddResponse xmlns="http://tempuri.org/"> <AddResult>3</AddResult> </AddResponse> </s:Body> </s:Envelope>
步骤三 通过ListenUri实现基于tcpTracer的消息路由
对于路由的实现,本质上就是实现逻辑地址和物理地址的分离。ClientViaBehavior实际上是通过在客户端分离逻辑地址(服务的目的地址)和物理地址(消息被发送的目的地址)实现了消息路由。接下来模拟的是在服务端分离逻辑地址与物理地址(监听地址)的解决方案。
通过ListenUri实现的基本原理如图2-14所示:客户端保持不变,在对服务进行寄宿的时候,将ListenUri的端口设为8888,那么服务实际的监听地址的端口将从9999变成8888。由于客户端保持不变,所以请求消息仍然发送到端口9999,为了实现tcpTracer对消息正常的路由,只需要将原端口和目的端口指定为9999(逻辑地址)和8888(物理地址)就可以了(和步骤二完全相反)。
图2-14 基于ListenUri的消息路由
为此,我们需要修改服务寄宿的配置,在终结点配置节中指定listenUri为http://127.0.0.1:8888/CalculatorService。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <bindings> <customBinding> <binding name="SimpleBinding"> <textMessageEncoding /> <httpTransport /> </binding> </customBinding> </bindings> <services> <service name="Artech.TcpTraceDemo.Services.CalculatorService"> <endpoint address="http://127.0.0.1:9999/CalculatorService" binding="customBinding" bindingConfiguration="SimpleBinding" contract="Artech. TcpTraceDemo.Contracts.ICalculator" listenUri="http://127.0.0.1:8888/CalculatorService" /> </service> </services> </system.serviceModel> </configuration>
现在我们启动tcpTracer,将Listen On Port#和Destination Port #设置为9999和8888。
图2-15 tcpTracer的端口设置
当我们先后启动服务寄宿和服务访问的控制台应用程序,在tcpTracer中,我们可以得到和步骤二一样的结果。