你说,服务器端回调有啥用呢?这样问,估计不好回答,是吧。不急,先讨论一个情景。

假设现有服务器端S,客户端A开始连接S并调用相关操作,其中有一个操作,在功能上有些特殊,调用后无法即时回复,因为在服务器上要作一些后续,而这些处理也许会消耗一定时间,比如:

向服务器上传了一个文件,但是,为了节约空间或出于其他目的,服务器要对刚上传的文件进行处理(压缩或者多媒体文件转码),这些操作无法马上向客户端回复,而客户端也不可能就停在这里一直在等。我们希望,在客户端上传文件后马上返回,而服务器对文件处理完成后再通知一下客户端。

这样就引出一个东东——回调,E文叫Call Back。我估计用E文表述可能更好理解,Call back就是相对于Call to而言的,即调用的方向与Call to相反。

是啊,有必要解释一下,什么叫回调。我讲一个故事吧。

有一天,脑残去书店买书,之前他听别人说有一本书叫《吹牛沉思录》很好看,于是脑残也想买一本。可是,当他到书店后,东找西寻了一番,硬是没看见那本书的影子。

于是,他跑到柜台问工作人员:“我想找《吹牛沉思录》,没找到。”

工作人员马上启动书店的信息管理系统,但可以由于该系统品德不太好,居然用了35秒才启动,然后,工作人员在上面查了一下,回过头说:“抱歉,这本书太抢手了,卖完了,需要拿货。”

脑残追问:“那要啥时候有货?”

工作人员说:“大概两三天后吧,这样吧,你留个联系方式,等到货到了我再联系你。”

……

对的,这就是回调的故事。

脑残(调用方)不知道书店什么时候有货(不清楚调用的操作什么时候返回),但他总不能每天都跑去书店看看,这样太不滑算(消耗资源),于是,书店(被调用方)建议,留下联系方式(只保留内存中函数指针的地址,即回调地址),只要货到了就通知脑残(反调用)。

回调比较典型的一种就是事件,事件驱动模型以前是在VB中被大量使用,后来.NET也继承了这些优点,在此之前,C++/MFC大家都知道的,是通过消息来处理的(消息循环),其实,事件就是对消息的进一步封装,这使得应用更加简便和灵活。

在.NET中我们知道,事件其实就是一个委托,由于委托可以同时绑定多个方法的特点,故被选为事件的表现类型,估计是这样的。

比如,我们常用的,为按钮的Click事件定义一个处理。

button.Click += new EventHandler(onClick)

这样,事件Click的订阅者就是onClick方法,所谓订阅事件,就像我们平时订阅XX杂志一样,只要有新一期发布就发快递给你,你不用天天打电话去杂志社问。

onClick并不是每一刻都去问button:“你被Click了吗?”,onClick就像一个报警系统,只要特定的事件发生,它就会报警。这就是一种回调,onClick不必主动去调用button,只要处于监听状态即可,只要button被Click,onClick就会执行,不用你去调用它。

讲了这么多,不知道各位理解了没?

在WCF中使用回调,只需要多定义一个接口即可,这个接口的方法和服务协定一样,要附加OperationContractAttribute特性。

然后在定义服务类时,在ServiceContractAttribute的CallbackContract中设置一个回调接口的Type。

在服务操作中,通过OperationContext的GetCallbackChannel方法取出回调协定的实例,调用回调的方法,就会在客户端寻找回调接口的实现类并调用对应的成员。

这样说显然不好理解,还是实践出真知。我们来做一个选号程序。

一:服务端实现

这次我们的实现和前些有些区别,我们这里增加了配置文件来代替部分程序功能。

第一步,新建一个控制台应用程序。

第二步,定义一个回调接口。

  1. namespace Server
  2. {
  3. interface ICallback
  4. {
  5. // 回调操作也必须One Way
  6. [OperationContract(IsOneWay = true)]
  7. void CallClient(int v);
  8. }
  9. }

第三步,定义服务协定。

  1. namespace Server
  2. {
  3. [ServiceContract(
  4. Namespace = "MyNamespace",
  5. CallbackContract = typeof(ICallback), /* 标注回调协定 */
  6. SessionMode = SessionMode.Required /* 要求会话 */
  7. )]
  8. public interface IServer
  9. {
  10. // 会话从调用该操作启动
  11. [OperationContract(IsOneWay = true, /* 必须 */
  12. IsInitiating = true, /* 启动会话 */
  13. IsTerminating = false)]
  14. void CallServerOp();
  15.  
  16. // 调用该操作后,会话结束
  17. [OperationContract(IsOneWay = true, /* 使用回调,必须为OneWay */
  18. IsTerminating = true, /* 该操作标识会话终止 */
  19. IsInitiating = false)]
  20. void End();
  21. }
  22. }

CallbackContract属性指向ICallback的Type。因为我要使用计时器每隔3秒钟生成一个随机数,并回调到客户端,故要启用会话。
第四步,实现服务协定。

  1. namespace Server
  2. {
  3. public class MyServer : IServer, IDisposable
  4. {
  5. private ICallback icb;
  6. private Timer timer = null;//计时器,定时干活
  7. Random rand = null;//生成随机整数
  8.  
  9. public void CallServerOp()
  10. {
  11. this.icb = OperationContext.Current.GetCallbackChannel<ICallback>();
  12. rand = new Random();
  13. // 生成随整数,并回调到客户端
  14. // 每隔3秒生成一次
  15. timer = new Timer((obj) => icb.CallClient(rand.Next()), null, 10, 3000);
  16. }
  17.  
  18. public void Dispose()
  19. {
  20. timer.Dispose();
  21. Console.WriteLine("{0} - 服务实例已释放。", DateTime.Now.ToLongTimeString());
  22. }
  23.  
  24. public void End() //结束
  25. {
  26. Console.WriteLine("会话即将结束。");
  27. }
  28. }
  29. }

第五步,完成服务器端的配置(通过App.config配置文件)。

新建配置文件,命名App.config,内容如下。

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <configuration>
  3. <system.serviceModel>
  4. <client />
  5. <services>
  6. <service name="Server.MyServer" behaviorConfiguration="Mybehavior" >
  7. <endpoint binding="netTcpBinding" bindingConfiguration="MynetTcpBinding" contract="Server.IServer" address="net.tcp://localhost:1211/rr">
  8. <identity>
  9. <dns value="localhost"/>
  10. </identity>
  11. </endpoint>
  12. <host>
  13. <baseAddresses>
  14. <add baseAddress="http://localhost:1378/services" />
  15. </baseAddresses>
  16. </host>
  17. </service>
  18. </services>
  19. <behaviors>
  20. <serviceBehaviors>
  21. <behavior name="Mybehavior" >
  22. <serviceMetadata httpGetEnabled="True"/>
  23. </behavior>
  24. </serviceBehaviors>
  25. </behaviors>
  26. <bindings>
  27. <netTcpBinding>
  28. <binding name="MynetTcpBinding">
  29. <security mode="None" />
  30. </binding>
  31. </netTcpBinding>
  32. </bindings>
  33. </system.serviceModel>
  34. </configuration>

既支持会话,传输速度又快的,非TCP莫属了,所以这里我选择NetTcpBinding,这样在默认行为下,每启动一个会话就创建一个服务实例,而当会话结束时就会释放。

第六步,实现服务的寄宿程序。

第二-五步实现了服务端的功能,结下来我们建一个服务的寄宿程序,用于服务的启动和停止。

添加Window服务类:

  1. namespace Server
  2. {
  3. partial class Host : ServiceBase
  4. {
  5. public Host()
  6. {
  7. InitializeComponent();
  8. }
  9.  
  10. #region 启动入口
  11. /// <summary>
  12. /// 启动入口
  13. /// </summary>
  14. /// <param name="args"></param>
  15. static void Main(string[] args)
  16. {
  17. var host = new Host();
  18. try
  19. {
  20. host.OnStart(args);
  21. Console.WriteLine("服务已启动");
  22. Console.ReadLine();
  23. }
  24. catch (Exception e)
  25. {
  26. }
  27. }
  28. #endregion
  29.  
  30. protected override void OnStart(string[] args)
  31. {
  32. var service = new ServiceHost(typeof(Server.MyServer));
  33. service.Open();
  34. }
  35.  
  36. protected override void OnStop()
  37. {
  38. // TODO: Add code here to perform any tear-down necessary to stop your service.
  39. }
  40. }
  41. }

至此服务端的代码就完成了。

第五-六步功能类似于:

  1. static void Main(string[] args)
  2. {
  3. Console.Title = "WCF服务端";
  4. // 服务器基址
  5. Uri baseAddress = new Uri("http://localhost:1378/services");
  6. // 声明服务器主机
  7. using (ServiceHost host = new ServiceHost(typeof(MyService), baseAddress))
  8. {
  9. // 添加绑定和终结点
  10. // tcp绑定支持会话
  11. NetTcpBinding binding = new NetTcpBinding();
  12. binding.Security.Mode = SecurityMode.None;
  13. host.AddServiceEndpoint(typeof(IService), binding, "net.tcp://localhost:1211/rr");
  14. // 添加服务描述
  15. host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
  16. try
  17. {
  18. // 打开服务
  19. host.Open();
  20. Console.WriteLine("服务已启动。");
  21. }
  22. catch (Exception ex)
  23. {
  24. Console.WriteLine(ex.Message);
  25. }
  26. Console.ReadKey();
  27. }
  28. }

二:客户端实现

第一步,新建一个wpf窗体应用项目。

第二步,到对应目录以管理员身份运行服务器端,然后在客户端添加服务引用。可以看到引用后客户端增加了app.config文件。

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <configuration>
  3. <system.serviceModel>
  4. <bindings>
  5. <netTcpBinding>
  6. <binding name="NetTcpBinding_IServer">
  7. <security mode="None" />
  8. </binding>
  9. </netTcpBinding>
  10. </bindings>
  11. <client>
  12. <endpoint address="net.tcp://localhost:1211/rr" binding="netTcpBinding"
  13. bindingConfiguration="NetTcpBinding_IServer" contract="ServiceReference1.IServer"
  14. name="NetTcpBinding_IServer">
  15. <identity>
  16. <dns value="localhost" />
  17. </identity>
  18. </endpoint>
  19. </client>
  20. </system.serviceModel>
  21. </configuration>

第三步,在客户端实现回调接口。

  1. namespace Client
  2. {
  3. /// <summary>
  4. /// 实现回调接口
  5. /// </summary>
  6. class MyCallback : ServiceReference1.IServerCallback
  7. {
  8. // 因为该方法是由服务器调用的
  9. // 如果希望在客户端能即时作出响应
  10. // 应当使用事件
  11. public void CallClient(int v)
  12. {
  13. if (this.ValueCallbacked != null)
  14. {
  15. this.ValueCallbacked(this, v);
  16. }
  17. }
  18.  
  19. /// <summary>
  20. /// 回调引发该事件
  21. /// </summary>
  22. public delegate void EventHandler(Object sender, int e);
  23. public event EventHandler ValueCallbacked;
  24. }
  25. }

注意,回调的接口是在客户端实现的,不是服务器端。

第四步,设计窗口。

  1. namespace Client
  2. {
  3. /// <summary>
  4. /// Interaction logic for MainWindow.xaml
  5. /// </summary>
  6. public partial class MainWindow : Window
  7. {
  8. ServiceReference1.ServerClient cl = null;
  9. MyCallback cb = null;
  10.  
  11. public MainWindow()
  12. {
  13. InitializeComponent();
  14. cb = new MyCallback();
  15. cb.ValueCallbacked += new MyCallback.EventHandler(cb_ValueCallbacked);
  16. }
  17.  
  18. void cb_ValueCallbacked(object sender, int e)
  19. {
  20. this.label2.Content = e.ToString();
  21. }
  22.  
  23. private void button1_Click(object sender, RoutedEventArgs e)
  24. {
  25. cl = new ServiceReference1.ServerClient(new System.ServiceModel.InstanceContext(cb));
  26. cl.CallServerOp();
  27. button1.IsEnabled = false;
  28. button2.IsEnabled = true;
  29. }
  30.  
  31. private void button2_Click(object sender, RoutedEventArgs e)
  32. {
  33. cl.End();
  34. button1.IsEnabled = true;
  35. button2.IsEnabled = false;
  36. }
  37. }
  38. }

现在来测试一下吧。

本文源码:http://files.cnblogs.com/yuanli/MyApp12.zip

传说中的WCF(12):服务器回调有啥用的更多相关文章

  1. 在Ubuntu 12 服务器上源码安装 OpenERP 8.0

    原文:http://vivianyw.blog.163.com/blog/static/134547422201421112349489/ 1. 安装SSH: sudo apt-get install ...

  2. 分享一个大型进销存供应链项目(多层架构、分布式WCF多服务器部署、微软企业库架构)

    项目源码下载:  WWW.DI81.COM 分享一个大型进销存供应链项目(多层架构.分布式WCF多服务器部署.微软企业库架构) 这是一个比较大型的项目,准备开源了.支持N家门店同时操作.远程WCF+企 ...

  3. 浅议Grpc传输机制和WCF中的回调机制的代码迁移

    浅议Grpc传输机制和WCF中的回调机制的代码迁移 一.引子 如您所知,gRPC是目前比较常见的rpc框架,可以方便的作为服务与服务之间的通信基础设施,为构建微服务体系提供非常强有力的支持. 而基于. ...

  4. 跟我一起学WCF(12)——WCF中Rest服务入门

    一.引言 要将Rest与.NET Framework 3.0配合使用,还需要构建基础架构的一些部件.在.NET Framework 3.5中,WCF在System.ServiceModel.Web组件 ...

  5. 传说中的WCF(11):会话(Session)

    在标题中我加了一个大家都很熟悉的单词——Session,熟吧?玩过Web开发的朋友肯定在梦中都会见到她. 在Web中为什么要会话呢?毕竟每个用户在一个Web应用中可能不止进行一次操作,比如,某二手飞机 ...

  6. 传说中的WCF(10):消息拦截与篡改

    我们知道,在WCF中,客户端对服务操作方法的每一次调用,都可以被看作是一条消息,而且,可能我们还会有一个疑问:如何知道客户端与服务器通讯过 程中,期间发送和接收的SOAP是什么样子.当然,也有人是通过 ...

  7. 传说中的WCF(9):流与文件传输

    在使用Socket/TCP来传输文件,弄起来不仅会有些复杂,而且较经典的“粘包”问题有时候会让人火冒七丈.如果你不喜欢用Socket来传文件,不妨试试WCF,WCF的流模式传输还是相当强大和相当实用的 ...

  8. 传说中的WCF(8):玩转消息协定

    Message翻译成中文,相信各位不陌生,是啊,就是消息,在WCF中也有消息这玩意儿,不知道你怎么去理解它.反正俺的理解,就像我们互发短信一个道理,通讯的双方就是服务器与客户端,说白了吧,就是二者之间 ...

  9. 传说中的WCF(7):“单向”&“双向”

    在WCF中,服务器与客户端的通讯有单向(单工)和双向(双工)之分.要说有什么形式上的表现,那就是单向与双向生成的SOAP不同,咱们先放下代码不说.但通常情况下,我们也不太需要去研究生成的SOAP是啥样 ...

随机推荐

  1. NFC应用实例

    package com.example.mynfcdemon; import android.app.Activity;import android.nfc.NfcAdapter;import and ...

  2. JVM学习总结五(番外)——JConsole

    之前本来打算结合自己写的小程序来介绍JConsole和VirtualVM的使用的,但是发现很难通过一个程序把所有的场景都体现出来,所以还是决定用书中的典型小例子来讲更加清晰. 一.JConsole的基 ...

  3. 论C# java的基本类型

    http://blog.csdn.net/com360/article/details/8201930 http://www.360doc.com/content/13/0818/13/8074294 ...

  4. node.js的学习

    require('http') 内置模块 server.js var http = require('http'); function start(){ server = http.createSer ...

  5. 跨域访问-需要设置HTTP响应标头设置

    跨域访问-需要设置HTTP响应标头设置 前提:服务端网站的配置(被请求的网站) 1.需要在IIS服务器站点的功能视图中设置HTTP响应标头: 2.双击“HTTP响应标头”进入设置界面 3.点击右侧添加 ...

  6. 30.DDR2问题2_local_init_done为什么没拉高?

    按照初始化时序,在200us时,mem_clk时钟稳定,开始初始化设置,设置完后,会产生一个初始化完成标志,local_init_done会拉高,没有拉高,可能有以下几个原因: 1.确认DDR2 IP ...

  7. disable_irq()与disable_irq_nosync()区别

    disable_irq关闭中断并等待中断处理完后返回, 而disable_irq_nosync立即返回.

  8. C Primer Plus学习笔记

    1.汇编语言是特地的Cpu设计所采用的一组内部指令的助记符,不同的Cpu类型使用不同的Cpu C给予你更多的自由,也让你承担更多的风险 自由的代价是永远的警惕 2.目标代码文件.可执行文件和库 3.可 ...

  9. 使用git客户端获取shiro

    1.进入下载的目标文件夹右键( Git Bash Here )

  10. 【收藏】Linux添加/删除用户和用户组

    1.建用户: adduser phpq                             //新建phpq用户 passwd phpq                               ...