上一节,我们说到WCF配置,谈到我们还欠一个对App.config的另外一个解释。。
这节以WCF服务端的配置说明App.config中出现参数的功能。我们要说明的这些参数主要含:
限流只是行为中的一项参数、大数据传输会同时在绑定与行为中体现。我们将分别说明
这节开始,说WCF依赖的参数,准确的说,是WCF服务运行所依赖的绑定与行为参数。
我们知道,WCF是面向服务通信框架,它弱化了Server-Client之间的关系,强化了Server-Client通信所依赖的基础:契约。
WCF定义了四种类型的契约:
服务(ServiceConract)、数据(DataContract)、错误(FaultContract)、消息(MessageContract)
这节,我们说ServiceConract服务契约,仅仅说参数,即App.config中的参数,因为是这些参数影响了WCF服务接口中方法的行为
对Lig本身,指的是下面接口中的方法的集合
public interface ILigAgent
7.1服务与服务参数
7.1.1 App.config与ServiceModelSectionGroup:
——————————————————————————————————————————————
回头看下LServiceHost中的ApplyConfiguration中的下面这行代码:
/// <summary>
/// Override ApplyConfiguration to load custom config file
/// Attention : ConfigPath must be static variable according to the follow rules:
/// 1. First run ApplyConfiguration of base class of ServiceHost
/// 2. Second run constructor of LServiceHost itself
/// </summary>
protected override void ApplyConfiguration()
{
//.....................
// Gets all service model config sections
ServiceModelSectionGroup servicemodelSections = ServiceModelSectionGroup.GetSectionGroup(cfg);
//..................
}
1) App.config由 ServiceModelSectionGroup类维护。
2) 参数最终由ServiceHost与ChannelFactory<T>使用。这是LServiceHost/LDuplex代码编写所依赖的原理。
ServiceModelSectionGroup含
——————————————————————————————————————————————
服务端终结点属性(Services)、客户端终结点属性(Client)、行为属性(Behaviors)、绑定属性(Bindings)。
由ServiceHostBase完成解析App.config并设置这几个属性。 客户端由ChannelFactory<T>解析App.config
——————————————————————————————————————————————
其实,了解App.config只需要了解ServiceModelSectionGroup信息是如何影响ServiceHost与ChannelFactory的行为就OK了。
这儿,你大概清楚了我们的那个LServiceHost/LDuplex是如何写出来的了。。
图 7.1.1 维护App.config的类
7.1.2 WCF的服务
——————————————————————————————————————————————
WCF的服务是一组公开功能的集合,借宿在特定的进程中。WCF服务分服务接口与服务实现。
在Codes层次,它是一组公开方法的集合。在这儿,如你所见到的在ILigAgent中出现的方法的集合。它通过如下方式被定义为WCF服务接口:
[ServiceContract(CallbackContract = typeof(ILigAgentCallback))]
public interface ILigAgent
如图7.1.2所示
1) App.config中的services节点(服务参数)公开了WCF服务所依赖的配置信息:endpoint
3) Endpoint中配置了binding属性信息。
图 7.1.2 WCF服务端配置
7.1.3 WCF的服务参数解析
——————————————————————————————————————————————
Services中的service节点信息。对Services中的Service节点信息,ServiceHost主要解析了三部分数据:
1) Services属性
ServiceModelSectionGroup中的成员
2) behaviorConfuration
(BehaviorConguration属性对应的私有成员。此处为什么是小写,实际上ServiceHost使用了BehaviorConguration对应的私有变量behaviorConfuration)
3) binding
Service节点解析:
service节点信息由
public sealed class ServiceEndpointElement
类维护。通过 ServiceHostBase 的如下方法
protected void LoadConfigurationSection(ServiceElement serviceSection)
加载到应用程序。实际上,ServiceEndpointElement信息变成了ServiceElement中Endpoints集合属性中的成员。
这样,我们知道,多个sevice节点信息被加载到了Endpoints集合属性中。
behaviorCOnfuration解析:
Binding解析
而Binding则被解析为了继承于Binding的类,对Lig系统,此处被解析为了 NetTcpBinding类。
public ServiceEndpointElementCollection Endpoints { get; }
图7.1.3 ServiceEndpointElement
到这儿。你基本清楚了WCF配置中的所有参数是如何在ServiceHost中起作用的了。下面我们看具体参数
7.1.4 bindings中的超时配置参数
——————————————————————————————————————————————
整个WCF的绑定模型是一个复杂组件组合体系。本文将抛开信道与信道栈的概念仅从代码层次对bindings参数给予说明。
public abstract class Binding : IDefaultCommunicationTimeouts
Binding 实现了通信超时IDefaultCommunicationTimeouts接口,拥有如下四个参数的行为:
图7.1.4 通信超时接口
private TimeSpan closeTimeout;
private TimeSpan openTimeout;
private TimeSpan receiveTimeout
private TimeSpan sendTimeout;
public TimeSpan CloseTimeout { get; set; }
public TimeSpan OpenTimeout { get; set; }
public TimeSpan ReceiveTimeout { get; set; }
public TimeSpan SendTimeout { get; set; }
7.1.5 我们关注的是这四个参数是如何影响ServiceHost的
——————————————————————————————————————————————
我们知道,我们的LigServer中有这样的代码:
serviceHost.Open();
//其实它还有一个重载版本,含了超时参数。
public void Open(TimeSpan timeout)
实际上,ServiceHost用的是继承于CommunicationObject中的接口。同时ChannelFactory<T>也继承了该类。所以在服务端与客户端均为有效参数。
public abstract class ServiceHostBase : CommunicationObject
图7.1.5 通信对像接口
ServiceHost & ChannelFactory<T>类间的继承关系
public class LServiceHost : ServiceHost
public class ServiceHost : ServiceHostBase
public abstract class ServiceHostBase : CommunicationObject
public class ChannelFactory<TChannel> :ICommunicationObject
7.1.6 超时参数
——————————————————————————————————————————————
1.openTimeout – 服务端、客户端有效配置参数
服务开启超时,默认1分钟。
public void Open()
客户端连接超时,默认1分钟。
public TChannel CreateChannel()
2.closeTimeout -服务端、客户端有效配置参数
服务端主动关闭一个连接超时。
客户端关闭一个连接超时
3.sendTimeout 服务端、客户端有效配置参数
客户端发送数据超时。默认1分钟。
服务端回调发送数据超时。默认1分钟 (双工通信时配置有效)
4.receiveTimeout服务端、服务端配置有效参数
服务端接收数据超时,默认10分钟。
客户端回调接口接收数据超时,默认10分钟(双工通信时配置有效)
7.2.1 可靠性会话参数
——————————————————————————————————————————————
先看下NetTcpBinding类
图7.2.1 NetTcpBinding类
ReliableSession属性决定了客户端与服务端会话可靠性。(该参数在事务传输中非常有用)
public OptionalReliableSessionReliableSession {get; }
public class OptionalReliableSession : ReliableSession
public bool Enabled {get;set; }
Enabled。是否开启可靠会话。对NetTcpBinding绑定,默认不开启。
ReliableSession有一个属性
public TimeSpanInactivityTimeout {get;set; }
inactivityTimeout: 表示在客户端和服务器端非活动期间的空闲超时。Enabled属性配置为true时该参数有效。
e.g:
如果
<reliableSession enabled = "true" inactivityTimeout = "00:01:00"/>
此时如果WCF客户端1分种不调用WCF服务,则WCF服务端会自动认为客户端已经掉线。此时WCF连接会自动断开。还记不记得我们前面的配置。此处的inactivityTimeout无效。。该种写法是一种画蛇添足。这是我们前面配置中引入的第一个无效参数。
这儿,为什么没写正确的,是因为,错误的东西更容易让人印像深刻。你懂的。
<reliableSession enabled = "false" inactivityTimeout = "00:00:10"/>
可靠性会话关闭,WCF连接由服务端的receiveTimeout配置参数引发。这意味着,如果WCF客户端超过receiveTimeout没有调用服务。WCF连接会自动断开。
inactivityTimeout + receiveTimeout
这2个参数,决定了WCF服务端保持连接状态的最长有效时长。
7.2.2 Tcp通道消息传输限流 –流操作
——————————————————————————————————————————————
WCF通道中的消息传输默认采用缓冲方式,当客户端与服务端交换消息时,这些消息会被放入接收端缓存中,一旦接收到完整信息,就立即被传递。
然而,对于大数据,这样的缓冲无疑是低效的,因为WCF传递消息要等待缓存消息完整后才被传递。而WCF中提供的流操作正好用于改善该情况。
NetTcpBinding中有三个属性。
maxBufferPoolSize="52428800"maxBufferSize="6553600"maxReceivedMessageSize="6553600"
1) 这三个属性于大数据的消息传输,使用这三个参数需要显式的设置transferMode = “Streamed”启用流操作
2) 同时关闭可靠性传输。所有的消息传递会被转换为二进流。
默认情况下transferMode = “Buffered”
maxReceivedMessageSize:接收端最大消息长度 单位:字节 。默认64K
maxBufferSize:接收端最大缓冲长度 单位:字节
maxBufferPoolSize:接收消息缓冲区管理器分配的最大内存量 单位:字节
启用该属性基本还要设置一个消息复杂度的属性ReaderQuotas,该属性为终结点处理SOAP消息的复杂性约束配置。。。。。
maxConnections="100"
maxConnections一个Service节点允许最大连接数量。作用与ServiceThrottlingBehavior 中的限流MaxConcurrentSessions效果等同。
7.3 服务限流参数
限流参数主要有两项。我说ServiceThrottlingBehavior与DataContractSerializer
7.3.1 ServiceThrottlingBehavior 服务调用限流
——————————————————————————————————————————————
WCF对限流的控制是通过一个服务行为(Service Behavior)实现的,该服务行为类型名称为ServiceThrottlingBehavior。含三个参数
MaxConcurrentCalls:服务端允许的所有客户端最大并发调用总数,默认16
MaxConcurrentInstances:服务端并发最大实例数。默认26;
MaxConcurrentSessions:服务端允许客户端最大连接数,默认10。
图7.3.1 服务行为限流
7.3.2 DataContractSerializer服务传参限流
——————————————————————————————————————————————
WCF数据契约定义了专门的数据契约序列化器,该序列化器参通过DataContractSerializer表示,用来序列化数据契约中我们的标记为[DataMember]的成员
DataContractSerializer 有一个MaxItemsInObjectGraph属性用于设置序列化与反序列化时允许的最大对像数。
如果你的服务接口中有如下类型:
[OperationContract]
List<ActiveProfile> GetActiveLogsInfo();
List<ActiveProfile>是一个链表,如果其长度超过65536,在客户调用这样的方法会出现一个通道状态为Fault的错误。。
<behavior name="serializerBehavior">
<dataContractSerializer maxItemsInObjectGraph="200000"/>
</behavior>
此,配置我们讲完了。虽然我们没讲客户端配置。但有一点要注意。客户端与服务端完全可以共享一个配置。所以netTcpBinding中出现的参数在客户端要完全一致。
若不一致。。
你程序在这儿,就埋下了Bug。等出现的时候,掘地三尺的查吧。自求多福。
——————————————————————————————————————————————
再回答最后一个问题:
——————————————————————————————————————————————
为什么我们不把服务端配置与客户端配置搞一起,非得整两个配置,多麻烦?
1) 你知道,SOA本身就解释为面向服务的架构,就是要减小Server-Client间的耦合。
2) 我们的Server可能在部署在千里之外,客户端与Server共享一个配置,对Server来说,给客户端的配置暴露了Server不该暴露的资源,Server的信息是非安全的。
3) 同时这么做人为的增加了Server-Client间的耦合。这也不是面向对象中封装所要求的做法。。。
OK,Lig系统到这儿,算是这章讲得最细节了,是因为参数。详细的原因很简单:
1) 我想传递一个信息,作为Coder,我们要非常、非清清楚的知道,我们程序运行所依赖的每一个参数。
2) 我提ORM、提SOA、提设计模式。是因为。你软件的架构在很大程度上决定了你的代码有没有BUG。
其实,对每一个优秀的程序员来说,写出没有Bug的程序,应该是最基本的要求。
因为,你的工作,你代码的稳定程度,决定了你团队代码的质量。
以前我也一直不相信,即便几十万行代码,为什么有人会声称他们的代码是0BUG,直到我自己写出OBUG的代码。。
其实,还有个原因,就是因为,《0 bug:C/C++商用工程之道》只回答了一部分答案,还有一部分,需要你自己去寻找。
自信,其实、你我、都不希望、真的就是源于无知。你懂的。。。。。好了。
附:客户端与服务端完全可以共享一个配置
(不要使用这个配置.使用时Server/Client配置分开)
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="Lig" value="viviute"/>
<add key="Ligger" value="define your own config"/>
</appSettings>
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="ligTcpBinding" closeTimeout="00:02:00" openTimeout="00:02:00" sendTimeout="00:10:00" receiveTimeout="24.00:00:00"
maxConnections="100" maxBufferPoolSize="409600000" maxBufferSize="102400000" maxReceivedMessageSize="102400000">
<reliableSession enabled = "false" inactivityTimeout="10:00:00" />
<security mode="None" />
</binding>
</netTcpBinding>
</bindings>
<services>
<service behaviorConfiguration="metaDataBehavior" name="Lig.vivitue.Contract.Services.LigAgent">
<endpoint address="net.tcp://127.0.0.1:6023/Lig.vivitue.Contract.Services.LigAgent"
binding="netTcpBinding" bindingConfiguration="ligTcpBinding"
contract="Lig.vivitue.Contract.Services.ILigAgent"/>
</service>
</services>
<client>
<endpoint name="Lig.vivitue.Contract.Services.LigAgent" behaviorConfiguration="IgoreSvcCertValidation"
address="net.tcp://127.0.0.1:6023/Lig.vivitue.Contract.Services.LigAgent"
contract="Lig.vivitue.Contract.Services.ILigAgent"
binding="netTcpBinding" bindingConfiguration="ligTcpBinding">
<identity>
<dns value="vivitue" />
</identity>
</endpoint>
</client>
<behaviors>
<serviceBehaviors>
<behavior name="metaDataBehavior">
<serviceMetadata httpGetEnabled="true" httpGetUrl="http://127.0.0.1:7023/Lig.vivitue.LigMetadata"/>
</behavior>
<behavior name="serializerBehavior">
<dataContractSerializer maxItemsInObjectGraph="200000"/>
</behavior>
<behavior name="throttlingBehavior">
<serviceThrottling maxConcurrentCalls="50" maxConcurrentInstances="200" maxConcurrentSessions="100" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="IgoreSvcCertValidation">
<clientCredentials>
<serviceCertificate>
<authentication certificateValidationMode="None"/>
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
</configuration>