原文地址 http://www.cnblogs.com/idior/articles/971252.html
介绍
WCF具有非常易用的编程模型,服务开发者在掌握ABC的概念后可以很容易的使用WCF去实现他们的服务。同时也具有极高的扩展性,比如说,如果你想给你的服务添加一些安全相关的特性,只需要给你的服务或者是操作加上一些相应的attribute即可。
但是,你有没有想过,当你在给一个方法头上加了OperationContract特性,或者你给ServiceContract特性加了一些参数后,WCF服务运行时究竟会做什么。WCF将一切变得简单,但是这往往让我们抓狂,我们很想知道WCF内部的复杂的实现究竟是怎么搞的,WCF服务究竟是怎么运作的。这篇文章就是为了阐述这些的。
你知道一个服务在开启的时候会做什么吗?怎样从传输层去获取消息并将其分发到相应的服务实例?这些对于服务开发者来说都是透明的我们只需要做的就是实现服务契约,打开ServiceHost,剩下的事情就交给WCF框架进行处理了。现在我们就来挖掘一下WCF框架是怎么完成这一工作的。^_^
一个服务去处理一个来自调用者的请求会经过4个步骤,我会在下面的章节中详细介绍这四个步骤:
- 初始化
- 开启服务
- 接收连接
- 处理请求
为了更好的理解我们将要讨论的,你需要有一些WCF的基础知识,并且使用过一段时间。现在我们开始吧。
初始化
在这一步,我们对于服务所有的描述,不论是通过配置文件,还是通过代码的方式,抑或是通过特性,都将会被转换成服务的ServiceDescription类型的实例,它收集了描述这个服务的所有的信息。这些信息用于服务的WSDL的生成,更重要的是,WCF会使用这些信息去创建一个运行时管道,通过这个运行时管道,WCF可以为客户端请求找到正确的服务实例和服务操作。
ServiceHost用来创建ServiceDescription,SeviceDescription的信息主要来源于编码和配置文件,所以为此,ServiceHost为每个来源提供了一个方法,CreateDescription和ApplyConfiguration。
在CreateDescription方法中,运用反射技术将在服务契约上定义的特性ServiceContract和OperationContract相关的信息给找出来,这些信息用于创建Behavior,ContractDescription,OperationDescription,如下图所示:
在ApplyConfiguration中相应的就从app.config配置文件中读取配置的信息,比如地址,绑定,契约,终结点,以及行为相关的配置(如果我们添加了的话)。
在创建好ServiceHost对象之后,我们还可以在代码中通过一些方法在这个ServiceHost开启之前对它进行一些修改,如下图所示:
最终,所有相关的信息都准备好放在了ServiceDescription中,现在我们可以使用这些信息去构建运行时管道来处理客户端请求了。恩,是时候开启这个服务了。
开启服务
我们对上面这张WCF运行时架构的图片应该不会陌生,客户端通过代理通过一系列的管道去访问服务端最终会被服务端的服务实例处理。在这个图片中,消息会被管道中所有的组件进行处理,但是我们并不知道这些组件是怎么创建的以及消息是怎么分发的。现在我就来解释一下当服务开启时这些组件是怎么创建的。
步骤1:ServiceHost会根据终结点配置信息,对每个监听地址创建一个ChannelListener,这个ChannelListener可以用于在之后创建服务端的信道和信道栈。
步骤2:构建ChannelDispatcher和EndpointDispatcher(根据监听地址和终结点地址),这两个组件在消息的分发中扮演着很重要的角色。
步骤3:构建ServiceDescription中定义的行为,这些行为可以影响到WCF运行时组件的行为。比如说,你在一个自定义行为中提供了一个自定义的InstanceProvider,当WCF运行时通过IInstanceProvider请求一个服务实例时,你可以用你自定义的InstanceProvider来提供。
步骤4:ChannelDispatcher打开ServiceHost创建的所有的ChannelListener,然后得到一个ImmutableDispatchRuntime 类型的实例,这个实例是用于分发消息的核心组件。当从信道中取出消息之后,ImmutableDispatchRuntime会拿到一个服务实例,然后选择操作去调用执行。
最后,信道分发器会创建一个Listenerhandle并使用ChannelPump方法来轮询接受客户端连接。
正如我们所见到的,在开启服务阶段我们还是在做一些准备的工作。我们设置了信道工厂,但是我们并没有创建信道栈。我们构建了所有的分发器,但是在消息到达之前他们是不会工作的。现在我们只需要一个消息来敲这一扇门。
接收连接
一旦ListenerHandler接收到了一个新连接,它就会调用ChannelListener的Accept方法创建信道。然后ListenerHandler创建一个ChannelHandler对象并与创建好的信道进行关联,然后ChannelHandler一直通过MesagePump对这个channel进行消息的轮询,换句话说,它创建了一个等待接受的通道。
处理请求
当客户端发出一个请求后,ChannelHandler会被告知有message进来通过一个回调。首先,他们通知所有的额channel去接收这个消息,这个时候所有的信道(尤其是协议信道)会有一个机会去处理这个消息来实现他们的协议,比如说Security,Transaction等。
然后这个ChannelHandler会根据优先级去调用所有的EndpointDispatcher的过滤方法来决定将这个消息分发到哪个Endpoint。然后使用EndpointDispatcher的DispatcherRuntime对象来决定去调用哪个服务操作。默认的,WCF跟根据消息请求头的Action属性来决定去选择哪个操作。在开启服务阶段,WCFhi创建一个DispatcherOperationRuntime字典,key为操作的Action属性(默认为操作名,可通过Name指定),所以如果我们传递一个消息的Action头给这个字典,我们就可以得到应该被调用的合适的操作。如果我们不想通过这样的额Action头去匹配,也可以使用自定义的OperationSelector。
在我们拿到DispatchOperationRuntime之后,ChannelJHandler还不能去调用它,因为我们还没有拿到响应的服务实例。所以ChannelHandler会分发消息到ImmutableDispatchRuntime去拿到一个服务实例上下文。首先它会check一下是否已经存在了一个上下文,如果有的话它就会尝试使用IInstanceContextProvider去拿到那个上下文。如果我们在服务行为上修饰InstanceContextMode为PerSession或Singleton,我们可能会拿到一个已经存在的实例上下文,而且我们之后拿到的服务实例也是同一个。否则,如果InstanceContextMode设置为PerCall的话,我们就必须总是要创建一个新的服务实例上下文了。在顺备好实例上下文之后ImmutableDispatchRuntime会调用IDispatchMessageInspector来拦截这个消息,在这个时候我们就可以自定义一个拦截器来围绕消息做一些事情,比如说打印消息内容、修改消息或者是进行消息的统计等等。消息在经过拦截器之后,ImmutableDispatchRuntime 会调用实例上下文来获得一个服务实例,随后实例上下文就调用InstanceProvider来拿到这个实例。现在你可以想象我们要在这里做什么了吧,既然我们拿到了服务实例,我们就可以去调用响应的方法。所以我们调用InvokeBegin方法在步骤1中查询到的DispatchOperationRuntime对象上。
现在我们开始去调用这个方法。首先我们需要反序列化这个消息来获得方法的CLR类型的参数,所以DispatchOperationRuntime 会调用IDispatcherMessageFormatter去做这件事情。在反序列化之后DispatchOperationRuntime会调用IParameterInspatcher去拦截这些参数。在这里你就有机会去对这些参数做一些手脚了,比如验证什么的。最后,我们使用IOperationInvoker来调用这个方法。
现在我们已经从迷宫中走出来了,我们已经知道了WCF运行时究竟做了什么。那么我们可以从中学到什么知识呢?个人认为,我从中学到了两件事:
- 这个易用的编程模型是怎么工作的:
我学到了再我我的服务和操作加上一些WCF的特性后会发生什么,以及如果我改变其属性值会发生什么事情。
- 怎样去扩展WCF运行时框架:
我学到了在WCF运行时框架中的一些可以扩展的地方,现在我知道了这些扩展会在什么时候被调用以及怎么被调用,所以我对于围绕他们做一些自定义的扩展很有信心。
最后,我想向大家展示一下WCF运行时的调用树。我想它应该是一个很复杂的协作图,但是我没有用协作图来表示是因为它太复杂了,没法再简单的word文档中呈现。所以我认为调用树应该会更合适,我希望它对于你一样也会有一些帮助。树中的颜色表明这个方法属于的class,@符号表示的是一些我自己命名的方法逻辑。树中可能会有一些错误,如果你发现错误的话请通知我一下,谢谢。