zoukankan      html  css  js  c++  java
  • 传说中的WCF(12):服务器回调有啥用

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

    假设现有服务器端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方法取出回调协定的实例,调用回调的方法,就会在客户端寻找回调接口的实现类并调用对应的成员。

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

    一:服务端实现

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

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

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

    复制代码
    namespace Server
    {
        interface ICallback
        {
            // 回调操作也必须One Way
            [OperationContract(IsOneWay = true)]
            void CallClient(int v);
        }
    }
    复制代码

    第三步,定义服务协定。

    复制代码
    namespace Server
    {
        [ServiceContract(
            Namespace = "MyNamespace",
            CallbackContract = typeof(ICallback), /* 标注回调协定 */
            SessionMode = SessionMode.Required /* 要求会话 */
            )]
        public interface IServer
        {
            // 会话从调用该操作启动
            [OperationContract(IsOneWay = true, /* 必须 */
                IsInitiating = true, /* 启动会话 */
                IsTerminating = false)]
            void CallServerOp();
    
            // 调用该操作后,会话结束
            [OperationContract(IsOneWay = true, /* 使用回调,必须为OneWay */
                IsTerminating = true, /* 该操作标识会话终止 */
                IsInitiating = false)]
            void End();
        }
    }
    复制代码

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

    复制代码
    namespace Server
    {
        public class MyServer : IServer, IDisposable
        {
            private ICallback icb;
            private Timer timer = null;//计时器,定时干活
            Random rand = null;//生成随机整数
    
            public void CallServerOp()
            {
                this.icb = OperationContext.Current.GetCallbackChannel<ICallback>();
                rand = new Random();
                // 生成随整数,并回调到客户端
                // 每隔3秒生成一次
                timer = new Timer((obj) => icb.CallClient(rand.Next()), null, 10, 3000);
            }
    
            public void Dispose()
            {
                timer.Dispose();
                Console.WriteLine("{0} - 服务实例已释放。", DateTime.Now.ToLongTimeString());
            }
    
    
            public void End()  //结束
            {
                Console.WriteLine("会话即将结束。");
            }
        }
    }
    复制代码

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

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

    复制代码
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <system.serviceModel>
        <client />
        <services>
          <service name="Server.MyServer"  behaviorConfiguration="Mybehavior" >
            <endpoint binding="netTcpBinding" bindingConfiguration="MynetTcpBinding" contract="Server.IServer" address="net.tcp://localhost:1211/rr">
              <identity>
                <dns value="localhost"/>
              </identity>
            </endpoint>
            <host>
              <baseAddresses>
                <add baseAddress="http://localhost:1378/services" />
              </baseAddresses>
            </host>
          </service>
        </services>
        <behaviors>
          <serviceBehaviors>
            <behavior name="Mybehavior" >
              <serviceMetadata httpGetEnabled="True"/>
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <bindings>
          <netTcpBinding>
            <binding name="MynetTcpBinding">
              <security mode="None" />
            </binding>
          </netTcpBinding>
        </bindings>
      </system.serviceModel>
    </configuration>
    复制代码

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

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

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

    添加Window服务类:

    复制代码
    namespace Server
    {
        partial class Host : ServiceBase
        {
            public Host()
            {
                InitializeComponent();
            }
    
            #region 启动入口
            /// <summary>
            /// 启动入口
            /// </summary>
            /// <param name="args"></param>
            static void Main(string[] args)
            {
                var host = new Host();
                try
                {
                    host.OnStart(args);
                    Console.WriteLine("服务已启动");
                    Console.ReadLine();
                }
                catch (Exception e)
                {
                }
            }
            #endregion
    
            protected override void OnStart(string[] args)
            {
                var service = new ServiceHost(typeof(Server.MyServer));
                service.Open();
            }
    
            protected override void OnStop()
            {
                // TODO: Add code here to perform any tear-down necessary to stop your service.
            }
        }
    }
    复制代码

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

    第五-六步功能类似于:

    复制代码
            static void Main(string[] args)
            {
                Console.Title = "WCF服务端";
                // 服务器基址
                Uri baseAddress = new Uri("http://localhost:1378/services");
                // 声明服务器主机
                using (ServiceHost host = new ServiceHost(typeof(MyService), baseAddress))
                {
                    // 添加绑定和终结点
                    // tcp绑定支持会话
                    NetTcpBinding binding = new NetTcpBinding();
                    binding.Security.Mode = SecurityMode.None;
                    host.AddServiceEndpoint(typeof(IService), binding, "net.tcp://localhost:1211/rr");
                    // 添加服务描述
                    host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
                    try
                    {
                        // 打开服务
                        host.Open();
                        Console.WriteLine("服务已启动。");
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                    Console.ReadKey();
                }
            }
    复制代码

    二:客户端实现

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

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

    复制代码
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
        <system.serviceModel>
            <bindings>
                <netTcpBinding>
                    <binding name="NetTcpBinding_IServer">
                        <security mode="None" />
                    </binding>
                </netTcpBinding>
            </bindings>
            <client>
                <endpoint address="net.tcp://localhost:1211/rr" binding="netTcpBinding"
                    bindingConfiguration="NetTcpBinding_IServer" contract="ServiceReference1.IServer"
                    name="NetTcpBinding_IServer">
                    <identity>
                        <dns value="localhost" />
                    </identity>
                </endpoint>
            </client>
        </system.serviceModel>
    </configuration>
    复制代码

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

    复制代码
    namespace Client
    {
        /// <summary>
        /// 实现回调接口
        /// </summary>
        class MyCallback : ServiceReference1.IServerCallback
        {
            // 因为该方法是由服务器调用的
            // 如果希望在客户端能即时作出响应
            // 应当使用事件
            public void CallClient(int v)
            {
                if (this.ValueCallbacked != null)
                {
                    this.ValueCallbacked(this, v);
                }
            }
    
            /// <summary>
            /// 回调引发该事件
            /// </summary>
            public delegate void EventHandler(Object sender, int e);
            public event EventHandler ValueCallbacked;
        }
    }
    复制代码

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

    第四步,设计窗口。

    复制代码
    namespace Client
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            ServiceReference1.ServerClient cl = null;
            MyCallback cb = null;
    
            public MainWindow()
            {
                InitializeComponent();
                cb = new MyCallback();
                cb.ValueCallbacked += new MyCallback.EventHandler(cb_ValueCallbacked);
            }
    
            void cb_ValueCallbacked(object sender, int e)
            {
                this.label2.Content = e.ToString();
            }
    
            private void button1_Click(object sender, RoutedEventArgs e)
            {
                cl = new ServiceReference1.ServerClient(new System.ServiceModel.InstanceContext(cb));
                cl.CallServerOp();
                button1.IsEnabled = false;
                button2.IsEnabled = true;
            }
    
            private void button2_Click(object sender, RoutedEventArgs e)
            {
                cl.End();
                button1.IsEnabled = true;
                button2.IsEnabled = false;
            }
        }
    }
    复制代码

    现在来测试一下吧。

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

  • 相关阅读:
    Longest Palindromic Substring
    PayPal MLSE job description
    Continuous Median
    Remove Duplicates From Linked List
    Valid IP Address
    Longest substring without duplication
    Largest range
    Subarray sort
    Multi String Search
    Suffix Trie Construction
  • 原文地址:https://www.cnblogs.com/ywsoftware/p/3663879.html
Copyright © 2011-2022 走看看