WCF技术剖析(卷1)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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中,我们可以得到和步骤二一样的结果。