第六篇:单向与双向通讯

项目开发中我们时常会遇到需要异步调用的问题,有时忽略服务端的返回值,有时希望服务端在需要的时候回调,今天就来看看在WCF中如何实现。

先看不需要服务端返回值的单向调用,老规矩,直接上代码,再解释。

1、服务端

契约接口中增加一个Sleep方法:

  1. using System;  

  2. using System.ServiceModel;  

  3. using System.Text;  

  4. namespace Server  

  5. {  

  6.    [ServiceContract(Namespace="WCF.Demo")]  

  7. publicinterface IData  

  8.    {  

  9.        [OperationContract]  

  10. string SayHello(string userName);  

  11. /// <summary>

  12. /// IsOneWay = true 表明这是一个单向调用,注意返回值是void,因为既然是单向调用,客户端肯定不会等待接收返回值的

  13. /// </summary>

  14.        [OperationContract(IsOneWay = true)]

  15. void Sleep();

  16.    }  

  17. }


对应的实现类中,我们来实现这个方法:

  1. using System;  

  2. using System.Text;  

  3. namespace Server  

  4. {  

  5. publicclass DataProvider : IData  

  6.    {  

  7. publicstring SayHello(string userName)  

  8.        {  

  9. returnstring.Format("Hello {0}.", userName);  

  10.        }  

  11. /// <summary>

  12. /// 实现Sleep方法,暂时不做任何事情,只是睡眠5秒

  13. /// </summary>

  14. publicvoid Sleep()

  15.        {

  16.            Thread.Sleep(5000);

  17.        }

  18.    }  

  19. }  

App.config就不再列出来了,里面用的是一个netTcpBinding的endpoint。


2、客户端

首先别忘了客户端的契约要与服务端保持一致,App.config也不列出来了,里面有对应的endpoint。主要是调用的代码:

  1. using System;  

  2. using System.ServiceModel;  

  3. using System.ServiceModel.Channels;  

  4. namespace Client  

  5. {  

  6. class Program  

  7.    {  

  8. staticvoid Main(string[] args)  

  9.        {  

  10.            var proxy = new ChannelFactory<Server.IData>("DataService").CreateChannel();  

  11. //先调用SayHello方法

  12.            Console.WriteLine(proxy.SayHello("WCF"));  

  13. //调用一下Sleep方法,按我们的设想,它应该是异步的,所以不会阻塞后面的调用

  14.            proxy.Sleep();

  15. //再调用一次SayHello方法

  16.            Console.WriteLine(proxy.SayHello("WCF"));  

  17. //关闭连接

  18.            ((IChannel)proxy).Close();  

  19.    }  

  20. }

按我们的设想,两次SayHello调用之间应该没有延迟,因为Sleep是异步的嘛,编译运行一下,结果……  中间卡住了5秒,这是为什么呢?

这其中涉及到一个并发模型的问题,默认情况下,WCF以单线程模型对外提供服务,也就是说,只能一个一个处理请求,即使是一个OneWay的单向调用,也只能等它处理完后才会接着处理后面的SayHello请求,所以会卡5秒。

并发模式有以下三种,MSDN上的介绍有点复杂,我给简化一下:

Single:单线程调用,请求只能一个一个处理;

Reentrant:可重入的单线程调用,本质仍是单线程,处理回调时,回调请求会进入队列尾部排队;

Multiple:多线程调用,请求是并发的响应的;


调置服务并发模型是在契约的实现类上,我们为DataService类加一个Attribute:

  1. /// <summary>

  2. /// 用ServiceBehavior为契约实现类标定行为属性,此处指定并发模型为ConcurrencyMode.Multiple,即并发访问

  3. /// </summary>

  4. [ServiceConcurrencyMode = ConcurrencyMode.Multiple)]

  5. publicclass DataProvider : IData

  6. {

  7. //略

  8. }


这回再编译运行一下,连续打出了2行 Hello WCF,中间不再阻塞了。


现在我们再来看看双向通讯的问题。双向通讯可以基于HTTP、TCP、Named Pipe、MSMQ,但要注意,basicHttpBinding和wsHttpBinding不行,要换用wsDualHttpBinding,它会创建两个连接来进行双向通讯。至于TCP,它天然就是双向通讯的。

1、服务端

服务契约要进行修改,增加关于回调的契约:

  1. using System;

  2. using System.ServiceModel;

  3. using System.ServiceModel.Description;

  4. namespace Server

  5. {

  6. /// <summary>

  7. /// 增加了CallbackContract的标识,用于指明针对此服务契约的回调契约是IDataCallback

  8. /// </summary>

  9.    [ServiceContract(Namespace = "WCF.Demo", CallbackContract = typeof(IDataCallback))]

  10. publicinterface IData

  11.    {

  12.        [OperationContract]

  13. string SayHello(string userName);

  14.        [OperationContract(IsOneWay = true)]

  15. void Sleep();

  16.    }

  17. /// <summary>

  18. /// 定义服务回调契约,注意它没有契约标识,只是个一般接口

  19. /// </summary>

  20. publicinterface IDataCallback

  21.    {

  22. /// <summary>

  23. /// 定义一个回调方法,由于回调不可能要求对方再响应,所以也标识成OneWay的调用,同样不需要有返回值

  24. /// </summary>

  25.       [OperationContract(IsOneWay = true)]

  26. void SleepCallback(string text);

  27.    }

  28. }


对应的契约实现类要修改一下:

  1. using System;

  2. using System.ServiceModel;

  3. using System.ServiceModel.Description;

  4. using System.Threading;

  5. using System.Net;

  6. namespace Server

  7. {

  8.    [ServiceConcurrencyMode = ConcurrencyMode.Multiple)]

  9. publicclass DataProvider : IData

  10.    {

  11. publicstring SayHello(string userName)

  12.        {

  13. string.Format("Hello {0}.", userName);

  14.        }

  15. publicvoid Sleep()

  16.        {

  17.            //先睡5秒

  18.            Thread.Sleep(5000);


  19.            //用OperationContext.Current来获取指定类型的回调对象

  20.            var callback = OperationContext.Current.GetCallbackChannel<IDataCallback>();

  21.            //回调SleepCallback方法,并传递参数

  22.            callback.SleepCallback("睡醒了");

  23.        }

  24.    }

  25. }


2、客户端

仍然提醒一下别忘了把新的服务契约更新到客户端。客户端的调用要调整一下:

  1. using System;  

  2. using System.ServiceModel;  

  3. using System.ServiceModel.Channels;  

  4. namespace Client  

  5. {  

  6. class Program  

  7.    {  

  8. staticvoid Main(string[] args)  

  9.        {  

  10. //定义一个实现回调接口的类实例

  11.            var context = new DataCallbackImp();

  12. //创建代理的时候变了,要用DuplexChannelFactory,因为IData契约已经标识了有回调,所以必须要用支持双向通讯的ChannelFactory,传入刚才创建的回调实例

  13.            var proxy = new DuplexChannelFactory<Server.IData>(context, "DataService").CreateChannel();  

  14. //调用Sleep

  15.            proxy.Sleep();

  16. //调用SayHello方法

  17.            Console.WriteLine(proxy.SayHello("WCF"));  

  18. //等待按任意键,先不要关连接

  19.            Console.ReadKey();

  20.            ((IChannel)proxy).Close();  

  21.    }  

  22. /// <summary>

  23. /// 实现回调接口中的类,图省事写到这里了

  24. /// </summary>

  25. class DataCallbackImp : Server.IDataCallback

  26.    {

  27. /// <summary>

  28. /// 实现SleepCallback方法

  29. /// </summary>

  30. publicvoid SleepCallback(string text)

  31.        {

  32.            Console.WriteLine("收到回调了:" + text);

  33.        }

  34.    }

  35. }


编译运行,屏幕先显示一行“Hello WCF.”,过5秒后显示“收到回调了:睡醒了”。


本文出自 “兔子窝” 博客,请务必保留此出处http://boytnt.blog.51cto.com/966121/803655