[WCF]终结点与服务寻址(二)

  本文是终结点与服务寻址系列的第二篇文章。

2.使用基地址部署更具移植性的服务终结点

  在《[WCF]终结点与服务寻址(一)》的实验中,我为服务终结点部署设置的访问地址(逻辑地址)使用的是绝对地址。使用这样的地址本身不存在什么技术上的问题,但WCF让开发人员有另外的选择,那就是服务宿主使用基地址,而服务终结点使用相对地址,最后同样为终结点构成完整的访问地址。在本文的实验中,服务契约与服务的实现都没有任何改变,但我对服务宿主程序做了一些修改,使得启动宿主程序时,显示更多的信息,修改后的代码如下:

代码
namespace WCF_Study1.Host
{
class Program
{
static void Main(string[] args)
{
using(ServiceHost host = new ServiceHost(typeof(WCF_Study1.ServiceContracts.SystemInfoService)))
{
host.Open();

DisplayAllBaseAddress(host);

DisplayAllEndpointAddress(host);

Console.WriteLine(
"服务已经启动,按任意键关闭服务.....");

Console.ReadLine();
}
}





private static void DisplayAllBaseAddress(ServiceHost host)
{
Console.WriteLine(
"服务端基地址列表:");

foreach (Uri address in host.BaseAddresses)
Console.WriteLine(address.ToString());

Console.WriteLine();
}





private static void DisplayAllEndpointAddress(ServiceHost host)
{
Console.WriteLine(
"服务端终结点地址列表:");

foreach(ChannelDispatcher cd in host.ChannelDispatchers)
foreach (EndpointDispatcher ed in cd.Endpoints)
{
Console.WriteLine(
"服务终结点逻辑地址:{0}", ed.EndpointAddress.Uri.ToString());
}

/*
* 输出结果应为:
* 服务端终结点地址列表:
* 服务终结点逻辑地址:
http://localhost:8000/SystemInfoService
* 服务终结点逻辑地址:net.tcp://localhost:8001/SystemInfoService
* 服务终结点逻辑地址:
http://localhost:8880/meta
* 服务终结点逻辑地址:
http://localhost:8000/
*
* 可以看到,列表中前两个终结点就是我们发布的服务的终结点。由于使用了不一样的基地址,
* 因此即使配置终结点使使用的相对地址一样,也不会出现冲突,因为最后整合的完整地址是不
* 一样的。也可以看到,尽管我没有进行匹配,这两个终结点也知道自己应该匹配到哪个基地址
* 中,依据就是终结点配置时使用的绑定类型,WCF运行时会自动根据终结点的绑定类型,从服务
* 的基地址列表中搜寻可以匹配的基地址。
* 同时也可以看到第三个终结点地址为服务元素据获取服务发布的终结点,至于最后一
* 个是什么我也不清楚,希望有朋友可以告诉我一下。
*/
Console.WriteLine();
}
}
}

我所添加的两个方法,用于输出服务宿主程序中所有的基地址和终结点逻辑地址。这些代码也略微揭示了终结点、终结点地址、服务宿主三者的关系。在DisplayAllEndpointAddress方法中,出现了一个ChannelDispatcher类型,该类型代表信道分发器,关于它的一些作用,在本系列文章往后介绍。接下来就是服务端的配置代码:

代码
<services>
<service name="WCF_Study1.ServiceContracts.SystemInfoService"
behaviorConfiguration
="metaBehavior">
<endpoint address="SystemInfoService"
binding
="basicHttpBinding"
contract
="WCF_Study1.ServiceContracts.ISystemInfoServiceContract" />

<endpoint address="SystemInfoService"
binding
="netTcpBinding"
contract
="WCF_Study1.ServiceContracts.ISystemInfoServiceContract" />

<endpoint address="http://localhost:8002/SystemInfoServiceV2"
binding
="basicHttpBinding"
contract
="WCF_Study1.ServiceContracts.ISystemInfoServiceContract" />

<!--
上面最后一个终结点并没有采用基地址,而是采用绝对地址,完全可以正常被部署。
-->

<!--
<endpoint address="SystemInfoService"
binding="netNamedPipeBinding"
contract="WCF_Study1.ServiceContracts.ISystemInfoServiceContract" />
当我添加这个新终结点后,启动服务时会触发异常,因为此终结点没法找到能够匹配的
基地址,因为我没有定义使用命名管道协议的基地址。
-->

<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8001"/>
<add baseAddress="http://localhost:8000"/>

<!--
<add baseAddress="net.tcp//localhost:8000"/>
当我尝试使用这个基地址,而不是基于8001端口的那个。在启动服务的过程就抛出了异常。
原因就在于两个基地址公用了端口,并且无论我以什么顺序配置两个基地址,导致异常抛出
的一定是TCP协议的基地址。
-->

<!--
<add baseAddress="http://localhost:8002"/>
当我尝试添加这个基地址的时候,会抛出基地址冲突异常,原因是WCF规定,每个服务中,每
个类型的绑定协议只能定义一个基地址。
-->
</baseAddresses>
</host>
</service>
</services>

首先是<host></host>节点中的<baseAddress></baseAddress>节点中,添加了两个及地址,分别是基于TCP协议的(net.tcp)和基于HTTP协议的(http)访问地址,在我的注释里面也表明了,WCF中不允许一个服务宿主拥有两个基于同样传输协议的基地址,同时也不允许两个不同协议的基地址完全一样。以下是当出现这两个错误的时候,所抛出的异常信息:

  配置好基地址以后,接下来就是配置服务终结点。在这个实验中,我部署了两个终结点,公开相同的服务契约,但使用不一样的绑定,一个是基于TCP的,另一个是基于HTTP的。可以看到,我对两个终结点的Address属性均设置为SystemInfoService,单看这个属性的设置,就好象我为两个终结点设置同样的访问地址一样,其实当它们真正被部署的时候,所公开的访问地址是不一样的。以下是启动服务宿主程序后的截图:

从图中可以看到,两个终结点的访问地址是不一样的,并且就是基地址和终结点的address(相对地址)属性值拼起来的合成地址。那这些只拥有相对地址的终结点是怎么跟服务宿主中配置好的基地址相匹配呢?答案就是这些终结点配置代码中所指定的绑定类型。当服务宿主启动的时候,WCF会根据每个终结点所采用的绑定类型对应的协议(例如basicHttpBinding对应HTTP协议,netTcpBinding对应TCP协议),从基地址列表中找到基于相同协议的地址。接下来我还故意定义了一个不存在对应基地址的终结点,这个终结点是基于命名管道通信协议的。读者如果把该终结点的配置代码取消注释,然后启动服务宿主程序,就会看见以下异常:

  读者还可以从刚才的配置代码中看到,还有一个访问地址为http://localhost:8002/SystemInfoServiceV2的终结点,该终结点并没有使用基地址列表中的任何一个,而是使用了绝对的逻辑地址,而这种方式是允许的,那是否代表服务宿主多了一个基地址呢?答案是否定的,因为当我在配置文件中加入一个新的基于HTTP协议的基地址,并启动服务宿主,马上会看到刚才的异常。

  服务端任务完成了,接下来就是看客户端发生的事情了。首先给出客户端的某个方法定义的程序代码:

代码
     private static void CallServiceWithoutSpecifyingTheEndpoint_GetServerMachineName()
{
WCF_Study1.Client.Services.SystemInfoServiceContractClient proxy
= new WCF_Study1.Client.Services.SystemInfoServiceContractClient();
/*
* 使用没有构造方法参数的构造方法版本,由此来构造服务代理时,马上会抛出异常,
* 因为没有明确使用哪个终结点,而此时有三个基于不同协议的终结点可供选择,因此必须
* 明确使用哪个终结点。
*/

Console.WriteLine(
"调用背景:没有指定使用哪个终结点调用服务。");

Console.WriteLine(
"调用结果:Remote Server Name:{0}", proxy.GetServerMachineName());

Console.WriteLine();
}

当我在程序中调用这个方法的时候,马上会抛出以下异常:

出现这个异常的原因就是没有为服务代理对象指定一个明确的终结点,而且现在在客户端配置文件中具有多个客户端终结点的配置。接下来是客户端的配置代码: 

代码
<client>
<endpoint address="http://localhost:8000/SystemInfoService"
binding
="basicHttpBinding"
bindingConfiguration
="BasicHttpBinding_ISystemInfoServiceContract"
contract
="Services.ISystemInfoServiceContract"
name
="BasicHttpBinding_ISystemInfoServiceContract" />
<endpoint address="net.tcp://localhost:8001/SystemInfoService"
binding
="netTcpBinding"
bindingConfiguration
="NetTcpBinding_ISystemInfoServiceContract"
contract
="Services.ISystemInfoServiceContract"
name
="NetTcpBinding_ISystemInfoServiceContract">
<identity>
<userPrincipalName value="KL-THINK\KL" />
</identity>
</endpoint>
<endpoint address="http://localhost:8002/SystemInfoServiceV2"
binding
="basicHttpBinding"
bindingConfiguration
="BasicHttpBinding_ISystemInfoServiceContract"
contract
="Services.ISystemInfoServiceContract"
name
="BasicHttpBinding_ISystemInfoServiceContract1" />
</client>

在配置代码中,每个<endpoint/>节点都设置了一个name属性,而正是要使用这个属性的值来为服务代理对象指定使用哪个客户端终结点,方法是在服务代理对象的构造方法中传入该属性的值,也就是终结点的配置名称,修改后的方法代码如下所示:

代码
private static void CallServiceInHttpEndpoint_GetServerMachineName()
{
WCF_Study1.Client.Services.SystemInfoServiceContractClient proxy
= new WCF_Study1.Client.Services.SystemInfoServiceContractClient("BasicHttpBinding_ISystemInfoServiceContract");

Console.WriteLine(
"调用背景:使用基于HTTP协议作为绑定的终结点来调用服务。");

Console.WriteLine(
"调用的终结点逻辑地址:{0}", proxy.Endpoint.Address.ToString());

Console.WriteLine(
"调用结果:Remote Server Name:{0}", proxy.GetServerMachineName());

Console.WriteLine();
}





private static void CallServiceInTcpEndpoint_GetServerMachineName()
{
WCF_Study1.Client.Services.SystemInfoServiceContractClient proxy
= new WCF_Study1.Client.Services.SystemInfoServiceContractClient("NetTcpBinding_ISystemInfoServiceContract");

Console.WriteLine(
"调用背景:使用基于TCP协议作为绑定的终结点来调用服务。");

Console.WriteLine(
"调用的终结点逻辑地址:{0}", proxy.Endpoint.Address.ToString());

Console.WriteLine(
"调用结果:Remote Server Name:{0}", proxy.GetServerMachineName());

Console.WriteLine();
}

  在服务宿主中定义基地址的一个很重要的原因就是方便移植。对于大型应用,一旦公开超过10个甚至几十个服务终结点后,如果因为要迁移服务宿主进程到别的服务器,而一个一个地修改终结点访问地址,是多么低效和容易出错(当然我可以使用文本替换这种功能)。

posted @ 2010-05-09 21:02  DOF_KL  阅读(2360)  评论(2编辑  收藏  举报