WCF的服务端与客户端在通信时有三种模式:单向模式、请求/应答模式和双工模式。
①如果选用了单向模式,调用方在向被调用方进行了调用后不期待任何回应,被调用方在执行完调用后不给调用方任何反馈。如客户端通过单向模式调用了一个服务端的操作后,就去干别的了,不会等待服务端给他任何响应,他也无从得知调用是否成功,甚至连发生了错误也全然不知。这种模式的特点是,客户端在调用操作后立即返回,从客户端角度看,用户操作的响应是非常快的,只是客户端无法得知调用结果。
②如果选用了请求/应答模式,客户端向服务端发出调用后会一直等待服务端的回复,服务端在执行完操作后会把结果返回给客户端,即使服务操作签名返回值为void,服务端还是会返回一条空消息,告诉客户端调用完成了,客户端在接到返回后才会从调用方法返回继续进行下面的工作。这种模式的特点是客户端总是可以知道服务执行的情况,如果出错,错误也会返回,客户端对服务的执行监控的很好,但是由于在服务返回之前客户端会一直等待,所以如果服务端的服务执行时间比较长的话,客户端这边的用户响应就会很慢,如果客户端对服务的调用与用户界面在同一线程,在用户看来,应用程序就死在那里了。
③如果选用了双工模式,客户端和服务端都可以单独的向对方发送消息调用,其实这种模式是在单向模式基础上进行的,两边的调用都是单向调用,但是两边都可以独立的进行,谁也不用等待谁,这种模式比较复杂一些。
(1)单向模式与请求/应答模式
这两个模式的设置位置相同,都是通过修改操作协定的OperationContract属性的IsOneWay属性来设置。
①单向模式配置:
[ServiceContract]
public interface IHelloWCF
{
[OperationContract(IsOneWay=true)]
void PHelloWCF();
}
注意,在单向模式下,返回值必须是void,并且不能使用任何Out或Ref的方式返回参数值,也就是说不能以任何手段返回任何值,这是基础结构所不允许的,这样做会导致服务端抛出异常。而在请求/应答模式下,这些都是可以的,即使没有返回值(返回值为void),返回消息也会照样发送,只不过是个空消息。②请求/应答模式配置:
[ServiceContract]
public interface IHelloWCF
{
[OperationContract(IsOneWay=false)]
string PHelloWCF();
}
如果不配置IsOneWay属性,那么他默认是False的,也就是说默认的消息通信模式是请求/应答模式,除非我们显式的指定为单向模式。
实现的小例子:
①单向模式:
服务端.cs文件源代码:
using System;
using System.ServiceModel;
namespace LearnWCF
{
public interface IHelloWCF
{
[OperationContract(IsOneWay=true)]
void PHelloWCF();
}
public class HelloWCF:IHelloWCF
{
public void PHelloWCF()
{
System.Threading.Thread.Sleep(3000);
}
}
}
服务端配置文件源代码:
<configuration>
<system.serviceModel>
<services>
<service name="LearnWCF.HelloWCF" behaviorConfiguration="metadataExchange">
<endpoint address="" binding="wsHttpBinding" contract="LearnWCF.IHelloWCF"/>
<endpoint address="mex" binding="wsHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="metadataExchange">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
服务端.svc文件源代码:
<%@ServiceHost
language="c#"
Debug="true"
Service="LearnWCF.HelloWCF"
%>
客户端调用源代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ServiceModel;
namespace SessionMode_client
{
class Program
{
static void Main(string[] args)
{
SessionMode.HelloWCFClient client = new SessionMode.HelloWCFClient();
Console.WriteLine(DateTime.Now.ToLongTimeString());
client.PHelloWCF();
Console.WriteLine(DateTime.Now.ToLongTimeString());
Console.ReadLine();
}
}
}
这两部分的代码应该是非常熟悉了,唯一的区别是,单向模式返回值必须是void,所以服务实现方法只是延时3秒。
保存运行结果如下:
由结果可以看到,当处于单向模式时,只用了1秒,客户端与服务端建立会话后把调用送出就立即返回了,没有等待服务端睡的那三秒,当然此时的客户端也根本就不知道服务端在做什么。
②请求/应答模式
这里只需将IsOneWay属性改成false即可,保存运行结果如下;
由结果可以看到,在请求/应答模式下,整个调用花费了4秒钟,除了服务方法中Sleep了3秒,建立会话通讯什么的还用了1秒,在服务端方法Sleep的时候,客户端一直在等待。
注意事项:请求应答模式是需要会话支持的,必须使用支持会话的绑定,而且服务协定的SessionMode必须至少为Allowed,服务类的ServiceBehavior的InstanceContextMode必须是PerSession,我们在这里没有配置,因为他们是默认的,但是我们必须知道他们需要这样的配置才能支持请求/应答模式。
出现的问题:如果你在试验中遇到了莫名其妙的问题,尝试把客户端服务引用全部删掉重新添加服务引用,因为有的时候更新服务引用不总是那么好用。
(2)双工模式
在双工模式下,服务端和客户端都可以互相调用对方,并且每次调用都是单向模式,不需要等待。这样调用双方就会有很好的异步体验,想调的时候就调,然后我就去干别的,什么时候调用完成了,你可以通过回调来通知我,我再决定下一步的动作,谁都不等谁(一般调用是客户端发起的,回调是由服务端发起的)。
实现双工模式的条件:①必须支持使用双工通信的绑定。(例如wsDualHttpBinding)
②必须使用会话。
客户端要想调服务端,客户端必须拥有服务协定接口,而服务端必须拥有服务协定接口以及实现接口的服务类,在运行时服务端还要有服务类的实例。同理服务端调用客户端也是需要这些因素,只是名称变为回调接口,回调类。
双工模式运行的小例子(还是挂载在IIS上):
①首先建立SVC文件
<%@ServiceHost
language="c#"
Debug="true"
Service="LearnWCF.HelloWCF"
%>
这与之前的一致。
②编写服务代码文件
using System;
using System.ServiceModel;
namespace LearnWCF
{
[ServiceContract(SessionMode=SessionMode.Required,
CallbackContract=typeof(IHelloWCFCallback))]
public interface IHelloWCF
{
[OperationContract(IsOneWay=true)]
void PHelloWCF();
}
//定义回调协定接口
public interface IHelloWCFCallback
{
[OperationContract(IsOneWay = true)]
void Callback(string msg);
}
public class HelloWCF:IHelloWCF
{
private int _Counter;
public void PHelloWCF()
{
System.Threading.Thread.Sleep(10000);
string msg = "Hello from service!Time:" + DateTime.Now.ToLongTimeString();
//获得回调通道
IHelloWCFCallback callbackChannel = OperationContext.Current.GetCallbackChannel<IHelloWCFCallback>();
//调用回调操作
callbackChannel.Callback(msg);
}
}
}
接着,定义了一个IHelloWCFCallback的回调协定接口,这个接口的实现是写在客户端的。
然后,指定了IHelloWCF服务协定接口的回调协定为该回调协定接口,这样服务协定就知道怎样进行回调了。
最后,是实现服务接口的实现,实现过程是:第一步是休眠5秒,第二部是获得回调通道,第三步是调用回调操作, 将字符串msg传递给回调函数。
③编写配置文件
<configuration>
<system.serviceModel>
<services>
<service name="LearnWCF.HelloWCF" behaviorConfiguration="metadataExchange">
<endpoint address="" binding="wsDualHttpBinding" contract="LearnWCF.IHelloWCF"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="metadataExchange">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
这里主要修改的地方为,将服务终结点的绑定方式修改成wsDualHttpBinding,从而支持双工通信。
④建立客户端应用程序。
第一步,创建控制台应用程序,并添加服务引用。
第二部,编写客户端代码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ServiceModel;
namespace SessionMode_client
{
class Program
{
static void Main(string[] args)
{
//建立回调服务对象
HelloWCFCallback callbackObject = new HelloWCFCallback();
//建立实例上下文对象
InstanceContext clientContext = new InstanceContext(callbackObject);
//初始化代理类对象
SessionMode.HelloWCFClient client = new SessionMode.HelloWCFClient(clientContext);
Console.WriteLine("Client call begin:"+DateTime.Now.ToLongTimeString());
client.PHelloWCF();
Console.WriteLine("Client call end:"+DateTime.Now.ToLongTimeString());
Console.WriteLine("Client can process other things.");
Console.ReadLine();
}
}
public class HelloWCFCallback : SessionMode.IHelloWCFCallback
{
public void Callback(string msg)
{
Console.WriteLine(msg);
}
}
}
首先,是建立回调的服务对象。然后,是将得到的服务对象作为参数,建立实例上下文对象。
接着,是将得到的实例上下文对象作为代理类的构造函数参数,来初始化代理类,代理类会检测到服务端元数据中服务协定使用双工,然后会为我们准备好双工通道。
接着,使用代理类对象来进行调用操作。
最后,是实现回调协定接口,这里就是把服务端传过来的参数输出。
⑤最后,保存运行,得到的结果:
通过结果以及结合源代码,可以知道:客户端就调用了服务端一次,就花费了2s,然后客户端就去干别的事去了;客户端调用服务端,并休眠5s后,服务端执行回调操作,最后一行结果是服务端主动回调客户端方法后输出的结果。
双工通信小结:
关键点:
(1) 通信两端都有两个协定接口,接口的实现在两边一边一个。
(2) 支持双工的绑定。
(3) 指定服务协定的回调协定以建立回调联系。
(4) 客户端自己构造运行时服务类对象和实例上下文对象。
(5) 服务端通过操作上下文获得回调通道 。
(6) 需要会话的支持。
(7) 两个协定中的协定操作应为单向模式。
应用过程中发现一个问题:
在服务端,若服务名为WCFService,在定义回调函数接口时取名为ICallback;然后在客户端引用服务时,发现代理类已经自动生成回调接口,但是该接口名与服务端不一致,取名为IWCFServiceCallback,但是用法一致。目前不了解原因,找资料也找不到,因为不影响使用,先搁置。
服务器端的定义:
客户端的引用: