WCF后续之旅(15): 逻辑地址和物理地址

在WCF中,每个终结点都包含两个不同的地址——逻辑地址和物理地址。逻辑地址就是终结点Address属性表示的地址。至于物理地址,对于消息发送放来讲,就是消息被真正发送的目的地址;而对于消息的接收放来讲,就是监听器真正监听的地址。

一、服务端的物理地址

在默认的情况下,终结点的逻辑地址和物理地址是同一个URI。换句话说,终结的逻辑地址是必须的,如何物理地址没有指定的,默认使用逻辑地址作为物理地址。对于消息接收方的终结点来讲,物理地址就是监听地址,通过ServiceEndpoint的ListenUri表示:

   1: //---------------------------------------------------------------
   2: // EndpointAddress & WCF Addressing (c) by 2008 Jiang Jin Nan
   3: //---------------------------------------------------------------
   4: public class ServiceEndpoint
   5: {
   6:     ... ...
   7:     public Uri ListenUri { get; set; }
   8: } 

在对服务进行寄宿的时候,我们可以调用SeriviceHostBase或者ServiceHost的AddServiceEndpoint对应的重载来为添加的终结点指定ListenUri

   1: //---------------------------------------------------------------
   2: // EndpointAddress & WCF Addressing (c) by 2008 Jiang Jin Nan
   3: //---------------------------------------------------------------
   4: public abstract class ServiceHostBase : CommunicationObject, IExtensibleObject<ServiceHostBase>, IDisposable
   5: {
   6:     //... ...
   7:     public ServiceEndpoint AddServiceEndpoint(string implementedContract, Binding binding, string address, Uri listenUri);
   8:     public ServiceEndpoint AddServiceEndpoint(string implementedContract, Binding binding, Uri address, Uri listenUri);
   9: } 
  10:  
  11: public class ServiceHost : ServiceHostBase
  12: {
  13:     //... ...
  14:     public ServiceEndpoint AddServiceEndpoint(Type implementedContract, Binding binding, string address, Uri listenUri);
  15:     public ServiceEndpoint AddServiceEndpoint(Type implementedContract, Binding binding, Uri address, Uri listenUri);
  16: } 
  17:  

在下面的代码片断中,就为终结点指定了一个同于逻辑地址的物理地址(ListenUri):

   1: //---------------------------------------------------------------
   2: // ListenUri.cs (c) by 2008 Jiang Jin Nan
   3: //---------------------------------------------------------------
   4: using (ServiceHost serviceHost = new ServiceHost(typeof(CalculateService)))
   5: {
   6:    serviceHost.AddServiceEndpoint(typeof(ICalculate),new WSHttpBinding(),
   7:        "http://127.0.0.1:9999/calculateservice",
   8:        new Uri ("http://127.0.0.1:8888/calculateservice"));
   9:    Console.Read();
  10: } 
  11:  

当然,ListenUri也可以通过配置进行指定,下面的配置和上面的代码是等效的:

   1: <configuration>
   2:     <system.serviceModel>        
   3:         <services>
   4:             <service name="Artech.WcfServices.Services.CalculateService">
   5:                 <endpoint  binding="wsHttpBinding"
   6:                     contract="Artech.WcfServices.Contracts.ICalculate"   address="http://127.0.0.1:8888/calculateservice"
   7:                     listenUri="http://127.0.0.1:8888/calculateservice" />
   8:             </service>
   9:         </services>
  10:     </system.serviceModel>
  11: </configuration> 
  12:  

二、客户端的物理地址

上面我们介绍了基于消息接收端终结点物理地址的指定,现在我们来介绍对于消息发送端的终结点,物理地址如何指定。在上面我们说过,对于消息的发送端来讲,物理地址其实就是消息发送的真正目的地址。该地址通过一个特殊的EndpointBehavior,ClientViaBehavor来指定。ClientViaBehavor定义的Uri代表该物理地址。

   1: //---------------------------------------------------------------
   2: // EndpointAddress & WCF Addressing (c) by 2008 Jiang Jin Nan
   3: //---------------------------------------------------------------
   4: public class ClientViaBehavior : IEndpointBehavior
   5: {
   6:     //... ...
   7:     public Uri Uri { get; set; }
   8: } 

ClientViaBehavor是WCF自定的EndpointBehavior, 我们可以通过下面的配置应用该ClientViaBehavor。通过<endpointBehaviors>下的<clientVia〉配置节,通过viaUri设置了一个不同于终结点地址(http://127.0.0.1:9999/calculateservice)的物理地址:http://127.0.0.1:8888/calculateservice

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <system.serviceModel>
   4:         <behaviors>
   5:             <endpointBehaviors>
   6:                 <behavior name="clientViaBehavior">
   7:                     <clientVia viaUri="http://127.0.0.1:8888/calculateservice" />
   8:                 </behavior>
   9:             </endpointBehaviors>
  10:         </behaviors>
  11:         <client>
  12:             <endpoint address="http://127.0.0.1:9999/calculateservice" behaviorConfiguration="clientViaBehavior"
  13:                 binding="wsHttpBinding" bindingConfiguration="" contract="Artech.WcfServices.Contracts.ICalculate"
  14:                 name="calculateservice">               
  15:             </endpoint>
  16:         </client>
  17:     </system.serviceModel>
  18: </configuration>

三、ListenUri和ListenUriMode

上面我们介绍了终结点的ListenUri属性用于指定一个用于网络监听的物理地址,我们接下来讨论与ListenUri相关的另一个概念——ListenUriMode。ListenUriMode代表的是确定真正监听地址的模式。ListenUriMode通过System.ServiceModel.Description.ListenUriMode枚举表示,而ListenUriMode定义了两个枚举值:Explicit和Unique。

   1: //---------------------------------------------------------------
   2: // EndpointAddress & WCF Addressing (c) by 2008 Jiang Jin Nan
   3: //---------------------------------------------------------------
   4: public enum ListenUriMode
   5: {
   6:     Explicit,
   7:     Unique
   8: } 

ListenUriMode.Explicit表示显示采用终结点ListenUri属性设置的Uri作为最终的监听地址;而Unique则根据ListenUri采用不同的策略保证最终使用的监听地址是唯一的。而对于如何确保监听地址的唯一性,WCF采用如下的策略:

  • 如果采用TCP作为传输协议,在不采用端口共享的情况下,会选择一个未被使用的端口作为最终监听地址的端口一确保地址的唯一性
  • 如果采用TCP作为传输协议,同时采用端口共享情况下,会添加一个GUID作为后缀以确保地址的唯一性
  • 对于非TCP作为传输协议,会添加一个GUID作为后缀以确保地址的唯一性

在ServiceEndpoint中,定义了一个ListenUriMode属性,用于指定终结点的ListenUriMode。

   1: //---------------------------------------------------------------
   2: // EndpointAddress & WCF Addressing (c) by 2008 Jiang Jin Nan
   3: //---------------------------------------------------------------
   4: public class ServiceEndpoint
   5: {
   6:     //... ...
   7:     public Uri ListenUri { get; set; }
   8:     public ListenUriMode ListenUriMode { get; set; }
   9: } 
  10:  

在对服务进行寄宿的时候,我们可以通过代码的方式为添加的终结点指定ListenUriMode。下面的代码将终结点设置成ListenUriMode.Unique.

   1: //---------------------------------------------------------------
   2: // EndpointAddress & WCF Addressing (c) by 2008 Jiang Jin Nan
   3: //---------------------------------------------------------------
   4: using (ServiceHost serviceHost = new ServiceHost(typeof(CalculateService)))
   5: {    
   6:  
   7:     ServiceEndpoint endpoint = serviceHost.AddServiceEndpoint(typeof(ICalculate), new WSHttpBinding(),
   8:     http://127.0.0.1:9999/calculateservice,    new Uri("http://127.0.0.1:8888/calculateservice"));
   9:     endpoint.ListenUriMode = ListenUriMode.Unique;
  10:     Console.Read();
  11: } 
  12:  

ListenUriMode也可以通过配置的方式进行指定,下面的配置和上面的代码是等效的。

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3: <system.serviceModel>
   4:         <services>
   5:             <service name="Artech.ListenUriDemos.Services.CalculateService">
   6:                 <endpoint address="http://127.0.0.1:9999/calculateservice"  binding="wsHttpBinding" contract="Artech.ListenUriDemos.Contracts.ICalculate"
   7: listenUriMode="Unique" listenUri="http://127.0.0.1:8888/calculateservice" />
   8:             </service>
   9:         </services>
  10:     </system.serviceModel>
  11: </configuration> 
  12:  

为了验证ListenUriMode.Unique模式下,我写了下面一个简单的例子:在对服务(Artech.ListenUriDemos.Services.CalculateService)进行寄宿的时候,为之添加了如下5个终结点,具体的配置如下:

image

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <system.serviceModel>
   4:         <bindings>
   5:             <netTcpBinding>
   6:                 <binding name="PortSharingBinding" portSharingEnabled="true" />
   7:             </netTcpBinding>
   8:         </bindings>
   9:         <services>
  10:             <service name="Artech.ListenUriDemos.Services.CalculateService">
  11:                <!--1. BasicHttpBinding & ListenUriMode.Explicit-->
  12:                 <endpoint address="http://127.0.0.1:5555/service1" binding="basicHttpBinding"
  13:                     name="httpExplicitListenUriMode" contract="Artech.ListenUriDemos.Contracts.ICalculate" />
  14:                 <!--2. BasicHttpBinding & ListenUriMode.Unique-->
  15:                 <endpoint address="http://127.0.0.1:6666/service2" binding="basicHttpBinding"
  16:                     name="httpUniquListenUriMode" contract="Artech.ListenUriDemos.Contracts.ICalculate"
  17:                     listenUriMode="Unique" />
  18:                <!--3. NetTcpBinding & ListenUriMode.Explicit-->
  19:                 <endpoint address="net.tcp://127.0.0.1:7777/service3" binding="netTcpBinding"
  20:                     bindingConfiguration="" name="tcpExplicitListenUriMode" contract="Artech.ListenUriDemos.Contracts.ICalculate" />
  21:                 <!--4. NetTcpBinding & ListenUriMode.Unique-->
  22:                 <endpoint address="net.tcp://127.0.0.1:8888/service4" binding="netTcpBinding"
  23:                     bindingConfiguration="" name="tcpUniquListenUriMode" contract="Artech.ListenUriDemos.Contracts.ICalculate"
  24:                     listenUriMode="Unique" />
  25:                 <!--5. NetTcpBinding & ListenUriMode.Unique & Port Sharing-->
  26:                 <endpoint address="net.tcp://127.0.0.1:9999/service5" binding="netTcpBinding"
  27:                     bindingConfiguration="PortSharingBinding" name="tcpPortSharingUniquListenUriMode"
  28:                     contract="Artech.ListenUriDemos.Contracts.ICalculate" listenUriMode="Unique" />
  29:             </service>
  30:         </services>
  31:     </system.serviceModel>    
  32: </configuration> 
  33:  

在一个控制台应用程序中,通过下面的代码实现对服务的寄宿。然后遍历ServiceHost的ChannelDispatcher列表,并将ChannelDispatcher对象的ChannelListener的Uri打印出来:

   1: //---------------------------------------------------------------
   2: // EndpointAddress & WCF Addressing (c) by 2008 Jiang Jin Nan
   3: //---------------------------------------------------------------
   4: using System;
   5: using System.ServiceModel;
   6: using Artech.ListenUriDemos.Services;
   7: using System.ServiceModel.Dispatcher; 
   8:  
   9: namespace Artech.ListenUriDemos.Hosting
  10: {
  11:     class Program
  12:     {
  13:         static void Main(string[] args)
  14:         {
  15:             using (ServiceHost serviceHost = new ServiceHost(typeof(CalculateService)))
  16:             {
  17:                 serviceHost.Open(); 
  18:  
  19:                 int i = 0;
  20:                 foreach (ChannelDispatcher channelDispatcher in serviceHost.ChannelDispatchers)
  21:                 {
  22:                     Console.WriteLine("第{0}个终结点的监听地址为: {1}", ++i,channelDispatcher.Listener.Uri);
  23:                 } 
  24:  
  25:                 Console.Read();
  26:             }
  27:         }
  28:     }
  29: } 
  30:  

最终的输出如下,从中我们可以看出,对于ListenUriMode.Unique的三个终结点:第2、4、5个,第2个终结点采用了基于HTTP的BasicHttpBinding,WCF通过加了一个GUID后缀确保监听地址的唯一性;使用了基于NetTcpBinding的第4个终结点,通过使用一个可用的端口(1119)确保监听地址的唯一性;而对于通过采用了NetTcpBinding的第5个终结点,由于采用了端口共享,不能改变其端口,所以仍然采用添加GUID后缀的方式确保监听地址的唯一性。

   1: 第1个终结点的监听地址为: http://127.0.0.1:5555/service1
   2: 第2个终结点的监听地址为: http://127.0.0.1:6666/service2/d9ce6f30-3103-4ec9-b73b-34f32c65b0a1
   3: 第3个终结点的监听地址为: net.tcp://127.0.0.1:7777/service3
   4: 第4个终结点的监听地址为: net.tcp://127.0.0.1:1119/service4
   5: 第5个终结点的监听地址为: net.tcp://127.0.0.1:9999/service5/b4f69288-913b-43ec-8e42-e58f150ee91c

 

WCF后续之旅: 
WCF后续之旅(1): WCF是如何通过Binding进行通信的 
WCF后续之旅(2): 如何对Channel Layer进行扩展——创建自定义Channel 
WCF后续之旅(3): WCF Service Mode Layer 的中枢—Dispatcher 
WCF后续之旅(4):WCF Extension Point 概览 
WCF后续之旅(5): 通过WCF Extension实现Localization 
WCF后续之旅(6): 通过WCF Extension实现Context信息的传递 
WCF后续之旅(7):通过WCF Extension实现和Enterprise Library Unity Container的集成 
WCF后续之旅(8):通过WCF Extension 实现与MS Enterprise Library Policy Injection Application Block 的集成 
WCF后续之旅(9):通过WCF的双向通信实现Session管理[Part I] 
WCF后续之旅(9): 通过WCF双向通信实现Session管理[Part II] 
WCF后续之旅(10): 通过WCF Extension实现以对象池的方式创建Service Instance 
WCF后续之旅(11): 关于并发、回调的线程关联性(Thread Affinity) 
WCF后续之旅(12): 线程关联性(Thread Affinity)对WCF并发访问的影响 
WCF后续之旅(13): 创建一个简单的WCF SOAP Message拦截、转发工具[上篇] 
WCF后续之旅(13):创建一个简单的SOAP Message拦截、转发工具[下篇] 
WCF后续之旅(14):TCP端口共享 
WCF后续之旅(15): 逻辑地址和物理地址 
WCF后续之旅(16): 消息是如何分发到Endpoint的--消息筛选(Message Filter) 
WCF后续之旅(17):通过tcpTracer进行消息的路由


作者:Artech
出处:http://artech.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
posted @ 2008-09-17 09:32  Artech  阅读(...)  评论(... 编辑 收藏