第十讲:绑定(信道)
Posted HulkXu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第十讲:绑定(信道)相关的知识,希望对你有一定的参考价值。
代码
https://yunpan.cn/cPns5DkGnRGNs 密码:3913
绑定简介
从整个基础架构的层次结构上讲,WCF可以分成两个部分:服务模型层和信道层。
服务模型层建立在信道层之上,提供了一个统一的、可扩展的编程模型。
信道层则通过绑定创建的信道栈为消息通道提供了一个传输、处理的通道。
信道(Channel)与信道栈(Channel Stack)
在信道层,若干信道逐个相连,成为一个信道栈。WCF采用基于消息的通信手段,信道栈提供一个消息传输和处理的通道。
我们举个例子:
比如有一个为居民提供饮用水的自来水厂,它的任务是抽取自然水源,进行必要的净化处理,最终输送到居民区。净化的流程可能是这样的:天然水源被汲取到一个蓄水池中进行杂质的过滤(过滤池);被过滤后的水流到第二个池子进行消毒处理(消毒池);被消毒的水流到流到第三个池子中进行水质软化处理(软化池);最终水通过自来水管流到居民的家中。
WCF的信道栈就相当于一个自来水厂,而构成信道栈的一个个信道就相当于上面提到的过滤池,消毒池,软化池等。唯一不同的是,自来水厂处理的是水,而信道栈处理的是消息。这样设计的最大好处就是具有很强的可扩展性,水的净化需要进行多次处理,那么对于消息处理来说,不可能,也没有必要设计出一种万能信道完成所有的消息处理任务。我们更希望的方式是让一个信道专注于某一个单一功能的实现,通过对信道有序,合理的组合去实现实际消息处理功能。
对于WCF的信道栈来说,有两种信道是必须的:传输信道和消息编码信道。原因很简单,信道栈的最终任务总是实现对消息的传输,传输信道是必须的;在传输之前需要对消息进行编码,消息编码功能通过消息编码信道实现。所以,最简单的信道栈由传输信道和消息编码信道组成。
当然,在我们之前的例子当中,使用的 绑定 (即 EndPoint 中的 B) 都包含 传输信道和消息编码信道,只不过这一步是微软帮我们已经封装好的,我们在配置B的时候 感觉不到。
绑定与信道栈
在上面我们提到,整个WCF的体系结构可以分为两个层次:服务模型层和信道层。绑定:在整个结构体系中扮演着中间人的角色。从层次隶属来讲,绑定属于服务模型层,同时又是整个信道层的缔造者。在服务端,当服务被成功寄宿时,WCF通过服务终结点的绑定对象创建一个或多个信道监听器(ChannelListener),绑定到监听端口进行请求的侦听。当请求消息抵达,则利用信道监听器创建的信道栈进行消息的接受。服务操作执行的结果最终封装到回复消息中,通过相同的信道栈被回送。在客户端,通过绑定创建信道工厂(ChannelFactory),借助信道工厂创建的信道栈进行请求消息的发送与回复消息的接收。
我们看一个小Demo,代码在云盘
用于我们更加了解WCF的信道栈的工作,如何直接通过绑定进行消息通信。通过这个案例让大家对如何通过绑定创建信道栈,如何通过信道栈进行消息的发送和接收有一个感性的认识。
首先先看 结构,这个结构非常简单
[ 10-01 ]
再看监听端:
[ 10-02 ]
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel.Channels; 6 using System.ServiceModel; 7 8 namespace Listener 9 { /// <summary> 10 /// 创建请求监听的应用程序 11 /// </summary> 12 class Program 13 { 14 static void Main(string[] args) 15 { 16 17 Uri listenUri = new Uri("http://127.0.0.1:9999/listener"); 18 Binding binding = new BasicHttpBinding(); 19 IChannelListener<IReplyChannel> channelListener = binding.BuildChannelListener<IReplyChannel>(listenUri); 20 //执行完Open方法,表示信道监听器对象channel。 21 channelListener.Open(); 22 //信道监听器对象通过调用AcceptChannel创建信道栈进行请求的监听,可以看成信息接收后的载体 23 IReplyChannel channel = channelListener.AcceptChannel(TimeSpan.MaxValue); 24 //一旦信道栈被成功创建,那么就可以利用它对请求消息进行接收,处理了。 25 channel.Open(); 26 Console.WriteLine("开始监听"); 27 //通过一个无限循环来处理来自不同客户端的消息请求。 28 while (true) 29 { 30 /* 31 * 请求的接收通过IReplyChannel的ReceiveRequest方法实现,该方法接受一个TimeSpan类型参数,代表该方法从执行开始成功接受请求的时间,由于客户端 32 * 请求的频率不确定,在这里给它指定了一个最大值。ReceiveRequest并不像我们想象的一样返回一个代表请求消息的Message对象,而是返回一个RequestContext对象, 33 * 并通过该对象将创建的回复消息回复给请求方。注意:在请求/回复消息交换模式中,RequestContext是连接请求和回复的纽带, 34 * RequestContext不仅仅是对请求消息的封装,还可以用于回复消息的发送。 35 * 在本例中,我们通过它的RequestMessage属性得到请求消息,然后通过CreateReplyMessage方法创建一个回复消息,通过Reply方法回复给发送方. 36 */ 37 RequestContext requestContext = channel.ReceiveRequest(TimeSpan.MaxValue); 38 Console.WriteLine("接受到请求消息:\n{0}", requestContext.RequestMessage); 39 requestContext.Reply(CreateReplyMessage(binding)); 40 } 41 } 42 43 static Message CreateReplyMessage(Binding binding) 44 { 45 string action = "urn:ibeifeng.com/reply"; 46 string body = "这是一个简单的回复消息"; 47 return Message.CreateMessage(binding.MessageVersion, action, body); 48 } 49 50 51 } 52 }
最后看我们的发送端:
[ 10-03 ]
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 using System.ServiceModel.Channels; 7 namespace Sender 8 { 9 /// <summary> 10 /// 创建消息发送端应用程序 11 /// </summary> 12 class Program 13 { 14 static void Main(string[] args) 15 { 16 Uri listenUri = new Uri("http://127.0.0.1:9999/listener"); 17 Binding binding = new BasicHttpBinding(); 18 IChannelFactory<IRequestChannel> channelFactory = binding.BuildChannelFactory<IRequestChannel>(); 19 channelFactory.Open(); 20 //对请求消息的发送 21 IRequestChannel channel = channelFactory.CreateChannel(new EndpointAddress(listenUri)); 22 channel.Open(); 23 24 //channel.Request 不光发送消息,并且 接收 服务端消息 25 Message replyMessage = channel.Request(CreateRequestMessage(binding)); 26 Console.WriteLine("接收到回复消息\n{0}",replyMessage); 27 Console.Read(); 28 } 29 static Message CreateRequestMessage(Binding binding) 30 { 31 string action = "urn:ibeifeng.com/request"; 32 string body = "这是一个简单的请求消息"; 33 return Message.CreateMessage(binding.MessageVersion,action,body); 34 } 35 } 36 }
道管理器
在WCF中,信道通过信道管理器(ChannelManager)创建。对于信道管理器,大家可能有点陌生,不过应该还记得这两个对象:信道监听器(ChannelListener)和信道工厂(ChannelFactory).实际上,信道管理器是信道工厂和信道监听器的统称。由于它在客户端和服务端的作用不尽相同,信道管理器在服务端和客户端具有不同的名称。服务端的信道管理器的作用在于创建信道栈监听请求、接收消息,所以称为信道监听器;而客户端的信道管理器在于创建信道进行请求消息的发送和回复消息的接收,所以被称为信道工厂。
绑定编程
对于WCF来说,服务端和客户端采用基于终结点的通信手段。作为终结三要素之一,绑定实现了所有消息通信的细节。无论是在服务端对服务的寄宿,还是在客户端对服务的访问,都要创建相应的终结点,在终结点的创建过程中,对绑定的指定是必须的。
1).服务寄宿对绑定的指定
当我们通过一个托管的应用程序对一个服务进行自我寄宿的时候,一般会为服务创建一个ServiceHost对象,然后通过ServiceHost的AddServiceEndpoint方法为之添一个或多个终结点。
例如 下面的例子:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 using (ServiceHost serviceHost = new ServiceHost(typeof(CalculatorService))) 6 { 7 //BasicHttpBinding绑定方式 8 serviceHost.AddServiceEndpoint(typeof(ICalculator), new BasicHttpBinding(), "http://127.0.0.1:6666/CalculatorService"); 9 //WSHttpBinding绑定方式 10 serviceHost.AddServiceEndpoint(typeof(ICalculator), new WSHttpBinding(), "http://127.0.0.1:6666/CalculatorService"); 11 //NetTcpBinding绑定方式 12 serviceHost.AddServiceEndpoint(typeof(ICalculator), new NetTcpBinding(), "net.tcp://127.0.0.1:6666/CalculatorService"); 13 serviceHost.Open(); 14 Console.Read(); 15 } 16 } 17 }
当我们采用基地址/相对地址的方式对服务进行寄宿的时候,对于终结点中指定的相对地址,WCF会在对应的ServiceHost基地址列表中寻找传输协议和与绑定类型相匹配的基地址,两者相互组合构成完整的终结点地址。比如,在下面的代码中,为服务CalculatorService添加了两个基地址:http://127.0.0.1:8888和net.tcp://127.0.0.1:9999.并通过AddServiceEndpoint方法添加了两个终结点,这不同的终结点具有不同的绑定类型和相同的相对地址(CalculatorService)。WCF会根据绑定的具体
类型,在基地址列表中寻找与传输协议匹配的基地址,基地址和相对地址相互组合,构成最终的终结点地址。
例如:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Uri[] baseAddresses = new Uri[] { new Uri("http://127.0.0.1:8888"), new Uri("net.tcp://127.0.0.1:9999") }; 6 using (ServiceHost serviceHost = new ServiceHost(typeof(CalculatorService), baseAddresses)) 7 { 8 serviceHost.AddServiceEndpoint(typeof(ICalculator), new WSHttpBinding(), "CalculatorService"); 9 serviceHost.AddServiceEndpoint(typeof(ICalculator), new NetTcpBinding(), "CalculatorService"); 10 serviceHost.Open(); 11 Console.Read(); 12 } 13 } 14 }
我们再看下 AppConfig的绑定写法规范
所有的绑定对象都具有一些共同的属性,比如OpenTimeout,CloseTimeout,SendTimeout,ReceiveTimeout等。此外,不同的绑定对象也有一些属于自己的属性。如果没有显式地对这些属性进行设置,那么将采用这些属性的默认值。如果希望通过配置的方式对
绑定对象进行定制,需要将相关的设置定义在binding列表中。下面的配置中,在<bindings>节点下添加了一个<basicHttpBinding>的节点,并为其指定了一系列的属性,并将该<basicHttpBinding>命名为”MyBinding”,在具体的终结点中,通过bindingConfiguration根据名称对绑定进行引用.
例如:
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <bindings> 5 <basicHttpBinding> 6 <binding name="MyBinding" openTimeout="00:05:00" receiveTimeout="00:15:00" sendTimeout="00:15:00"/> 7 </basicHttpBinding> 8 </bindings> 9 <services> 10 <service name="Services.CalculatorService"> 11 <endpoint address="http://127.0.0.1:6666/CalculatorService" binding="basicHttpBinding" bindingConfiguration="MyBinding" contract="Contracts.ICalculator"></endpoint> 12 </service> 13 </services> 14 </system.serviceModel> 15 </configuration>
我们再谈谈 绑定的类型:
绑定类型 | 配置名 | 描述 | 补充 |
BasicHttpBinding | basicHttpBinding |
一个web服务绑定, 用与早起的诸如WebService等Web服务相兼容 |
这里主要是与早起的WebService兼容 如果你现在要做一个与之前的WebService交互 就要使用这种绑定类型 |
WSHttpBinding | wsHttpBinding |
一个web服务绑定, 用于支持最新的可互操作Web服务标准, 并与最近的Web服务相兼容 |
比较常用的HTTP方式绑定 在安全性与事物上都比BasicHttpBinding强 推荐使用 |
WSDualHttpBinding | wsDualHttpBinding |
一个web服务绑定, 用于支持双向通信 |
支持HTTP协议的双工通信 |
NetNamedPipeBinding | netNamedPipeBinding |
一个面向连接的绑定 用于再同一台机器上通过命名管道进行通信 |
没什么说的 |
NetTcpBinding | netTcpBinding |
一个面向连接的绑定 用于通过TCP进行跨进程和跨机器边界的通信 |
也可以支持双工通信,是关于TCP的双工通信 |
NetMsmqBinding | netMsmqBinding | 基于支持MSMQ的持久可靠通信 | 消息队列的 绑定模式 |
说下这些绑定的类型 在什么场合使用:
BasicHttpBinding:
在默认的情况下采用HTTP和基于文本的消息编码方式.同时还提供了对安全的支持,无论是基于传输的安全还是基于消息的安全(后面讲,这里记住就可以了),都可以通过对绑定进行相应的设置实现。
将安全模式设为传输安全模式:
BasicHttpBinding binding=new BasicHttpBinding(BasicHttpSecurityMode.Transport)
如果我们将其设置成基于消息的安全模式,并将客户端的凭证类型设为证书:(Certificate,这对于基于消息安全模式的BasicHttpBinding是必须的).
BasicHttpBinding binding=new BasicHttpBinding(BasicHttpSecurityMode.Message)
binding.Security.Message.ClientCredentialType=BasicHttpMessageCredentialType.Certificate;
BasicHttpBinding还提供基于MTOM编码的支持。
方式对消息编码方式进行显示指定。代码如下:
BasicHttpBinding binding=new BasicHttpBinding();
binding.MessageEncoding=WSMessageEncoding.Mtom;
BasicHttpBinding可以和传统的ASP.NET ASMX Web Service进行互操作.
默认情况下采用基于文本的编码方式和基于Http的传输协议。同时实现对事务和实现基于消息的安全。
WsHttpBinding在默认的情况下就提供了基于消息安全的支持,此外WsHttBinding仍然提供基于HTTPS的传输安全。
下面的代码,为基于传输的安全的安全模式:
WsHttpBinding binding=new WsHttpBinding(SecurityMode.Transport)
除了提供对传输和消息安全的支持之外,还对传输的可靠性提供支持。可靠性消息确保网络环境不好的情况下消息能有效,有序地抵达目的地。
WsHttpBinding binding=new WsHttpBinding(SecurityMode.Message,true);
编码也可以是MTOM.
WsDualHttpBinding:
该绑定模式是专门为HTTP传输下双工消息交换模式设计的。除了基于传输的安全之外,WsHttpBinding所有的特性都被WsDualHttpBinding继承下来,这包括基于HTTP的传输,基于文本和MTOM的消息编码等。
为什么WsDualHttpBinding不支持基于传输的安全呢?
原因很简单,因为HTTP协议下的传输安全通过HTTPS(SSL)实现,HTTPS依赖于一个真正意义上的WEB站点,也就是说只有访问一个真正意义上WEB站点资源的前提下,HTTPS才会有意义。而对于双工通信来说,由于客户端满足这样要求,所以从服务端回调客户端的传输安全是无法确保的。
HTTP的通信通道不支持双工通信,WsDualHttpBinding采用了两个HTTP通道
到目前位置,我们以供介绍了3种类型的绑定。从对于传输协议的支持来看,它们都是基于HTTP和HTTPS的绑定;从消息的编码的角度来看,它们均支持基于纯文本的消息编码和MTOM编码。除了WsDualHttpBinding(受到双向通信的限制),这些属性都决定了这两种绑定具有较好的互操作性,也就是说,对于前两种绑定的应用并不限于基于.NET平台应用的交互,如果通过这些绑定寄宿服务,其他平台的客户端可以调用服务,同理我们也可以利用基于这些绑定的客户端访问其他非.NET平台的WEB服务,只要对方支持相应的标准.
NetTcpBinding:
由于NetTcpBinding采用TCP作为传输协议,所以它一般应用于企业网(局域网)中;由于采用二进制的消息编码方式,在性能上较基于文本的编码会有较大的提高;此外,由于和HTTP协议不同,TCP本身就是一个基于双工通信的协议,所以它和WsDualBinding一样,可以用于基于双工消息交换模式的WCF应用中.
除了对传输安全模式的支持(默认),也提供对消息安全模式的支持。
NetTcpBinding binding=new NetTcpBinding(SecurityMode.Message);
和WsHttpBinding一样,NetTcpBinding也提供对可靠会话的支持(后面讲,这里记住就可以了),以保障数据包或消息的可靠、有序传递。不过与WsHttpBinding的实现机制不同的是,NetTcpBinding采用的是TCP协议固有的可靠性传输机制,比如消息确认机制、重发机制等。
下面的代码,通过ReliableSession.Enabled属性让绑定实现对可靠会话的支持:
NetTcpBinding binding=new NetTcpBinding();
binding.ReliableSession.Enabled=true;
NetNamedPipeBinding:
用于同一台机器的跨进程通信(IPC),是性能最好的绑定.
以上所有的属性设置是通过代码方式展示的,也可以通过配置:
1 <system.serviceModel> 2 <bindings> 3 <wsHttpBinding> 4 <binding name="wsHttpSecureAndReliable" maxReceivedMessageSize="100000"> 5 <security mode="Transport"/> 6 </binding> 7 </wsHttpBinding> 8 </bindings> 9 <services> 10 <service name="HelloIndigo.HelloIndigoService"> 11 <endpoint contract="HelloIndigo.IHelloIndigoService" binding="wsHttpBinding" bindingConfiguration="wsHttpSecureAndReliable"></endpoint> 12 </service> 13 </services> 14 </system.serviceModel>
绑定中增加了消息的大小和数组的长度来适应最大可到100KB的数据。这个设置并不打算适应极端大型的消息负载,但是,它确实为一般消息超出64KB默认值的可能性留出了较大的回旋余地.
WCF默认接受信息就是为64KB,如果超出了 64KB 将出现异常信息.
声明式地定制BasicHttpBinding默认值
1 <basicHttpBinding> 2 <binding name="basicHttpSecure" maxBufferSize="100000" maxReceivedMessageSize="100000"> 3 <readerOuotas maxArrayLength="100000" maxStringContentLength="100000"/> 4 <security mode="Transport"/> 5 </binding> 6 </basicHttpBinding>
用代码设置
BasicHttpBinding binding=new BasicHttpBinding(BasicHttpSecurityMode.Transport);
binding.MaxBufferSize=100000;
binding.MaxReceivedMessageSize=100000;
binding.ReaderQuotas.MaxArrayLength=100000;
binding.ReaderQuotas.MaxStringContentLength=1000000;
EndpointAddress address=new EndpointAddress(“https://localhsot/........”);
2).服务调用对绑定的指定
通过添加服务引用的方式,生成的核心类是继承自System.ServiceModel.ClientBase<TChannel>的子类,TChannel为服务契约类型
1 [ServiceContract(NameSpace="http://www.HulkXu.com")] 2 Public interface ICalculator 3 { 4 [OperationContract] 5 double Add(double x,double y); 6 }
针对上面这个服务契约,引用对应的该服务,将会生成3个类型:ICalculator,ICalculatorChannel,CalculateClient.
ICalculator:可以看成是客户端的等效服务契约
ICalculatorChannel:定义了客户端信道的基本行为
CalculateClient:是最终用于服务访问的服务代理类,并实现了服务契约
为ChannelFactory<TChannel>指定绑定从远程调用的角度来理解WCF的客户端程序,其目的在于创建一个代理对象实现远程调用;从消息交换的角度来讲,WCF客户端程序的目的在于创建一个信道栈用于向服务端发送消息和接收回复。所以我们需要的就是通过一个对象来创建这样的服务代理或是信道栈,从编程模型的角度来看,这个对象就是System.ServiceModel.ChannelFactory<TChannel>
服务调用对绑定的指定通过前面的介绍,我们知道了WCF服务调用具有两种典型的方式:一种通过添加服务引用的方式,另一种是通过ChannelFactory直接创建服务代理对象进行服务调用.
以上是关于第十讲:绑定(信道)的主要内容,如果未能解决你的问题,请参考以下文章