天天看點

[原創]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>      
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>      
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"      
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>      
12:        

為了驗證ListenUriMode.Unique模式下,我寫了下面一個簡單的例子:在對服務(Artech.ListenUriDemos.Services.CalculateService)進行寄宿的時候,為之添加了如下5個終結點,具體的配置如下:

[原創]WCF後續之旅(15): 邏輯位址和實體位址
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>      
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