第三讲:WCF介绍(3)
代码
https://yunpan.cn/cPns5DkGnRGNs 密码:3913
典型传输协议下的URI
(1)HTTP和HTTPS
HTTPS(安全超文本传输协议).它是为了在WWW上解决安全的数据传输
而设计的。HTTS是采用了SSL的HTTP,SSL是一种加密协议。它们默认的端口
号分别是80和443
(2)TCP
WCF通过NetTcpBinding支持基于TCP的传输。对于TCP的URI,其传输协议前缀均为
net.tcp://。默认端口号808
net.tcp://127.0.0.1:808/calculatorService
(3):Pipe
对于同一台机器上不同进程间的通信(IPC),WCF具有专门的实现方式:命名管道(Named Pipes).
通过命名管道进行跨进程通信能够获得最好的性能优势。WCF将命名管道专门用于同一台机器的跨
进程通信,所以基于IPC的URI的主机名|域名|IP地址部分只能是本机的机器名,或者直接是localhost
或127.0.0.1
基于IPC的URI, 都具有net.pipe前缀,端口没有任何意义.
net.pipe://127.0.0.1/calculatorService
(4):Msmq 消息队列
net.msmq://xxx.com/calculatorService (公有队列)
net.msmq://xxx.com/private/calculatorService(私有队列)
为服务指定地址 ( 逻辑地址和物理地址 )
1:通过代码方式指定地址.
2:通过配置指定地址.
默认的情况下,监听地址与终结点地址是统一的。只有在逻辑地址和物理地址相互分离的情况下,才需要指定不同于终结点地址的监听地址.(也就是说服务器启动之后,我们上面配置服务器终结点 http://127.0.0.1:6666/calculatorservice 这个地址会被服务器不断的监听。 我们上面的例子都是 这种方式 去 监听的,因为我们上面的例子并没有配置 物理地址。)
在WCF中,每个终结点(服务端以及客户端的终结点)都包含两个不同的地址:逻辑地址和物理地址。逻辑地址就是以终结点Address属性表示的地址。(我们上面 第一讲与第二讲 的例子配置的都是 逻辑地址 )
至于物理地址,对于消息发送端来讲,就是消息被真正发送的目的地址:而对于消息的接收端来讲,就是监听器真正监听的地址。
之前第一讲的时候 我们 使用代码方式完成服务端的 服务寄宿 时候需要 填写 A,B,C
其实后面还有一个选项参数 ,就是我们的 物理地址 参数了,只不过之前我们都没有写
[ 3-01 ]
看上图 我们 服务器的 物理地址 配置就是这里,如果这里配置了 物理地址 ,那么服务器的监听地址就不是配置的 参数 A 了,而是配置的物理地址。
[ 3-02 ]
我们配置一下服务端的 (以代码的方式去配置)
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Security.Policy; 5 using System.ServiceModel; 6 using System.ServiceModel.Description; 7 using System.Text; 8 using System.Threading.Tasks; 9 using Contracts; 10 using Services; 11 12 namespace Hosting 13 { 14 class Program 15 { 16 static void Main(string[] args) 17 { 18 //首先提供一个主机进程,实际上就是完成寄宿的主机( CalculatorService 完成了对契约的实现,所以我们这里就寄宿就寄宿它了 ) 19 using (var host = new ServiceHost(typeof(CalculatorService))) 20 { 21 /* 22 然后 当我们把寄宿后,就要完成我们的EndPoint(终结点) 了,之前提过EndPoint=A,B,C 那么这里就要绑定了 23 之前的图片 [ 1-05 ] 已经展示 过这个 EndPoint 24 这里的 WSHttpBinding 就是 指定 HTTP 协议 当然还有很多种,后面说到 25 添加终结点 首先 C,然后 B 最后 A 26 */ 27 host.AddServiceEndpoint(typeof(ICalculator), new WSHttpBinding(), "http://127.0.0.1:6666/calculatorservice", new Uri("http://127.0.0.1:8888/calculatorservice")); 28 29 //这里检测 元数据 为不为空 这里的元数据也不管是什么东西,下面我们说到 30 if (host.Description.Behaviors.Find<ServiceMetadataBehavior>() == null) 31 { 32 //也就是说这里我们是以元数据的形式发布出去进行客户端与服务器的交互 33 //控制服务元数据和相关信息的发布 34 ServiceMetadataBehavior behavior = new ServiceMetadataBehavior(); 35 behavior.HttpGetEnabled = true;//是否可以通过 HTTP Get 形式 去访问 36 // 元数据的地址,可以通过这个地址 在 浏览器中 进行访问 37 behavior.HttpGetUrl = new Uri("http://127.0.0.1:6666/calculatorservice/metadata"); 38 //添加到元数据中去 39 host.Description.Behaviors.Add(behavior); 40 } 41 //指定一个事件,当服务启动之后 需要做什么,这里指定一个委托,在 Open 成功后,就执行这里的事件 42 host.Opened += (sender, eventArgs) => Console.WriteLine("服务已经启动,按任何按钮停止"); 43 //开启服务 44 host.Open(); 45 Console.Read(); 46 } 47 } 48 } 49 }
[ 3-03 ]
当然也可以使用 配置文件的方式去配置服务端
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <behaviors> 5 <serviceBehaviors> 6 <behavior name="metadataBehavior"> 7 <serviceMetadata httpGetEnabled="true" httpGetUrl="http://127.0.0.1:9999/calculatorservice/metadata"/> 8 </behavior> 9 </serviceBehaviors> 10 </behaviors> 11 <services> 12 <service behaviorConfiguration="metadataBehavior" name="Services.CalculatorService"> 13 <!--服务端 配置 物理地址 也就是 真正监听的地址 listenUri="" --> 14 <endpoint address="http://127.0.0.1:9999/calculatorservice" 15 listenUri="http://127.0.0.1:8888/calculatorservice" 16 binding="wsHttpBinding" contract="Contracts.ICalculator"></endpoint> 17 </service> 18 </services> 19 20 </system.serviceModel> 21 </configuration>
那么我们客户端的配置就是 ( 这里就说WebConfig 去配置物理地址了。代码方式配置 物理地址没有找到,如果有朋友知道通知一声,谢谢! )
[ 3-04 ]
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <behaviors> 5 <endpointBehaviors> 6 <behavior name="clientViaBehavior"> 7 <!--客户端配置物理地址 (也就是信息具体发送地址) viaUri 这个 参数 --> 8 <clientVia viaUri="http://127.0.0.1:8888/calculatorservice"/> 9 </behavior> 10 </endpointBehaviors> 11 </behaviors> 12 <client> 13 <endpoint behaviorConfiguration="clientViaBehavior" address="http://127.0.0.1:9999/calculatorservice" 14 binding="wsHttpBinding" contract="Contracts.ICalculator" name="calculatorservice"></endpoint> 15 </client> 16 </system.serviceModel> 17 </configuration>
(1)服务端逻辑地址与物理地址。
对于消息接收放的终结点来讲,物理地址就是监听地址。
public ServiceEndpoint AddServiceEndpoint(string implementedContract,Binding binding,string address,Uri listenUri)
(2):客户端逻辑地址与物理地址
对于消息的发送端来讲,物理地址其实就是消息发送的真正目的地址。通过
ClientVia定义客户端URI代表物理地址.
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 using System.ServiceModel; 5 using System.ServiceModel.Description; 6 using Contracts; 7 using Services; 8 9 namespace Hosting 10 { 11 class Program 12 { 13 static void Main(string[] args) 14 { 15 //两个不同协议的基地址 16 Uri[] baseAddress = new Uri[2]; 17 baseAddress[0] = new Uri("http://127.0.0.1:9999"); 18 baseAddress[1] = new Uri("net.tcp://127.0.0.1:8888"); 19 20 using (ServiceHost host = new ServiceHost(typeof(CalculatorService), baseAddress)) 21 { 22 //两个不同协议的相对地址 23 host.AddServiceEndpoint(typeof(ICalculator), new WSHttpBinding(), "calculatorservice"); 24 host.AddServiceEndpoint(typeof(ICalculator), new NetTcpBinding(), "calculatorservice"); 25 26 host.Opened += delegate 27 { 28 Console.WriteLine("服务已经启动,请按任意键中止服务"); 29 }; 30 host.Open(); 31 Console.Read(); 32 } 33 } 34 } 35 }
客户端
[ 3-06 ]
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 using Contracts; 7 8 namespace Client 9 { 10 class Program 11 { 12 static void Main(string[] args) 13 { 14 //这里按照之前不变 15 using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>(new WSHttpBinding(), "http://127.0.0.1:9999/calculatorservice")) 16 { 17 ICalculator proxy = channelFactory.CreateChannel(); 18 using (proxy as IDisposable) 19 { 20 Console.WriteLine("x+y={2} when x={0} and y={1}", 1, 2, proxy.Add(1, 2)); 21 Console.ReadKey(); 22 } 23 } 24 } 25 } 26 }
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 using System.ServiceModel; 5 using System.ServiceModel.Description; 6 using Contracts; 7 using Services; 8 9 namespace Hosting 10 { 11 class Program 12 { 13 static void Main(string[] args) 14 { 15 //以下是通过配置文件更改了终结点的添加和服务行为的定义. 16 using (ServiceHost host = new ServiceHost(typeof(CalculatorService))) 17 { 18 host.Opened += delegate 19 { 20 Console.WriteLine("CalculaorService已经启动"); 21 }; 22 host.Open(); 23 Console.Read(); 24 } 25 } 26 } 27 }
服务端 App.config
[ 3-08 ]
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <services> 5 <service name="Services.CalculatorService"> 6 <!--配置两个不同协议的想相对地址--> 7 <endpoint address="calculatorservice" binding="wsHttpBinding" contract="Contracts.ICalculator"></endpoint> 8 <endpoint address="calculatorservice" binding="netTcpBinding" contract="Contracts.ICalculator"></endpoint> 9 <host> 10 <baseAddresses> 11 <!--配置两个不同协议的基地址--> 12 <add baseAddress="http://127.0.0.1:9999"/> 13 <add baseAddress="net.tcp://127.0.0.1:8888"/> 14 </baseAddresses> 15 </host> 16 </service> 17 </services> 18 </system.serviceModel> 19 </configuration>
服务端配置好了
客户端 Program.cs
[ 3-09 ]
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 using Contracts; 7 8 namespace Client 9 { 10 class Program 11 { 12 static void Main(string[] args) 13 { 14 using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorservice")) 15 { 16 ICalculator proxy = channelFactory.CreateChannel(); 17 using (proxy as IDisposable) 18 { 19 Console.WriteLine("x+y={2} when x={0} and y={1}", 1, 2, proxy.Add(1, 2)); 20 Console.ReadKey(); 21 } 22 } 23 } 24 } 25 }
客户端 App.config
[ 3-10 ]
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <client> 5 <endpoint address="http://127.0.0.1:9999/calculatorservice" binding="wsHttpBinding" contract="Contracts.ICalculator" name="calculatorservice"></endpoint> 6 <!--<endpoint address="net.tcp://127.0.0.1:8888/calculatorservice" binding="netTcpBinding" contract="Contracts.ICalculator" name="calculatorservice"></endpoint>--> 7 </client> 8 </system.serviceModel> 9 </configuration>
从上面的代码中我们可以看到 服务端 写了 两个 不同协议的监控地址 这是可以的,但不能配置同一协议的 两个不同的地址
而客户端则只能写一个 地址,不管是HTTP协议还是TCP谐音,有且只能写一个地址。
基地址与相对地址
除了以绝对路径的方式指定某个服务的终结点地址外,还可以通过”基地址+相对地址”的方式进行设置.对于一个服务来说,可以指定一个或多个基地址,但是对一种传输协议类型,只能就有一个唯一的基地址。
好了 我们的补充 就到这里了
我们来看我们这节的主要知识:服务契约
服务契约:
是相关操作的集合.
契约就是双方或多方就某个关注点达成的一种共识,是一方向另一方的一种承诺。签署了某个契约就意味着自己有义务履行契约中规定的各项规定,一旦违约势必影响契约双方的正常交互.
我们主张通过抽象将接口和实现相互分离,鼓励接口的依赖,避免基于实现的依赖。接口是稳定的,而实现则是易变的,基于接口的服务调用能够更有效地应对实现的变化带来的影响。
接口从本质上就是一种契约,当某个类实现了某个接口,就相当于签署了一份契约。所以契约关心的是“我能做到”,不在于”我如何做到”。所以,服务契约是以接口的形式进行定义的。
有了服务契约,那我们如何去描述 服务契约?
对于服务契约来说,它涉及的参与者就是服务的提供者和服务的消费者,服务的提供者通过服务契约的形式将服务公布出来,服务的消费者通过服务契约进行服务的消费。那么,要保证服务的正常消费,有一个根本的前提:服务的消费者能够正确“理解”服务提供者公布出来的服务契约。
也就是必须有一个统一的标准:XML因其简单,表意能力强,已经成为了事实上的标准。如何表达通过XML的数据结构?XSD是最好的选择。而对于WEB服务的描述,它有自己专门的标准,最基本的就是WSDL。
所以,如果希望服务契约能被基于不同平台的客户端所理解的话,就应以一种平台无关的标准进行描述,而在WCF中服务契约就是最终可以通过WSDL描述的。
就是我们上面说的 元数据
看这里的 GIF 图片 :
[ 3-11 ]
[ 3-12 ]
[ 3-13 ]
仔细看这里的XML:
[ 3-14 ]
1 <?xml version="1.0" encoding="UTF-8"?> 2 <xs:schema xmlns:tns=" " xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace=" " elementFormDefault="qualified"> 3 <xs:element name="Add"> 4 <xs:complexType> 5 <xs:sequence> 6 <xs:element name="x" type="xs:double" minOccurs="0"/> 7 <xs:element name="y" type="xs:double" minOccurs="0"/> 8 </xs:sequence> 9 </xs:complexType> 10 </xs:element> 11 <xs:element name="AddResponse"> 12 <xs:complexType> 13 <xs:sequence> 14 <xs:element name="AddResult" type="xs:double" minOccurs="0"/> 15 </xs:sequence> 16 </xs:complexType> 17 </xs:element> 18 </xs:schema>
这很明显的 描述了 我们的服务契约: 有一个Add 的方法 两个参数 x,y ,以及返回值 也是 double 类型