• Windows Communication Foundation介绍(二)


    三、WCF的技术要素



    作为基于SOA(Service Oriented Architecture)的一个框架产品,WCF最重要的就是能够快捷的创建一个服务(Service)。如下图所示,一个WCF Service由下面三部分构成:

    uploads/200708/06_141701_wcf06.gif


    1、Service Class:一个标记了[ServiceContract]Attribute的类,在其中可能包含多个方法。除了标记了一些WCF特有的Attribute外,这个类与一般的类没有什么区别。
    2、Host(宿主):可以是应用程序,进程如Windows Service等,它是WCF Service运行的环境。
    3、Endpoints:可以是一个,也可以是一组,它是WCF实现通信的核心要素。

    WCF Service由一个Endpoints集合组成,每个Endpoint就是用于通信的入口,客户端和服务端通过Endpoint交换信息,如下图所示:

    uploads/200708/06_141710_wcf07.gif


    从图中我们可以看到一个Endpoint由三部分组成:Address,Binding,Contract。便于记忆,我们往往将这三部分称为是Endpoint的ABCs。

    Address 是Endpoint的网络地址,它标记了消息发送的目的地。Binding描述的是如何发送消息,例如消息发送的传输协议(如TCP,HTTP),安全 (如SSL,SOAP消息安全)。Contract则描述的是消息所包含的内容,以及消息的组织和操作方式,例如是one-way,duplex和 request/reply。所以Endpoint中的ABCs分别代表的含义就是:where,how,what。当WCF发送消息时,通过 address知道消息发送的地址,通过binding知道怎样来发送它,通过contract则知道发送的消息是什么。

    在WCF中,类 ServiceEndpoint代表了一个Endpoint,在类中包含的EndpointAddress,Binding, ContractDescription类型分别对应Endpoint的Address,Binding,Contract,如下图:

    uploads/200708/06_141715_wcf08.gif


    EndpointAddress类又包含URI,Identity和可选的headers集合组成,如下图:

    uploads/200708/06_141721_wcf09.gif


    Endpoint 安全的唯一性识别通常是通过其URI的值,但为了避免一些特殊情况造成URI的重复,又引入了Identity附加到URI上,保证了Endpoint地 址的唯一性。至于可选的AddressHeader则提供了一些附加的信息,尤其是当多个Endpoint在使用同样的URI地址信息时, AddressHeader就非常必要了。

    Binding类(位于System.ServiceModel.Channels命名空间)包含Name,Namespace和BindingElement集合,如下图:

    uploads/200708/06_141727_wcf10.gif


    Binding 的Name以及Namespace是服务元数据(service’s metadata)的唯一标识。BindingElement描述的是WCF通信时binding的方式。例如, SecurityBindingElement表示Endpoint使用SOAP消息安全方式,而 ReliableSessionBindingElement表示Endpoint利用可信赖消息确保消息的传送。 TcpTransportBindingElement则表示Endpoint利用TCP作为通信的传输协议。每种BindingElement还有相应 的属性值,进一步详细的描述WCF通信的方式。

    BindingElement的顺序也非常重要。BindingElement集合通常会 创建一个用于通信的堆栈,其顺序与BindingElement集合中元素顺序一致。集合中最后一个binding element对应于通信堆栈的底部,而集合中的第一个binding element则对应于堆栈的顶端。入消息流的方向是从底部经过堆栈向上,而出消息流的方向则从顶端向下。因此,BindingElement集合中的 binding element顺序直接影响了通信堆栈处理消息的顺序。幸运的是,WCF已经提供了一系列预定义的Binding,能够满足大多数情况,而不需要我们自定 义Binding,殚精竭虑地考虑binding element的顺序。

    Contract是一组操作(Operations)的集合,该操作定义了Endpoint通信的内容,每个Operation都是一个简单的消息交换(message exchange),例如one-way或者request/reply消息交换。

    类ContractDescription 用于描述WCF的Contracts以及它们的操作operations。在ContractDescription类中,每个Contract的 operation都有相对应的OperationDescription,用于描述operation的类型,例如是one-way,还是 request/reply。在OperationDescription中还包含了MessageDecription集合用于描述message。

    在WCF 编程模型中,ContractDescription通常是在定义Contract的接口或类中创建。对于这个接口或类类型,标记以 ServiceContractAttribute,而其Operation方法则标记以OperationContractAttribute。当然我 们也可以不利用CLR的attribute,而采用手工创建。

    与Binding一样,每个Contract也包含有Name和 Namespace,用于在Service的元数据中作为唯一性识别。此外,Contract中还包含了ContractBehavior的集合, ContractBehavior类型可以用于修改或扩展contract的行为。类ContractDescription的组成如下图所示:

    uploads/200708/06_141732_wcf11.gif


    正 如在ContractDescription中包含的IContractBehavior一样,WCF专门提供了行为Behavior,它可以对客户端和 服务端的一些功能进行修改或者扩展。例如ServiceMetadataBehavior用于控制Service是否发布元数据。相似的, security behavior用于控制安全与授权,transaction behavior则控制事务。

    除了前面提到的 ContractBehavior,还包括ServiceBehavior和ChannelBehaivor。ServiceBehavior实现了 IServiceBehavior接口,ChannelBehaivor则实现了IChannleBehavior接口。

    由于WCF需要 管理的是服务端与客户端的通信。对于服务端,WCF提供了类ServiceDescription用于描述一个WCF Service,;而针对客户端,WCF管理的是发送消息时需要使用到的通道Channel,类ChannelDescription描述了这样的客户端 通道。

    ServiceDescription类的组成如下图所示:

    uploads/200708/06_141738_wcf12.gif


    我们可以利用代码的方式创建ServiceDescription对象,也可以利用WCF的Attribute,或者使用工具SvcUtil.exe。虽然可以显式的创建它,但通常情况下,它会作为运行中的Service一部分而被隐藏于后(我在后面会提到)。

    ChannelDescription 类的组成与ServiceDescription大致相同,但它仅仅包含了一个ServiceEndpoint,用于表示客户端通过通道通信的目标 Endpoint。当然,施加到ChannelDescription的Behavior也相应的为IChannelBehavior接口类型,如图所 示:

    uploads/200708/06_141743_wcf13.gif


    定义一个WCF Service非常简单,以SayHello为例,定义的Service可能如下:
    1. using System.ServiceModel  
    2.   
    3.     [ServiceContract]  
    4.     public class Service1  
    5.     {  
    6.         public string SayHello(string name)  
    7.         {  
    8.             return "Hello: " + name;  
    9.         }  
    10.     }  
    System.ServiceModel 是微软为WCF提供的一个新的类库,以用于面向服务的程序设计。在开发WCF应用程序时,需要先添加对System.ServiceModel的引用。 WCF中的大部分类和接口也都是在命名空间System.ServiceModel下。

    我们为Service1类标记了[ServiceContract],这就使得该类成为了一个WCF Service,而其中的方法SayHello()则因为标记了[OperationContract],而成为该Service的一个Operation。

    不过WCF推荐的做法是将接口定义为一个Service,这使得WCF Service具有更好的灵活性,毕竟对于一个接口而言,可以在同时有多个类实现该接口,这也就意味着可以有多个Service Contract的实现。那么上面的例子就可以修改为:
    1. [ServiceContract()]  
    2. public interface IService1  
    3. {  
    4.     [OperationContract]  
    5.     string SayHello(string name);  
    6. }  
    而类Service1则实现该IService1接口:
    1. public class Service1 : IService1  
    2. {  
    3.     public string SayHello(string name)  
    4.     {  
    5.         return "Hello: " + name;  
    6.     }  
    7. }  
    注意在实现了IService1接口的类Service1中,不再需要在类和方法中标注ServiceContractAttribute和OperationContractAttribute了。

    前面我已经提过,一个WCF Service必须有host作为它运行的环境。这个host可以是ASP.Net,可以是Windows Service,也可以是一个普通的应用程序,例如控制台程序。下面就是一个Host的实现:
    1. using System.ServiceModel  
    2.   
    3.     class HostApp  
    4.     {  
    5.         static void Main(string[] args)  
    6.         {  
    7.             MyServiceHost.StartService();  
    8.             Console.WriteLine("服务已经启动...");  
    9.             Console.Read();  
    10.             MyServiceHost.StopService();            
    11.         }  
    12.     }  
    13.   
    14.     internal class MyServiceHost  
    15.     {  
    16.         internal static ServiceHost myServiceHost = null;  
    17.   
    18.         internal static void StartService()  
    19.         {  
    20.             //Consider putting the baseAddress in the configuration system  
    21.             //and getting it here with AppSettings  
    22.             Uri baseAddress = new Uri("http:localhost:8080/service1");  
    23.   
    24.             //Instantiate new ServiceHost   
    25.             myServiceHost = new ServiceHost(typeof(WCFServiceLibrary2.Service1), baseAddress);  
    26.   
    27.             //Open myServiceHost  
    28.             myServiceHost.Open();  
    29.         }  
    30.   
    31.         internal static void StopService()  
    32.         {  
    33.             //Call StopService from your shutdown logic (i.e. dispose method)  
    34.             if (myServiceHost.State != CommunicationState.Closed)  
    35.                 myServiceHost.Close();  
    36.         }  
    37.     }  
    在 这个HostApp中,我们为Server1创建了一个ServiceHost对象。通过它就可以创建WCF运行时(Runtime),WCF Runtime是一组负责接收和发送消息的对象。ServiceHost可以创建SerivceDescription对象,利用 SerivceDescription,SercieHost为每一个ServiceEndpoint创建一个EndpointListener。 ServiceHost的组成如下图:

    uploads/200708/06_141748_wcf14.gif


    EndpointListener 侦听器包含了listening address,message filtering和dispatch,它们对应ServiceEndpoint中的EndpointAddress,Contract和 Binding。在EndpointListener中,还包含了一个Channel Stack,专门负责发送和接收消息。

    注意在创建 ServiceHost时,传递的type类型参数,不能是interface。因此,我在这里传入的是typeof(HelloWorld)。 ServiceHost类的AddServiceEndpoint()方法实现了为Host添加Endpoint的功能,其参数正好是Endpoint的 三部分:Address,Bingding和Contract。(此时的IHello即为ServiceContract,其方法Hello为 OperationContract)。

    ServiceHost的Open()方法用于创建和打开Service运行时,而在程序结束后 我又调用了Close()方法,来关闭这个运行时。实际上以本例而言,该方法可以不调用,因为在应用程序结束后,系统会自动关闭该host。但作为一种良 好的编程习惯,WCF仍然要求显式调用Close()方法,因为Service运行时其本质是利用Channel来完成消息的传递,当打开一个 Service运行时的时候,系统会占用一个Channel,调用完后,我们就需要释放对该通道的占用。当然我们也可以用using语句来管理 ServiceHost资源的释放。

    定义好了一个WCF Service,并将其运行在Host上后,如何实现它与客户端的通信呢?典型的情况下,服务端与客户端均采用了Web Service Description Language(WSDL),客户端可以通过工具SvcUtil.exe生成对应于该WCF Service的Proxy代码,以完成之间的消息传递,如图所示:

    uploads/200708/06_141754_wcf15.gif


    SvcUtil.exe是由WinFx Runtime Component SDK所提供的,如果安装SDK正确,可以在其中找到该应用工具。生成客户端Proxy代码的方法很简单,首先需要运行服务端Service。然后再命令行模式下运行下面的命令:
    svcutil.exe http://localhost:8080/service1?wsdl

    这样会在当前目录下产生两个文件service1.cs和output.config。前者最主要的就是包含了一个实现了Service1接口的Proxy对象,这个代理对象名为Service1Client,代码生成的结果如下:
    1. [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel""3.0.0.0")]  
    2. [System.ServiceModel.ServiceContractAttribute(ConfigurationName="IService1")]  
    3. public interface IService1  
    4. {  
    5.       
    6.     [System.ServiceModel.OperationContractAttribute(Action="http:tempuri.org/IService1/SayHello", ReplyAction="http:tempuri.org/IService1/SayHelloResponse")]  
    7.     string SayHello(string name);  
    8. }  
    9.   
    10. [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel""3.0.0.0")]  
    11. public interface IService1Channel : IService1, System.ServiceModel.IClientChannel  
    12. {  
    13. }  
    14.   
    15. [System.Diagnostics.DebuggerStepThroughAttribute()]  
    16. [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel""3.0.0.0")]  
    17. public partial class Service1Client : System.ServiceModel.ClientBase<IService1>, IService1  
    18. {  
    19.       
    20.     public Service1Client()  
    21.     {  
    22.     }  
    23.       
    24.     public Service1Client(string endpointConfigurationName) :   
    25.             base(endpointConfigurationName)  
    26.     {  
    27.     }  
    28.       
    29.     public Service1Client(string endpointConfigurationName, string remoteAddress) :   
    30.             base(endpointConfigurationName, remoteAddress)  
    31.     {  
    32.     }  
    33.       
    34.     public Service1Client(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :   
    35.             base(endpointConfigurationName, remoteAddress)  
    36.     {  
    37.     }  
    38.       
    39.     public Service1Client(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :   
    40.             base(binding, remoteAddress)  
    41.     {  
    42.     }  
    43.       
    44.     public string SayHello(string name)  
    45.     {  
    46.         return base.Channel.SayHello(name);  
    47.     }  
    48. }  
    至于后者,则是WCF Service的配置信息,主要包含的是Endpoint中Address,Binding以及Contract的配置(在后续文章我会详细介绍)。

    现在客户端就可以直接使用Service1Client对象,来完成与服务端的通信了:
    1. class ClientApp  
    2. {  
    3.     static void Main(string[] args)  
    4.     {  
    5.         Service1Client client = new Service1Client();  
    6.   
    7.         // 使用 "client" 变量在服务上调用操作。  
    8.         Console.WriteLine("请输入你的名字: ");  
    9.         string name = Console.ReadLine();  
    10.         Console.WriteLine(client.SayHello(name));  
    11.   
    12.         // 始终关闭客户端。  
    13.         client.Close();  
    14.   
    15.         Console.ReadLine();  
    16.   
    17.     }  
    18. }  
    除 了可以使用SvcUtil工具产生客户端代码,同样我们也可以利用代码的方式来完成客户端。客户端在发送消息给服务端时,其通信的基础是Service的 Endpoint,WCF提供了System.ServiceModel.Description.ServiceEndpoint类,通过创建它来实现 两端的通信。在前面,我还提到“对于客户端而言,WCF管理的是发送消息时需要使用到的通道Channel”,为此,WCF提供了 ChannelFactory(其命名空间为System.ServiceModel.Channel),专门用于创建客户端运行时(runtime)。 ChannelFactory与ServiceHost相对应,它可以创建ChannelDescription对象。与服务端ServiceHost不 同的是,客户端并不需要侦听器,因为客户端往往是建立连接的“发起方”,并不需要侦听进来的连接。因此客户端的Channel Stack会由ChannelDescription创建。

    ChannelFactory和ServiceHost都具有Channel Stack,而服务端与客户端的通信又是通过channel来完成,这就意味着,利用ChannelFactory,客户端可以发送消息到服务端。而客户 端本身并不存在Service对象,因此该Service的Proxy,是可以通过Channel来得到的。所以客户端的代码可以修改如下:
    1. using System.ServiceModel;  
    2. using System.ServiceModel.Description;  
    3. using System.ServiceModel.Channel  
    4.   
    5.     class ClientApp  
    6.     {  
    7.         static void Main(string[] args)  
    8.         {  
    9.             ServiceEndpoint httpEndpoint = new ServiceEndpoint(ContractDescription.GetContract(typeof(IService1)), new WSHttpBinding(), new EndpointAddress("http:localhost:8080/service1"));  
    10.   
    11.             using (ChannelFactory<IService1> factory = new ChannelFactory<IService1>(httpEndpoint))  
    12.             {  
    13.                 //创建IHello服务的代理对象;     
    14.                 IService1 service = factory.CreateChannel();  
    15.   
    16.                 Console.WriteLine("请输入你的名字: ");  
    17.                 string name = Console.ReadLine();  
    18.                 Console.WriteLine(service.SayHello(name));  
    19.             }  
    20.             Console.ReadKey();  
    21.         }    
    22.     }  
    对于上面的代码,我们有两点需要注意:
    1、采用这种方式,前提条件是客户端能够访问IHello接口。这也印证了之前我所叙述的最好使用interface来定义Service的好处。此外,为了保证部署的方便,有关Service的interface最好单独编译为一个程序集,便于更好的部署到客户端。
    2、客户端必须知道服务端binding的方式以及address。

    对于服务端而言,我们也可以直接在浏览器中打开该Service,在地址栏中输入http://localhost:8080/service1,如下图:

    uploads/200708/06_161535_wcf16.gif


    点击链接:http://localhost:8080/service1?wsdl,我们可以直接看到Service1的WSDL。注意到在这里我并没有使用IIS,实际上WCF内建了对httpsys的集成,允许任何应用程序自动成为HTTP listener。
  • 相关阅读:
    第十一节 CSS引入的三种方式
    第十节 表单
    第九节 页面布局(简历)
    第八节 HTML表格
    第七节 列表标签
    第六节 链接标签
    第五节 插入图的img标签
    WordPress 全方位优化指南(下)
    Cloud Insight!StatsD 系监控产品新宠!
    WordPress 全方位优化指南(上)
  • 原文地址:https://www.cnblogs.com/sunjt/p/1109553.html
Copyright © 2020-2023  润新知