《Windows Communication Foundation之旅》系列之二

《Windows Communication Foundation之旅》系列之二 

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

wcf06.gif

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

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

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,如下图:
 

wcf08.gif

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

wcf09.gif

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

Binding类包含Name,Namespace和BindingElement集合,如下图:
 

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的顺序。

下表是WCF中预定义的Binding:

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的组成如下图所示:
 

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类的组成如下图所示:
 

wcf12.gif

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

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

wcf13.gif

定义一个WCF Service非常简单,以Hello World为例,定义的Service可能如下:
using System.ServiceModel
[ServiceContract]
public class HelloWorld
{
    [OperationContract]
 public void Hello()
 {
  Console.WriteLine(“Hello World!”);
 }
}

System.ServiceModel是微软为WCF提供的一个新的类库,以用于面向服务的程序设计。在开发WCF应用程序时,需要先添加对System.ServiceModel的引用。WCF中的大部分类和接口也都是在命名空间System.ServiceModel下。

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

不过WCF推荐的做法是将接口定义为一个Service,这使得WCF Service具有更好的灵活性,毕竟对于一个接口而言,可以在同时有多个类实现该接口,这也就意味着可以有多个Service Contract的实现。那么上面的例子就可以修改为:
[ServiceContract]
public interface IHello
{
 [OperationContract]
 void Hello();
}

而类HelloWorld则实现该IHello接口:
public class HelloWorld:IHello
{
 public void Hello()
 {
  Console.WriteLine(“Hello World!”);
 }
}

注意在实现了IHello接口的类HelloWorld中,不再需要在类和方法中标注ServiceContractAttribute和OperationContractAttribute了。

前面我已经提过,一个WCF Service必须有host作为它运行的环境。这个host可以是ASP.Net,可以是Windows Service,也可以是一个普通的应用程序,例如控制台程序。下面就是一个Host的实现:
using System.ServiceModel
public class HostApp
{
 static void Main(string[] args)
 {
  ServiceHost host = new ServiceHost(typeof(HelloWorld), new Uri(“http://localhost:8080/HelloService”));
  host.AddServiceEndpoint(typeof(IHello), new BasicHttpBinding(),”Svc”);
  host.Open();
  Console.WriteLine(“Start Your Service.”);
  Console.ReadKey();
  host.Close();
 }
}

在这个HostApp中,我们为HelloWorld创建了一个ServiceHost对象。通过它就可以创建WCF运行时(Runtime),WCF Runtime是一组负责接收和发送消息的对象。ServiceHost可以创建SerivceDescription对象,利用SerivceDescription,SercieHost为每一个ServiceEndpoint创建一个EndpointListener。ServiceHost的组成如下图:
 

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代码,以完成之间的消息传递,如图所示:
 

wcf15.gif

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

这样会在当前目录下产生两个文件output.cs和output.config。前者最主要的就是包含了一个实现了IHello接口的Proxy对象,这个代理对象名为HelloProxy,代码生成的结果如下:
[System.CodeDom.Compiler.GeneratedCodeAttribute(”System.ServiceModel”, “3.0.0.0″)]
public partial class HelloProxy : System.ServiceModel.ClientBase, IHello
{   
    public HelloProxy()
    {
    }   
    public HelloProxy(string endpointConfigurationName) :
            base(endpointConfigurationName)
    {
    }   
    public HelloProxy(string endpointConfigurationName, string remoteAddress) :
            base(endpointConfigurationName, remoteAddress)
    {
    }   
    public HelloProxy(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
            base(endpointConfigurationName, remoteAddress)
    {
    }   
    public HelloProxy(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
            base(binding, remoteAddress)
    {
    }   
    public void Hello()
    {
        base.InnerProxy.Hello();
    }
}
(注:本程序在WinFx 2006 February CTP版本下运行通过)

至于后者,则是WCF Service的配置信息,主要包含的是Endpoint中Address,Binding以及Contract的配置(在后续文章我会详细介绍)。

现在客户端就可以直接使用HelloProxy对象,来完成与服务端的通信了:
public class ClientApp
{
 static void Main(string[] args)
    {
        using (HelloProxy proxy = new HelloProxy())
  {
         proxy.Hello();
        }
Console.ReadKey();
    }
}

除了可以使用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来得到的。所以客户端的代码可以修改如下:
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Channel

public class ClientApp
{
 static void Main(string[] args)
    {
        ServiceEndpoint httpEndpoint = new ServiceEndpoint(ContractDescription.GetContract(typeof(IHello)), new BasicHttpBinding(), new EndpointAddress(“http://localhost:8080/HelloService/Svc”));

  using (ChannelFactory factory = new ChannelFactory(httpEndPoint))
  {
   //创建IHello服务的代理对象;
   IHello service = factory.CreateChannel();
   service.Hello();
  }
  Console.ReadKey();
    }
}
(注:本程序在WinFx 2006 February CTP版本下运行通过)

对于上面的代码,我们有两点需要注意:
1、采用这种方式,前提条件是客户端能够访问IHello接口。这也印证了之前我所叙述的最好使用interface来定义Service的好处。此外,为了保证部署的方便,有关Service的interface最好单独编译为一个程序集,便于更好的部署到客户端。
2、客户端必须知道服务端binding的方式以及address。

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

wcf16.gif

点击链接:http://localhost:8080/HelloService?wsdl,我们可以直接看到HelloService的WSDL。注意到在这里我并没有使用IIS,实际上WCF内建了对httpsys的集成,允许任何应用程序自动成为HTTP listener。

转载于:https://www.cnblogs.com/cxd4321/archive/2007/01/12/618585.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/263189.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

centos 记录用户行为轨迹

遇到问题:公司增加了运维管理员,为确保服务器安全,和发生问题的时候好确认问题,需要记录每位服务器登陆者的行为轨迹。解决问题:linux script 命令正有如此强大的功能。满足我们需求。script记录终端会话。操作步骤&am…

Android内核剖析

--Android内核剖析 柯元旦 编著ISBN 978-7-121-14398-4 2011年9月出版定价:79.90元16开616页内容简介:本书内容分别从基础、内核、系统、编译以及硬件驱动几个方面对Android内核相关知识进行深入剖析,详细分析了Android内核的内部机制&#…

Bitmap的秘密

为什么80%的码农都做不了架构师?>>> 之前已经参加过几次QCon峰会,不过今年QCon 2014 上海峰会对我来说比较特别,不再只是一名听众,而是第一次登台演讲。感觉的确不太一样,一来是身份从听众变成了讲师&…

POJ 2018 Best Cow Fences (二分答案构造新权值 or 斜率优化)

$ POJ~2018~Best~Cow~ Fences $(二分答案构造新权值) $ solution: $ 题目大意: 给定正整数数列 $ A $ ,求一个平均数最大的长度不小于 $ L $ 的子段 这道题首先我们如果没有长度限制,直接扫一遍数组即可而有了长度限制…

hdu 2531 Catch him

Catch him Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 124 Accepted Submission(s): 49 Problem Description在美式足球中,四分卫负责指挥整只球队的进攻战术和跑位,以及给接球员传球…

POJ 3889 Fractal Streets(逼近模拟)

$ POJ~3889~Fractal~Streets $(模拟) $ solution: $ 这是一道淳朴的模拟题,最近发现这种题目总是可以用逼近法,就再来练练手吧。 首先对于每个编号我们可以用逼近法求出它在各个图上是处于左上,右上,左下&a…

我,只关心接口

我们去饭店吃饭,坐下。然后叫:服务员!好,服务员来了。你会说:倒茶。或说:点菜。是吧。你不会说:来,我们讨论一下什么是面向对象吧。这是为什么呢?很简单,对你…

POJ 2054 Color a Tree (贪心)

$ POJ~2054~Color~a~Tree $ $ solution: $ 我们先从题中抽取信息,因为每个点的费用和染色的次数有关,所以我们可以很自然的想到先给权值大的节点染色。但是题目还说每个节点染色前它的父亲节点也必须被染色,这就有了很多的后效性。 暂时没有办…

使用Null Object设计模式[转]

在ESFramework的设计实现中,很多地方都用到了Null Object设计模式。Null Object模式的含义在于,提供一个对象给指定的类型,用以代替这个对象为空的情况。 Null Object提供了“什么也不做”的行为,隐藏来自它的合作者的细节。对于如何理解和应…

angular input使用输入框filter格式化日期

最近使用angular日期选取器。只需要把所选的输出迄今input输入框,根据默认的假设,显示是在时间的形式的时间戳。不符合规定。需要格成一个特定的公式格公式。但input上ng-model不能直接对用于filter。因此内容需要一种方法来在这里显示格式化。 网上寻找…

CH0805 防线 (二分值域,前缀和,特殊性质)

$ CH~0805~ $ 防线 (二分值域,前缀和,特殊性质) $ solution: $ 注意博主所给题面的输出和原题有些不同 这道题当时想了很久很久,就是想不到怎么写。果然还是太 $ vegetable $ 了。首先我们可以肯定的是,我们不能暴力枚举&#xff…

基于Layui实现的树形菜单页面

基于Layui实现的树形菜单页面具体方法实现方法一:针对Layui模板的前后端统一更新1. 删除2. 添加3. 后端方法二:基于Dtree实现的纯前端树形增删改文中的组件地址具体方法实现 实现树形菜单,本文将给出两种实现方式。 针对Layui前端模板EasyW…

POJ 1723 Soldiers (中位数)

$ POJ~1723~Soldiers $ (中位数) $ solution: $ 这道题说难也不算太难,但是当时自己想的很矛盾。所以还是列一篇题解。 这道题首先比较容易看出来的就是:行和列是两个分开的问题,而且行的移动就是一个仓库选址的板子,直接求中位数…

(一)Windows环境下汇编编程读书笔记

看了一节关于80x86系列处理器简史,不知道云里和雾里,什么晶体管啊,什么什么之类的不知道云里和雾里,看了讲什么都不知道啊! 转载于:https://www.cnblogs.com/Nuxgod/articles/692990.html

Docker知识点总结及其命令的使用

DockerDocker简介Docker与Tomcat有什么区别?Docker与虚拟机有什么区别?Docker的基本组成Docker的联合文件系统Docker基本命令Docker中的几个重要组件一、容器数据卷二、Dockerfile三、Docker网络虚拟机共享网络的三种方式Docker共享网络的四种方式Docker…

主题:Spring注解入门(转载)

原文链接:http://www.iteye.com/topic/295348 1. 使用Spring注解来注入属性 1.1. 使用注解以前我们是怎样注入属性的 类的实现: Java代码 public class UserManagerImpl implements UserManager { private UserDao userDao; public void …

XDJM的情意比山高,比海深!!

又是兄弟姐妹们帮我提前过的生日,我们这帮人从SC出来后还没好好聚过,乘这个机会把大家约了出来。星期五整整一天都很快乐,特别是我,NANA,小乔期待着晚上的聚餐,期待着金贸的蛋糕,嘿嘿。。他们好…

最大子矩阵和

最大子矩阵和 $ n^3 $ 算法 $ solution: $ 首先我们不难想到枚举上下左右边界,然后两层循环统计权值和,复杂度 $ O(n^6) $ 。这个我们用前缀和可以省去后面的循环,将复杂度降成 $ O(n^4) $ 。然后我们考虑不枚举上下左右四个边界,…

Springfox-swagger使用详解

Springfox-swagger使用详解什么是Swagger?Swagger的具体使用一、导入依赖二、建立Swagger配置类三、通过Swagger测试接口引用什么是Swagger? 是一个开源的API Doc的框架可以将我们的Controller中的API方法以文档的形式展现,并支持为其添加注…

Android中调用系统已安装的播放器来播放网络流媒体视频

2019独角兽企业重金招聘Python工程师标准>>> 实现思路比较简单几行代码就可以搞定,在界面放一个Button或者带有播放图标的imageview,点击事件中调用本地播放器来播放。 Uri uri Uri.parse("http://218.200.69.66:8302/upload/Media/20…