一步一步学习WCF:请求应答Request-Reply、单向操作 One-Way、回调契约Call Back
Posted on 2011-02-16 16:16 .狐狸血. 阅读(580) 评论(0) 编辑 收藏 举报【1】请求应答(Request-Reply):
请求应答模式是默认的操作模式。这与经典的C/S编程类似,客户端发送请求,阻塞客户端进程,服务端返回操作结果。请求应答模式与绑定对应关系 :
- 绑定协议名称 支持可靠性 默认可靠性 支持有序传递 请求应答模式
- BasicHttpBinding No N/A No Yes
- NetTcpBinding Yes Off Yes Yes
- NetPeerTcpBinding No N/A No No
- NetNamedPipeBinding No N/A (On) Yes Yes
- WSHttpBinding Yes Off Yes Yes
- WSFederationHttpBinding Yes Off Yes Yes
- WSDualHttpBinding Yes On Yes Yes
- NetMsmqBinding No N/A No No
- MsmqIntegrationBinding No N/A No Yes
除了NetPeerTcpBinding和NetMsmqBinding绑定,所有的绑定均支持请求-应答操作。
【2】单向操作(One-Way):
【2.1】概念:
简单来说,单向操作没有返回值,客户端只管调用,不管结果。单向操作客户端一旦发出请求,WCF会生成一个请求,不会给客户端返回任何消息。单向操作不同于异步操作,虽然单向操作只是在发出调用的瞬间阻塞客户端,但如果发出多个单向调用,WCF会将请求调用放入队列,并在某个时候执行。队列存储调用的个数是有限的,一旦发出的调用个数超出了队列存储调用的设置值,则会发生阻塞现象,因为调用无法放入队列。当队列的请求出列后,产生阻塞的调用就会放入队列,并解除对客户端的阻塞。绑定协议与单向请求模式关系:
和请求应答模式不同。所有的WCF绑定通信协议都支持单向操作。
【2.2】实现方式:
配置单向操作的方式也很简单,WCF的OperationContract 定义了IsOneWay属性。我们设置设置单向操作的方法是利用OperationContract特性的IsOneWay
属性,例如:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//操作契约,单调操作,不返回应答消息,会话服务中,保证是最后一个操作
[OperationContract(IsOneWay=true,IsInitiating=false,IsTerminating=true)]//
void SayHello2(string name);
单向操作配置的属性定义在操作契约级别上。而不是用在服务契约级别。
【2.3】单向操作小节:
(1)被设置为单向操作的方法不能包含返回值,即它的返回值只能为void,否则会抛出InvalidOperationException异常。
(2)在会话契约中虽然允许定义单向操作([ServiceContract( SessionMode =SessionMode.Required, Namespace = "http://www.cnblogs.com/frank_xl/")]),但由于单向操作服务端管理客户端会话状态十分困难,因而,单向操作的最佳适用场景是在单调服务或单例服务中。如果在会话契约中定义了单向操作,就必须保证单向操作是终止会话的最后一个操作,返回void类型值。这可以通过分步操作来实现。代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//1.单向服务契约,会话服务
[ServiceContract( SessionMode =SessionMode.Required, Namespace = "http://www.cnblogs.com/frank_xl/")]
public interface IWCFServiceOneWay
{
//操作契约,单调操作,不返回应答消息,会话服务中,保证是最后一个操作
[OperationContract(IsOneWay=true,IsInitiating=false,IsTerminating=true)]//
void SayHello2(string name);
//操作契约,
[OperationContract]
string SayHello1(string name);
}
【3】回调操作(Call Back):
【3.1】概念:
回调不是一个新的概念,早在C语言里就有过,C#里更是有委托实现回调机制。软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用;回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。回调和异步调用的关系非常紧密,通常我们使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。同步调用是三者当中最简单的,而回调又常常是异步调用的基础,因此,下面我们着重讨论回调机制在WCF软件架构中的实现。回调机制如图所示:
并非所有的绑定协议都支持回调,http本质上是无连接的协议,TCP/IP协议才会在客户端和服务端维持通信信道。两者之间的对应关系如下:
BasicHttpBinding,WSHttpBinding绑定协议不支持回调操作。NetTcpBinding和NetNamedPipeBinding绑定支持回调操作;具有可靠消息传输的WSDualHttpBinding绑定是通过设置两个HTTP信道来支持双向通信。
【3.2】实现代码:
一个服务契约只能包含一个回调契约。通过ServiceContract特性,可以指定回调契约:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//0.回调服务契约,由于回调方法在客户端执行,因此无须添加 ServiceContractAttribute。对于回调操作,服务器无须获取其返回信息,因此添加 IsOneWay=true 特性参数。
public interface IWCFServiceCallBack
{
//操作契约
[OperationContract(IsOneWay=true)]//
void SayHelloCalllBack();
}
//1.服务契约,指定 SessionMode 和回调类型。
[ServiceContract(SessionMode = SessionMode.Required,CallbackContract = typeof(IWCFServiceCallBack))]
public interface IWCFService
{
//操作契约,
[OperationContract]
string SayHelloToUser(string name);
}
回调契约无须标记ServiceContract特性,但是在回调契约中必须为服务的操作标记OperationContract特性。
在导入回调契约的元数据中,回调契约以Callback结尾。服务端反序列化本地代码的时候会生成客户端回调操作契约Callback后缀。
【3.3】回调小节:
(1)如果使用了回调契约,回调契约不需要ServiceContract特性,设置为回调契约就默认了服务契约的特性。
(2)客户端通过回调传递给服务端的消息包含了回调契约终结点的引用。在服务端,可以通过OperationContext类的泛型方法GetCallbackChannel<T>()获得。代码如下:
//获取客户端通道实例
IWCFServiceCallBack callback = OperationContext.Current.GetCallbackChannel<IWCFServiceCallBack>();
【4】示例代码分析:
直接看概念还不能很好的理解回调的机制,下面我们来具体看看WCF里如何实现回调。客户端调用服务操作,服务操作通过客户端上下文实例调用客户端操作,这是回调操作的基本过程。一下是具体的代码实现讲解过程。这里只介绍回调操作的具体实现代码。单向操作过于简单,注释也比较详细,大家可以参考上传的代码。
【4.1】服务端:
定义一个回调契约IWCFServiceCallBack,服务契约IWCFService、服务类WCFService : IWCFService继承服务契约。代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//1.回调服务契约,由于回调方法在客户端执行,因此无须添加 ServiceContractAttribute。对于回调操作,服务器无须获取其返回信息,因此添加 IsOneWay=true 特性参数。
public interface IWCFServiceCallBack
{
//操作契约
[OperationContract()]//
void SayHelloCalllBack();
}
//2.服务契约,指定 CallbackContract 回调契约。
[ServiceContract(CallbackContract = typeof(IWCFServiceCallBack))]
public interface IWCFService
{
//操作契约,
[OperationContract]
string SayHelloToUser(string name);
}
//3.服务类,继承接口。实现服务契约定义的操作
public class WCFService : IWCFService
{
//获取当前操作客户端对象实例
IWCFServiceCallBack callback = OperationContext.Current.GetCallbackChannel<IWCFServiceCallBack>();
//实现接口定义的方法
public string SayHelloToUser(string name)
{
//Action<IWCFServiceCallBack> invoke = delegate(IWCFServiceCallBack callBack)
//{ callBack.SayHelloCalllBack(); };
////callback(invoke);
Console.WriteLine("Hello! {0} , ", name);
callback.SayHelloCalllBack();
return "Hello! " + name;
}
}
服务端 获取当前操作客户端对象实例
IWCFServiceCallBack callback = OperationContext.Current.GetCallbackChannel<IWCFServiceCallBack>();callback.SayHelloCalllBack();执行回调客户端当前实例方法。
【4.2】宿主:
宿主启动和绑定节点配置和前面几节讲解的配置过程类似。这里配置的协议是Http。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using (ServiceHost host = new ServiceHost(typeof(LEIWCFService))) //定义服务主机 即 宿主进程
{
host.AddServiceEndpoint(typeof(ILEIWCFService), new WSDualHttpBinding(), "http://127.0.0.1:9999/LEIWCFService");
if (host.Description.Behaviors.Find<ServiceMetadataBehavior>() == null)
{
ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();
behavior.HttpGetEnabled = true;
behavior.HttpGetUrl = new Uri("http://127.0.0.1:9999/LEIWCFService/metadata");
host.Description.Behaviors.Add(behavior);
}
host.Opened += delegate
{
Console.WriteLine("CalculaorService已经启动,按任意键终止服务!");
};
host.Open();
Console.Read();
}
【4.3】客户端:
运行服务托管宿主,客户端添加服务引用,反序列化服务元数据,如图:
修改客户端代码,重新实现回调契约的操作方法,如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
namespace Client
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public class WCFServiceCallback : ServiceReferenceCallBack.ILEIWCFServiceCallback
{
#region ILEIWCFServiceCallback 成员
public void SayHelloCallBack()
{
Console.WriteLine("客户端继承接口回调方法:WCF,您好!我是客户。" + DateTime.Now.ToString());
}
#endregion
}
class Program
{
static void Main(string[] args)
{
//CallBack 回调服务
Console.WriteLine("Call Back Operation Test");
WCFServiceCallback callBack = new WCFServiceCallback();
//WCFClientCallBack.IWCFServiceCallback callBack = new WCFClientCallBack.WCFServiceCallback();
InstanceContext context = new InstanceContext(callBack);
ServiceReferenceCallBack.LEIWCFServiceClient WCFServiceCallBackClientProxy = new Client.ServiceReferenceCallBack.LEIWCFServiceClient(context, "WSDualHttpBinding_ILEIWCFService");
try
{
//WCFClientCallBack.WCFServiceClient WCFServiceCallBackClientProxy = new WCFClientCallBack.WCFServiceClient(context, "NetTcpBinding_IWCFService");
//通过代理调用调用SayHelloToUser,传递对象
Console.WriteLine(WCFServiceCallBackClientProxy.SayHelloToUser("张三"));
Console.Read();
}
catch (TimeoutException ex)
{
WCFServiceCallBackClientProxy.Abort();
Console.WriteLine("服务超时异常");
}
catch (CommunicationException ex)
{
WCFServiceCallBackClientProxy.Abort();
Console.WriteLine("服务链接异常");
}
finally
{
WCFServiceCallBackClientProxy.Close();
}
}
}
}
【4.4】运行结果:
这里的运行结果包括单向操作和回调操作结果,客户端调用一个服务操作,服务操作再通过客户端上下文实例引用调用客户端操作。成功执行回调操作。结果如图:
【5】总结:
(1) 服务对回调的调用可能会产生死锁。就是指当回调的应答消息也需要获得与服务实例关联的相同的锁时,会导致死锁。此时服务线程已经被阻塞,服务操作正在等待回调操作执行完毕,而回调操作却又在等待服务释放锁。 解决死锁的办法:<1>将服务配置为允许多线程访问,会增加服务开发者负担。
<2>将回调设置为重入(Reentrancy) //3.服务类,继承接口。实现服务契约定义的操作
[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant)]
public class WCFService : IWCFService
{...}
。所谓“重入”,是指对同步域拥有独占访问权的线程A调用了同步域之外对象的方法,此时,另外的线程B若要访问该同步域,则线程A将释放对同步域的锁,允许线程B进入。直到线程B执行完毕并释放对同步域的锁后,线程A将重新进入该同步域。由于服务被配置为重入,则服务调用回调引用时会释放锁。然后将回调返回给客户端,控制权则返回给服务,服务会重入并重新获取锁。
<3>将回调操作设置为单向操作( [OperationContract(IsOneWay=true)]// void SayHelloCalllBack();)。此时,回调调用不会产生应答消息,服务操作一旦执行了回调操作,就会继续执行,回调对象不会争用与服务实例关联的锁,从而解决了死锁问题。
(2)单调服务的回调问题需要考虑回调对象的引用存储问题,因为每次调用结束都会释放服务实例对象,客户端的状态也会丢失。
(3)单例服务的问题是,犹豫所有的服务共享一个实例,而其生命周期的问题,回调的引用计数会增加。
(4)回调契约的层级问题,一旦一个服务契约提供了回调契约的定义,其所有的子接口必须生命和其一致的回调契约。
(5)NetTcpBinding和NetNamedPipeBinding绑定支持回调操作,并且和客户端共享一个通道端口;具有可靠消息传输的WSHttpBinding需要维护连个端口有可能产生端口冲突,编程时值得注意。