• WCF


    WCF顾名思义 即解决在windows平台下与各种平台中的程序之间通信的问题 而终结点则是WCF通信的唯一手段 终结点承载了所有通信的功能 一个WCF服务是通过对应的终结点发布出来的 发布出来的数据称为元数据 这些元数据包含了很多的信息 最重要的是 它还包含了如何调用WCF服务 即调用WCF暴露出来的方法 客户端也是通过对应的终结点来对服务进行调用的 即服务端通过终结点(假设名称为A)来发布元数据 客户端通过终结点(A)来获取元数据进而调用服务暴露出的方法

    终结点

    由契约 地址 绑定三个部分组成 本节主要讲解终结点中的地址部分 

    终结点地址

    地址的功能是用来定位远端的服务 没有地址则无法定位到某个WCF服务 且地址还提供了额外的寻址信息和进行服务认证的服务身份安全信息 web服务科视为一种网络资源 服务通过终结点发布出来

    终结点地址的URI

    终结点地址的核心就是一个URI(统一资源标识符)URI除了标识地址外 它还具有代表服务所在的位置和消息路由的目标地址 所以URI是唯一的标识一个网络资源同时它也代表了资源所处的位置以及访问方式 URI具有以下结构
    传输协议://主机名或域名或IP地址/资源路径 如
    http://art.com/myservice/test.svc

    配置终结点

    除了手动编码的方式实现为服务添加终结点 通常情况下 都是以config文件来为WCF服务配置终结点 
    使用<system.serviceModel>来表示WCF配置节 <services>是它的子节 表示一组服务 其中service节表示具体的一个服务 services可包含多个service子节 如 

     1 <?xml version="1.0" encoding="utf-8"?>
     2 <configuration>
     3   <system.serviceModel>
     4     <services>
     5       <service behaviorConfiguration="metadataBehavior" name="Service.CalculatorService">
     6         <endpoint name=""
     7          address="http://127.0.0.1/calculatorservice"
     8          binding="ws2007HttpBinding"
     9                contract="Service.Interface.ICalculator" />
    10       </service>
    11     </services>
    12   </system.serviceModel>
    13 </configuration>
    View Code

    service节点表示具体的一个服务 它的name属性表示WCF单个服务的类的名称 比如WCF服务有一个用于计算的CalculatorService服务 例子中的Service.CalculatorService Service是CalculatorService 服务的命名空间 它的endpoint子节通过终结点三要素address、binding和contract分别表示终结点的地址、绑定方式和契约 endpoint子节的name属性表示终结点的名字 而如果服务是寄宿于IIS 则服务项目的根目录需要一个svc文件 该文件就是终结点的地址 而无需在endpoint 中配置address address=""即可

    配置终结点基地址

    使用基地址后 endpoint终结点地址就只需要使用相对地址 即基地址+endpoint的地址 

    1 <service behaviorConfiguration="metadataBehavior" name="Service.CalculatorService">
    2   <host>
    3     <baseAddresses>
    4       <add baseAddress="http://127.0.0.1"/>
    5     </baseAddresses>
    6   </host>
    7   <endpoint address="calculator" binding="ws2007HttpBinding" bindingConfiguration=""
    8         contract="Service.Interface.ICalculator" />
    9 </service>
    View Code

    使用Host/baseAddress来表示一个具体的服务的基地址 如果按照以上配置 那么该服务的地址就是http://127.0.0.1/calculator 如果服务时寄宿于IIS 则根目录的SVC文件就是服务的基地址 再为endpoint指定address属性 假设address=Add 则该服务的地址就是http://127.0.0.1/calculator.svc/Add

    EndpointAddress类

    此类表示的是终结点的地址 即配置文件中endPoint节点的adreess属性 此类提供以下几个属性来表示endPoint的adreess 

    1 public class EndpointAddress
    2 {
    3     public Uri Uri { get; }
    4     public EndpointIdentity Identity { get; }
    5     public AddressHeaderCollection Headers { get; }
    6 }
    View Code

    终结点的地址不仅仅定位了服务的位置 还提供额外的寻址信、用于进行服务认证的服务身份信息 即一个EndpointAddress包含了三个功能

    1.标识服务同时定位服务 通过属性Uri获取
    2.服务身份标识 通过属性Identity 获取
    3.辅助寻址 通过属性Headers获取

    Identity属性

    客户端在调用服务前 服务端将自己的凭证(Windows凭证、X.509证书凭证等)提供给客户端 客户端通过Identity 代表的服务身份与服务端凭证进行比较从而验证正在调用服务确实是自己所希望调用的 而不是一个钓鱼服务

    Headers属性

    Headers是一个类型为AddressHeaderCollection的集合 集合存储的是AddressHeader对象 这个Headers属性我们称为地址报头 即报头是用来存储一些发送到服务端的寻址信息的容器 WCF支持多种不同类型的信息 信息格式可以是XML、Json或纯文本的等等 而使用得最多的XML格式的信息都是SOAP的 SOAP是一个传输协议 它由报头Header和主体body组成 主体部分一般是对业务数据的封装 而消息报头用于保存一些控制信息 Headers属性用于客户端时 AddressHeaderCollection会被添加到客户端的SOAP请求的报头中 Headers属性用于服务端时 在根据客户端请求的消息进行终结点路由过程中时 会提取相应的客户端终结点的地址中的报头信息和本地终结点的地址报头进行比较以选择出与请求相匹配的终结点 换句话说 即客户端请求时 会将一个标识作为报头存储在Headers中 服务端接收到请求 则调用自己的终结点地址的Headers读取报头标识 两厢匹配 以选择出与客户端请求消息相匹配的终结点以返回服务数据

    实例:禁止客户端与服务端报头不匹配的请求

    服务端的终结点地址的报头作用就是用于辅助实现对终结点的选择 因为一个服务可能实现多个契约 则该服务就有多个终结点 通过在服务寄宿的项目中配置的config文件中设定终结点地址报头后 那么在客户端请求时也必须提供完全一样的地址报头 只有这样服务端才能根据客户端终结点地址的报头去筛选服务端自己的地址报头 一旦匹配成功 且请求的Uri是一样的 才会返回对应的终结点的服务给客户端

    在Hosting项目中设置app.config配置文件 

     1 <system.serviceModel>
     2   <services>
     3     <service name="Service.CalculatorService">
     4       <endpoint address="http://127.0.0.1:9999/calculatorservice" binding="wsHttpBinding" contract="Service.Interface.ICalculator">
     5         <headers>
     6           <sn xmlns="http://www.artech.com/">{DDA095DA-93CA-49EF-BE01-EF5B47179FD0}</sn>
     7         </headers>
     8       </endpoint>
     9     </service>
    10   </services>
    11 </system.serviceModel>
    View Code

    使用endpoint的headers节来设置终结点的报头 sn是一个自定义的节 它表示报头名字 xmlns属性是必须的 它表示报头的命名空间 这个值可以是任意的 不一定非得是以上定义的值 报头节包含的文本也是自定义的 它表示报头信息

    配置完成后 打开控制台项目Hosting的入口文件 开启服务寄宿 

    ServiceHost host = new ServiceHost(typeof(CalculatorService));
    host.Open();
    Console.Read();

    接着在客户端定义配置文件我们要与服务端的报头匹配 所以必须如下定义一个客户端请求的报头 

    1 <system.serviceModel>
    2   <client>
    3     <endpoint name="calculatorservice" address="http://127.0.0.1:9999/calculatorservice" binding="wsHttpBinding" contract="Service.Interface.ICalculator">
    4       <headers>
    5         <sn xmlns="http://www.123.com/">{DDA095DA-93CA-49EF-BE01-EF5B47179FD0}</sn>
    6       </headers>
    7     </endpoint>
    8   </client>
    9 </system.serviceModel>
    View Code

    现在可以运行客户端程序了 且因为报头一致 所以会成功调用服务

    假如预先在配置文件中设置了和服务端相同的报头 则调用服务的代码不变 而如果我们没有预先在客户端配置文件中设定终结点的报头 则必须手动编码将报头添加到请求服务的终结点地址的报头中 实现如下:

     1 using System.ServiceModel;
     2 using Service.Interface;
     3 using System.ServiceModel.Channels;
     4 
     5 namespace Client
     6 {
     7     class Program
     8     {
     9         static void Main(string[] args)
    10         {
    11             //根据终结点创建服务代理对象
    12             ChannelFactory<ICalculator> ChannelFactory = new ChannelFactory<ICalculator>("calculatorservice");
    13             ICalculator Calculator = ChannelFactory.CreateChannel();
    14             //设置服务代理对象的请求出站前的行为
    15             OperationContextScope contextScop = new OperationContextScope(Calculator as IClientChannel);
    16             string sn = "{DDA095DA-93CA-49EF-BE01-EF5B47179FD0}";
    17             //终结点地址的报头 参数依次为报头名字 报头命名空间和报头的值
    18             AddressHeader header = AddressHeader.CreateAddressHeader("sn", "http://www.123.com/", sn);
    19             //动态将报头转换为消息头 以便发送请求时 直接将报头消息发送到服务方
    20             MessageHeader msgHeader = header.ToMessageHeader();
    21             OperationContext.Current.OutgoingMessageHeaders.Add(msgHeader);
    22             //调用服务
    23             Console.WriteLine(Calculator.Add(1, 2));
    24             Console.Read();
    25         }
    26     }
    27 }
    View Code

    设置服务行为

    可以通过System.ServiceModel的ServiceBehavior特性设置服务端的服务类的行为 这里先介绍它AddressFilterMode参数 

     1 using Service.Interface;
     2 using System.ServiceModel;
     3 
     4 
     5 namespace Service
     6 {
     7     [ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
     8     public class CalculatorService : ICalculator
     9     {
    10         public double Add(double x, double y)
    11         {
    12             return x + y;
    13         }
    14     }
    15 }
    View Code

    AddressFilterMode有三个枚举值 Exact(默认 对终结点地址精确匹配) Prefix(前缀匹配)和any(无需匹配 只要Uri域名部分是本机的域名即可)此枚举可以改变服务对于侦听到的客户端请求中的终结点地址的筛选条件 以上代码将筛选地址的行为设为any 则就算服务的配置文件设定了要匹配报头 客户端也无需设置报头就可以调用服务

    共享端口

    如果你的服务端有两个或以上的寄宿服务 而服务之间相互独立 那么他们如果共享一个端口号 则只有第一个服务能正常开启 如何解决端口共享问题呢 要分两种情况 一是Http/Https端口共享 另一种是Net.TCP端口共享 在一般网络环境中 为了避免网络攻击 防火墙会将大部分端口屏蔽掉 仅仅保留那些常用的网络服务所用的端口 总之我们不能保证每个跨防火墙通信的程序都具有自己唯一的端口 因为会被屏蔽 防火墙只准多个程序使用一个或少量的端口 所以需要做到让服务端的多个通信程序端口共享

    Http/Https端口共享 

    如果通信是基于Http/Https的 则可以使用HTTP.SYS监听机制(IIS6及其以上版本实现了该驱动) 它是一个用于监听网络请求的驱动程序 IIS或web程序都可以使用它来监听来自网络的请求 默认该共享端口是开启的 无需特别指定

    TCP端口共享
    IIS只接受基于HTTP的请求 对于采用TCP协议的服务 则无效 而是使用windows提供的服务Net.Tcp Port Sharing Service
    从技术上讲 它实现了与HTTP.SYS一样的监听机制 专用于TCP请求的监听 让多个服务程序共享一个TCP端口 所有安装了.net framework3.5及其以上版本的 window都提供了Net.Tcp Port Sharing Service服务 右击计算机-服务-开启它即可

    服务端开启TCP端口共享

    手动编码方式开启共享TCP端口  

     1 using System.ServiceModel;
     2 using Service;
     3 using Service.Interface;
     4 
     5 namespace Hosting
     6 {
     7     class Program
     8     {
     9         static void Main(string[] args)
    10         {
    11             ServiceHost host = new ServiceHost(typeof(CalculatorService));
    12             NetTcpBinding bingding = new NetTcpBinding();
    13             bingding.PortSharingEnabled = true;
    14             host.AddServiceEndpoint(typeof(ICalculator), bingding, "net.tcp://127.0.0.1:9999/calculatorservice");
    15             host.Open();
    16             Console.Read();
    17         }
    18     }
    19 }
    View Code

    配置方式开启共享TCP端口 

     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="Service.CalculatorService">
    11         <endpoint address="net.tcp://127.0.0.1:9999/calculatorservice" binding="netTcpBinding" contract="Service.Interface.ICalculator" bindingConfiguration="portSharingBinding"/>
    12       </service>
    13     </services>
    14   </system.serviceModel>
    15 </configuration>
    View Code

    添加一个bindings节 指定它的子节为netTcpBinding 为netTcpBinding指定一个TCP端口共享 name属性是自定义的portSharingEnabled设为true 接着在service节的endpoint节中引用刚刚添加的TCP端口共享绑定(使用bindingConfiguration属性 值为TCP端口共享绑定的name)开启端口共享后 windows提供的服务Net.Tcp Port Sharing Service会为所有端口共享的终结点提供一个统一的端口

    终结点的逻辑地址和物理地址

    在WCF中 每个终结点都包含两个不同的地址(逻辑地址与物理地址)逻辑地址就是endpoint节中address的值 而物理地址则是服务端监听请求时或客户端发送请求时的地址 默认情况下 终结点的逻辑与物理地址其实是同一个uri 即(EndPointAddress.Uri)如果没有手动配置终结点的物理地址 会默认逻辑地址=物理地址

    手动编码添加服务端侦听的物理地址

    1 ServiceHost host = new ServiceHost(typeof(CalculatorService));
    2 host.AddServiceEndpoint(typeof(ICalculator), new WSHttpBinding(), "http://127.0.0.1:9999/calculatorservice", new Uri("http://127.0.0.1:9999/calculatorservice"));
    3 host.Open();
    4 Console.Read();

    AddServiceEndpoint方法可以动态为服务寄宿对象添加一个终结点 参数1为契约、参数2为绑定、参数3为终结点逻辑地址 参数4就是我们添加的物理地址 既然添加了物理地址 则服务端在侦听请求时 就会监视这个物理地址 而如果没提供物理地址 则它会监听逻辑地址

    配置方式添加服务端侦听的物理地址

    <endpoint address="http://127.0.0.1:9999/calculatorservice" binding="ws2007HttpBinding" contract="Service.Interface.ICalculator" listenUri="http://127.0.0.1:8888/calculatorservice"/>

    只需在endpoint节中指定一个listenUri即可完成物理地址的添加

    配置方式添加客户端发送请求的物理地址

    前面说过 有了物理地址 那么服务端侦听时 就会监视物理地址而非逻辑地址了 在客户端 也可以添加物理地址 添加后 客户端发送请求的地址就是用物理地址来发送了 而非逻辑地址了 以下添加了一个客户端配置文件 用于发送请求是一个终结点的物理地址 

     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 name="calculatorservice" address="http://127.0.0.1:9999/calculatorservice" binding="ws2007HttpBinding" contract="Service.Interface.ICalculator" behaviorConfiguration="clientViaBehavior"/>
    13     </client>
    14   </system.serviceModel>
    15 </configuration>
    View Code

    客户端终结点没有listenUri属性 该属性是用于服务端的侦听的物理地址 而客户端 需要添加behaviors节 该节表示客户端请求行为的集合 可以为在behaviors中添加endpointBehaviors来设定客户端请求的终结点的物理地址 clientVia节的属性viaUri就是一个终结点的物理地址 我们在endpoint节中将behaviorConfiguration指向刚才创建的behavior的name即可 这样该endpoint就有了一个物理地址

    服务端侦听模式

    就算我们配置了服务端侦听的终结点的物理地址listenUri 但还需要设置listenUriMode属性 即listenUri和listenUriMode共同决定服务端侦听的是哪个地址 listenUriMode是一个枚举 可能的值为Explicit和Unique Explicit表示以listenUri属性设置的地址为侦听地址 而Unique则根据情况表示为以下:

    1.如果采用的是TCP协议的地址且不采用端口共享且该地址被占用 则自动选择一个未被占用的端口替换掉原来地址的端口

    2.如果采用的是TCP协议的地址且采用端口共享 则自动在原来的地址后边加一个唯一的GUID作为地址后缀以确保唯一性

    3.非TCP协议的地址 则自动在原来的地址后边加一个唯一的GUID作为地址后缀以确保唯一性(比如HTTP/HTTPS本来就是采用端口共享的 需要.netframework3.5或以上)

    为验证以上侦听模式采用Unique后 侦听地址的唯一性 我们打开服务端配置文件 为一个服务添加多个终结点 修改如下  

     1 <?xml version="1.0" encoding="utf-8" ?>
     2 <configuration>
     3   <system.serviceModel>
     4     <bindings>
     5       <netTcpBinding>
     6         <binding name="portsharebindings" portSharingEnabled="true"/>
     7       </netTcpBinding>
     8     </bindings>
     9 
    10 
    11     <services>
    12       <service name="Service.CalculatorService">
    13         <endpoint address="http://127.0.0.1:5555/calculatorservice1" binding="wsHttpBinding"  contract="Service.Interface.ICalculator"  name="CalculatorService"/>
    14         <endpoint address="http://127.0.0.1:6666/calculatorservice2" binding="wsHttpBinding"  contract="Service.Interface.ICalculator"  name="CalculatorService" listenUriMode="Unique"/>
    15         <endpoint address="http://127.0.0.1:7777/calculatorservice3" binding="netTcpBinding"  contract="Service.Interface.ICalculator"  name="CalculatorService"/>
    16         <endpoint address="http://127.0.0.1:8888/calculatorservice4" binding="netTcpBinding"  contract="Service.Interface.ICalculator"  name="CalculatorService" listenUriMode="Unique"/>
    17         <endpoint address="http://127.0.0.1:9999/calculatorservice5" binding="netTcpBinding"  contract="Service.Interface.ICalculator"  name="CalculatorService" listenUriMode="Unique" bindingConfiguration="portsharebindings"/>
    18       </service>
    19     </services>
    20 
    21   </system.serviceModel>
    22 </configuration>
    View Code

    我们没有显示的指定侦听的物理地址 所以以上几个终结点使用的侦听地址就是终结点的物理地址 即Address.Uri 前两个采用的绑定方式为wsHttpBinding 后3个采用netTcpBinding 

    现在讲服务寄宿于控制台程序 打印出各个侦听地址的结果 

     1 namespace Hosting
     2 {
     3     class Program
     4     {
     5         static void Main(string[] args)
     6         {
     7             ServiceHost host = new ServiceHost(typeof(CalculatorService));
     8             host.Open();
     9             int i = 0;
    10             foreach (ChannelDispatcher item in host.ChannelDispatchers)
    11             {
    12           //打印逻辑地址
    13                 Console.WriteLine("{0}:{1}",i,item.Listener.Uri);
    14             }
    15             Console.WriteLine("服务已开启……");
    16             Console.Read();
    17         }
    18     }
    19 }
    View Code

    结果为

    第一个终结点没有设置侦听模式 所以服务端侦听的就是终结点的逻辑地址

    第二个终结点设置了侦听模式为Unique 所以服务端侦听的地址是其逻辑地址+GUID 以确保该地址的唯一性

    第三个终结点没有设置侦听模式 所以服务端侦听的就是终结点的逻辑地址

    第四个终结点设置了侦听模式为Unique 又因其端口8888是被控制台应用所占 所以它的端口8888被换成了其他端口38657 

    第五个终结点设置了侦听模式为Unique 且它声明了要使用共享端口 所以服务端侦听的地址是其逻辑地址+GUID 以确保该地址的唯一性

    服务行为和终结点行为

    服务行为和终结点行为 通过serviceBehaviors节和endpointBehaviors节定义 它们都隶属于behaviors节 这两种行为有很多的功能 比如上面使用了endpointBehaviors定义了客户端请求服务的物理地址 两种行为的结构如下 

     1 <system.serviceModel>
     2   <behaviors>
     3     <serviceBehaviors>
     4       <behavior name="111"></behavior>
     5     </serviceBehaviors>
     6     <endpointBehaviors>
     7       <behavior name="222"></behavior>
     8     </endpointBehaviors>
     9   </behaviors>
    10   <services behaviorConfiguration="111">
    11     <service>
    12       <endpoint behaviorConfiguration="222"/>
    13     </service>
    14   </services>
    15 </system.serviceModel>
    View Code

    要使服务应用serviceBehaviors节点的某个行为 则需要设置Service节点的behaviorConfiguration属性 该属性指向一个serviceBehaviors节点的某个behavior节点的name 要使终结点应用endpointBehaviors节点的某个行为 则需要设置endpoint节点的behaviorConfiguration属性 该属性指向一个endpointBehaviors节点的某个behavior节点的name

    客户端和服务端终结点地址的匹配

    我们假设服务端的某个A服务的终结点地址为 http://127.0.0.1:9999/calculatorservice 那么客户端要请求这个A服务 则它的终结点地址也必须是这个地址 假设服务端终结点的物理地址为 

    1 <system.serviceModel>
    2   <services>
    3     <service name="Service.CalculatorService">
    4       <endpoint address="http://127.0.0.1:9999/calculatorservice" binding="wsHttpBinding" listenUri="http://127.0.0.1:8888/calculatorservice"    
    5          contract="Service.Interface.ICalculator"/>
    6     </service>
    7   </services>
    8 </system.serviceModel>
    View Code

    则在客户端我们同样需要配置客户端终结点的物理地址 即请求发送到哪个地址 

     1 <system.serviceModel>
     2   <behaviors>
     3     <endpointBehaviors>
     4       <behavior name="mybehavior">
     5         <clientVia viaUri="http://127.0.0.1:8888/calculatorservice"/>
     6       </behavior>
     7     </endpointBehaviors>
     8   </behaviors>
     9     
    10   <client>
    11     <endpoint address="http://127.0.0.1:9999/calculatorservice" binding="wsHttpBinding" behaviorConfiguration="mybehavior" 
    12         contract="Service.Interface.ICalculator" name="calculatorservice">
    13     </endpoint>
    14   </client>
    15 </system.serviceModel>
    View Code

    信道分发器

    当服务成功寄宿后 WCF服务会在它所寄宿的服务端根据终结点来创建信道分发器 假设我们为同一个服务指定了三个终结点地址 即 客户端可以通过这三个终结点地址来调用服务 如

    http://127.0.0.1:9999/calculatorservice
    http://127.0.0.1:8888/calculatorservice
    http://127.0.0.1:7777/calculatorservice

    成功寄宿服务后 WCF就会根据该服务的3个终结点地址来创建三个信道分发器 每个信道分发器又包含了自己的信道侦听器 信道侦听器用于侦听信道分发器所绑定的终结点地址的端口 当有请求进入信道分发器时 信道分发器的信道侦听器就会侦听到这个请求然后将请求的信息移交给信道分发器 接着信道分发器会根据请求信息进行消息筛选进而确定要使用的终结点分发器 终结点分发器最终完成对请求消息的处理 即信道分发器用于侦听和接收请求 接到请求后信道分发器会根据客户端请求自身所携带的信息将请求信息与终结点分发器集合的item相匹配 筛选一番(message filter) 才能确定要使用具体的哪个终结点来处理请求

    信道分发器(ChannelDispatcher对象)
    信道侦听器(ChannelListener对象)
    终结点分发器(EndPointDispatcher对象)

    根据上面的3个地址 当服务成功寄宿后(open后)WCF服务就会创建三个信道分发器 它是根据唯一的端口号来确定需要创建多少个ChannelDispatcher对象的 我们假设前两个终结点地址设置了共享端口 端口号为666 则WCF会创建两个ChannelDispatcher 一个是666的共享端口的ChannelDispatcher对象 另一个是无共享的777端口的ChannelDispatcher对象 看例子

    为服务端的一个服务配置多个终结点 

     1 <services>
     2   <service name="Service.CalculatorService">
     3     <endpoint address="http://127.0.0.1:7777/calculatorservice" binding="wsHttpBinding" listenUri="http://127.0.0.1:9999/calculatorservice"    
     4         contract="Service.Interface.ICalculator"/>
     5     <endpoint address="http://127.0.0.1:8888/calculatorservice" binding="wsHttpBinding" listenUri="http://127.0.0.1:9999/calculatorservice"    
     6         contract="Service.Interface.ICalculator"/>
     7     <endpoint address="http://127.0.0.1:6666/calculatorservice" binding="wsHttpBinding" listenUri="http://127.0.0.1:5555/calculatorservice"    
     8         contract="Service.Interface.ICalculator"/>
     9   </service>
    10 </services>
    View Code

    因为是wsHttpBinding 即终结点是基于Http协议的 所以当为前两个终结点指定了侦听物理地址且他们的端口号都为9999 那么默认HTTP.SYS会开启端口共享 而信道分发器的个数是根据侦听地址的端口号的唯一性来创建的 所以 此例子中WCF会创建两个信道分发器 一个是基于9999端口的 另一个是5555端口的 

    现在假设客户端请求了http://127.0.0.1:9999/calculatorservice 服务的信道分发器侦听到这个请求后将该地址与它包含的终结点分发器集合的item相匹配(终结点分发器集合是信道分发器的属性) 将会发现有两个终结点分发器与之吻合 即前两个端口共享的终结点会与请求地址匹配成功 而要筛选出最终客户端要调用的是那个服务 则终结点分发器会根据客户端请求中的终结点地址的报头 即逻辑地址来确定最终要返回哪个服务给客户端 将服务端的配置文件设为上面为一个服务定义的多个终结点 接着在控制台入口文件实现服务寄宿 在寄宿后我们来测试一下信道分发器的个数和信道分发器的终结点分发器个数和每个请求最终使用的服务端终结点地址: 

     1 namespace Hosting
     2 {
     3     class Program
     4     {
     5         static void Main(string[] args)
     6         {
     7             ServiceHost host = new ServiceHost(typeof(CalculatorService));
     8             host.Open();
     9             Console.WriteLine("信道分发器总个数:{0}", host.ChannelDispatchers.Count());
    10             Console.WriteLine();
    11             foreach (ChannelDispatcher item in host.ChannelDispatchers)
    12             {
    13                 Console.WriteLine("服务端信道分发器侦听地址为:{0}", item.Listener.Uri);
    14                 Console.WriteLine("终结点分发器个数:{0}", item.Endpoints.Count());
    15                 foreach (EndpointDispatcher itempoint in item.Endpoints)
    16                 {
    17                     Console.WriteLine("匹配的服务端终结点逻辑地址为:{0}", itempoint.EndpointAddress.Uri);
    18                 }
    19                 Console.WriteLine();
    20             }
    21             Console.Read();
    22         }
    23     }
    24 }
    View Code

    结果为

    终结点分发器的消息筛选器

    终结点分发器是一个EndpointDispatcher类型的对象 它有两个表示消息筛选器的属性 AddressFilter和ContractFilter 两个属性都是一个MessageFilter类型的对象 这两个属性分别表示地址筛选器和契约筛选器 顾名思义 两个属性是用于当请求从信道分发器移交给终结点分发器时 AddressFilter处理客户端请求中的终结点逻辑地址 ContractFilter 处理客户端请求中的终结点的契约 无论是AddressFilter还是ContractFilter 都自动使用Match方法来筛选请求的信息 WCF为我们预定义了6种消息筛选器 以下是最常见的两种:

    ActionMessageFilter

    判断请求信息(SOAP)中的<Action>报头是否和对应的服务端终结点的契约提供的任何一个操作的Action一致(服务提供的方法名称 如用于提供计算服务的Add方法)

    EndPointAddressMessageFilter

    判断请求信息(SOAP)中的<To>报头地址是否和对应的服务端终结点的逻辑地址一致(无论客户端发送请求的地址是不是物理地址 当他发送时<To>报头都是指的逻辑地址)

    ContractFilter使用Match方法时 使用的就是ActionMessageFilter筛选器 AddressFilter使用Match方法时 使用的就是EndPointAddressMessageFilter筛选器 你还可以使用ServiceBehavior特性来改变AddressFilter的筛选机制 [ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]

    WCF - 学习总目录

  • 相关阅读:
    request.getAttribute()和 request.getParameter()的区别
    jquery中$.get()提交和$.post()提交有区别吗?
    jQuery有几种选择器?
    jQuery 库中的 $() 是什么?
    JavaScript内置可用类型
    MySQL数据库中,常用的数据类型
    简单叙述一下MYSQL的优化
    什么是JDBC的最佳实践?
    Vue官网教程-条件渲染
    Vue官网教程-Class与Style绑定
  • 原文地址:https://www.cnblogs.com/myrocknroll/p/3261874.html
Copyright © 2020-2023  润新知