傳說中的WCF(12):服務器回調有啥用

你說,服務器端回調有啥用呢?這樣問,估計很差回答,是吧。不急,先討論一個情景。服務器

假設現有服務器端S,客戶端A開始鏈接S並調用相關操做,其中有一個操做,在功能上有些特殊,調用後沒法即時回覆,由於在服務器上要做一些後續,而這些處理也許會消耗必定時間,好比:app

向服務器上傳了一個文件,可是,爲了節約空間或出於其餘目的,服務器要對剛上傳的文件進行處理(壓縮或者多媒體文件轉碼),這些操做沒法立刻向客戶端回覆,而客戶端也不可能就停在這裏一直在等。咱們但願,在客戶端上傳文件後立刻返回,而服務器對文件處理完成後再通知一下客戶端。dom

這樣就引出一個東東——回調,E文叫Call Back。我估計用E文表述可能更好理解,Call back就是相對於Call to而言的,即調用的方向與Call to相反。tcp

是啊,有必要解釋一下,什麼叫回調。我講一個故事吧。ide

有一天,腦殘去書店買書,以前他聽別人說有一本書叫《吹牛沉思錄》很好看,因而腦殘也想買一本。但是,當他到書店後,東找西尋了一番,硬是沒看見那本書的影子。函數

因而,他跑到櫃檯問工做人員:「我想找《吹牛沉思錄》,沒找到。」測試

工做人員立刻啓動書店的信息管理系統,但能夠因爲該系統品德不太好,竟然用了35秒才啓動,而後,工做人員在上面查了一下,回過頭說:「抱歉,這本書太搶手了,賣完了,須要拿貨。」ui

腦殘追問:「那要啥時候有貨?」this

工做人員說:「大概兩三天後吧,這樣吧,你留個聯繫方式,等到貨到了我再聯繫你。」spa

……

對的,這就是回調的故事。

腦殘(調用方)不知道書店何時有貨(不清楚調用的操做何時返回),但他總不能天天都跑去書店看看,這樣太不滑算(消耗資源),因而,書店(被調用方)建議,留下聯繫方式(只保留內存中函數指針的地址,即回調地址),只要貨到了就通知腦殘(反調用)。

回調比較典型的一種就是事件,事件驅動模型之前是在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();
            }
        }
View Code

二:客戶端實現

第一步,新建一個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

 

相關文章
相關標籤/搜索