经过WCF基础的ABC学习,已经可以构建简单的WCF的服务,使用不同的服务地址和绑定类型,根据业务提供所需的服务契约。但不禁想问,服务所使用的消息报文是什么样的形式么?蕴含什么样内容呢?WCF服务是否支持会话?WCF所提供的服务是单例的呢还是单调的呢?服务寄宿和客户端代理是如何实现的呢?接下来,将以这些问题为线索进行深入学习,包容包括XML序列化与SOAP消息、会话与实例、服务寄宿与客户端代理。
可以这么说,整个WCF服务都是构建在SOAP消息的基础上的,而SOAP消息是建立在XML这种跨平台的数据格式的基础上的,有人可能会说WCF也支持Restful风格的服务并支持json格式的数据,的确是这样,但这只是当时情况下的折中。现在可以发现,基本上以json格式为基础的restful服务其实都转化为了WebAPI项目,而不是WCF。所以仍然可以认为,WCF的初衷是为企业提供可靠的分布式SOA服务的,而WebAPI的出现,为WCF的不足之处进行了有利补充,用于提供公共的对外服务,更加的轻量级并与当下的互联网环境结合的更妥当。说了这么多,目的只有一个就是学习WCF一定要意识到学习SOAP相关知识的重要性,因为整个WS-*网络服务标准协议簇都建立在其之上,而与SOAP的基础XML相关的知识也就同样凸显出来了,其中最重要的就是XML的序列化,接下来通过图表进行简要的介绍。
知识点 | 诠释 |
消息格式化器MessageFormatter | WCF中消息的序列化和反序列化操作依赖该组件,服务器端(DispatchMessageFormatter)和客户端(ClientMessageFormatter),此外还可以通过实现IDispatch/ClientMessageFormatter接口自定义消息格式化器,例如压缩消息。 |
序列化器的选择 | 包括DataContractSerializer和XmlSerializer,后者存在多年,简单有效;前者是WCF专门构建的,可以用于复杂场景,是默认选择。 |
数据契约的等价性 | 在生产系统中,会出现修改接口的场景(例如添加删除接口方法数据成员,尽量避免),可能会对原有系统造成破坏,可以通过使用系统提供的扩展字段来避免这样的情况发生,附上一个可空的例子。 <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.sory.com"> <xs:complexType name="Customer"> <xs:sequence> <xs:element name="Address" nillable="true" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:schema> |
数据契约和数据成员 | DataContractAttribute和DataMemberAttribute的本质是通过反射进行序列化为数据流,WCF支持集合数据和字典数据的数据契约 |
.此外,XML的文档结构描述方式,xsd文件和原来的DTD文件的相关知识也注意学习,可以参看http://utopialxw.iteye.com/blog/1218622。
接下来的消息部分,内容很多,但实际中比较重要的仅仅是WS-Addressing的相关属性和在消息头中添加安全信息。WS-Addressing的相关属性如下表所示。
属性名 | 诠释 |
<To> | 以URI的形式表示消息发送的目标地址,如果没有显示指定,则采用默认地址http://www.w3.org/2005/08/addressing/anonymous |
<From> | 以终结点引用的形式表示源终结点,不太常用 |
<ReplyTo> | 以终结点引用的形式表示接受/回复消息的终结点,若未指定,则采用默认地址http://www.w3.org/2005/08/addressing/anonymous |
<Action> * | 以URI的形式表示消息的意图,比如调用服务操作 |
<MessageID> | 以URI的形式表示消息的唯一标识 |
<RelatesTo> | 表示关联消息的<MessageID>,比如将恢复消息的<RelatesTo>属性设置为请求消息的<MessageID>,从而关联两者 |
<ReferenceParameters> | 可以以任何XML元素提供额外的辅助信息 |
另一个为添加安全信息:messageProperty.Headers.Add(HttpRequestHeader.Cookie, "SecurityToken="+securityToken);
常见的,XML编码通常设计XmlDictionary,XmlDictionaryWriter和XmlDictionaryReader等几个类。
这部分的内容主要集中在对实例上下文的概念和单例、单调、会话三种模式的理解。在托管应用程序中,当创建一个托管对象时,CLR会在托管堆为该对象分配内存空间,对象的生命的终结对应内存的回收。负责该部分工作的模块为GC,其对没有被"根"引用的对象进行回收,对于WCF来说,其实例上下文即为其根,其层级关系为ServiceHost->InstanceContext->ServiceInstance。
实力上下文的模式包括PerCall单调、会话PerSession和Single三种。在单调模式时,系统的并发性最好,但对资源的消耗很大,比如数据库支持100个并发,这是有100个服务实例同时打开,那么第101请求就无法得到服务。而单例模式对资源的消耗较少,每次完成调用就释放资源,但是当请求多时,会有很多请求需要排队,响应不够及时。而会话模式,根据用户来创建服务示例,算是一种折衷。
Tip:高性能和可伸缩性是软件设计与架构中永远不可同时兼顾的,高性能需要充足的资源,高扩展性需要尽可能的节约资源,所以才有软件架构是意向"权衡"的艺术,目的不是使得各方面达到最优,而是找到一个平衡点使得整体最优,在实践中,没有完美的方案,只有适合方案,必须具体问题具体分析。
服务寄宿的内容比较简单,主要包括ServiceHost,iis寄宿和window寄宿的介绍。
ServiceHost:是服务寄宿的核心,包括创建、终结点添加、开启和关闭等基本步骤,服务描述的创建和运行时框架体系的构建也与其息息相关。
对于iis寄宿和windows寄宿来说,在当下的互联网环境下,iis寄宿显得更加合理,其优点包括:自动化的进程激活和关闭;自动化的进程回收;自动化的进程健康监测;ASP.NET共享寄宿模式,将多个应用寄宿在通过一个工作进程,改善服务器密度和可伸缩性;ASP.NET动态编译。通过.svc文件来标识服务,提供ASP.NET并行和兼容两种模式,默认使用第一种,第二种仅在需要共享SessionState时,不常用。
客户端代理的内容相对稍微多一点,其涉及透明代理和真实代理的概念,一个精简的WCF框架和服务限流操作的例子。
服务代理:作为服务在客户端的本地代理,本身不承担功能实现,仅仅是在服务与客户端之间起到一个中介的作用,是一种透明代理,可以通过RemotingServices.IsTransparentProxy方法来判断,其最终都通过ChannelFactory<TChannel>创建。在托管环境中,AppDomain提供了一种轻量级的隔离机制,不同AppDomain之间通过封送(Marshaling)来传递对象。封送分为按值封送和按引用封送两种形式,前者通过序列化/反序列化重建一个相同的本地对象,实现不同AppDomain的数据共享;后者将远程对象的引用传递给本地,实现跨应用程序与的远程调用(RPC)。那么如何实现RPC呢,简单来说,如果需要在A域(应用程序域)调用B域创建对象,那么B域需要为该对象创建其引用System.Runtime.Remoting.ObjRef对象,并将其按值封送带A域。A域基于它创建代理对象,之后再调用时,代理对象通过ObjRef将调用请求跨域发送给远程对象,最后将远程调用得到的结果返回给客户端程序。实际上,代理对象包含两种类型,一种是上面提到的透明代理,另一种为System.Runtime.Remoting.Proxies.RealProxy表示的真是代理。透明代理依托于真实代理,其将调用请求转发给真实代理,真实代理完成真正的远程调用,如下图所示。
精简的WCF框架
服务端的流程包括:请求消息的接受和回复信息的发送;请求消息的解码和回复消息的编码;请求消息的反序列化和回复消息的序列化;服务对象的创建;服务操作的执行。
客户端的流程包括:请求消息的序列化和回复消息的反序列化;请求消息的编码和回复消息的解码;请求消息的发送和回复消息的接收。整体过程如下图所示。
最后介绍一下服务限流(Throttling)机制,WCF具有一个MaxConcurrentSessions的阈值,表示服务端允许的最大并发会话数量,默认为100(针对一个CPU),一旦超过服务端就会拒绝之后的服务。而客户端的调用在限定时间内如果无法被及时处理,就会抛出一个TimeoutException异常。
参考资料:
[1]蒋金楠. WCF全面解析[M]. 上海:电子工业出版社, 2012.