Silverlight與WCF通信(三) :Silverlight與IIS宿主的WCF間的雙工通信


     最近項目比較忙,這第三篇更新的比較晚,今日補上。今天我們來演示一下Silverlight與WCF的雙工通信,所謂的雙工顧名思義就是雙方之間均可以向對方發送消息,但是WCF中的雙工並不和傳統的基於TCP協議的雙工通信是一樣的,WCF的雙工是客戶端調用WCF服務的時候,附加一個回調對象,服務端執行服務的時候,通過這個回調對象回調客戶端的操作,說白了就是 你調用我一下,我回調你一下,來實現的雙工通信。

     WCF的雙工通信我們這次演示的是訂閱-發布模式,大家也可以這么理解,多個客戶端調用訂閱服務后,服務器端推送數據給客戶端,我們來設想這樣一個案例場景:“服務器端監視一些設備的運行狀態,當設備有故障之后,客戶端要將告警的設備顯示出來”,實現起來並不難,客戶端通過定時請求服務器端的告警數據就可以實現,但是這種定時刷新服務器端的做法對服務器端壓力是很大的,不是一個明智選擇,下面來演示這個Demo,實現服務器端推送告警數據給客戶端。

項目結構

    項目結構和上兩篇是一樣的,簡單說明一下:

    LxContract程序集:WCF 的數據契約和操作契約    

    LxService  程序集:WCF操作契約的實現

    LxWCF_web:發布WCF的網站

    SilverlightDuplex:Silverlight應用程序,也就是該客戶端調用WCF服務

代碼實現

類庫LxContract:(包括數據契約Alarm.cs;操作契約IAlarmSrv.cs;回調契約IAlarmCallBack.cs)

Alarm.cs 代碼
using System;
using System.Runtime.Serialization;

namespace LxContract
{
    [DataContract]
    public class Alarm
    {
        /// <summary>
        /// 設備的編號
        /// </summary>
        [DataMember]
        public string DeviceNo { get; set; }

        /// <summary>
        ///告警時間
        /// </summary>
        [DataMember]
        public DateTime AlarmTime { get; set; }
    }
}
IAlarmCallBack.cs 代碼
using System.ServiceModel;

namespace LxContract
{
    public interface IAlarmCallBack
    {
        [OperationContract(IsOneWay = true)]
        void ReceiveAlarmData(Alarm alarm);
    }
}
IAlarmSrv.cs 代碼
using System.ServiceModel;
namespace LxContract
{
    [ServiceContract(CallbackContract = typeof(IAlarmCallBack))]
    public interface IAlarmSrv
    {
        [OperationContract]
        string RequestAlarmData();
    }
}

[ServiceContract(CallbackContract = typeof(IAlarmCallBack))]  服務端對客戶端進行回調其實就是調用寄宿在客戶端的代理中的ReceiveAlarmData方法,因此我們要利用 SeviceContract中的CallBackContract屬性來指定是哪個回調契約。

類庫LxService:(該類庫僅包括AlarmService.cs文件,AlarmService用來實現IAlarmSrv契約的具體操作)

AlarmService.cs
using System;
using LxContract;
using System.ServiceModel;

namespace LxService
{
    public class AlarmService : IAlarmSrv
    {
        System.Timers.Timer timer;
        IAlarmCallBack client;

        public string RequestAlarmData()
        {
            string ret = string.Format("當前時間:{0},服務器將每隔3秒鍾返回一條告警數據", DateTime.Now);
            //這里獲取當前的客戶端
            //可以定義一個List 來保存所有的客戶端
            client = OperationContext.Current.GetCallbackChannel<IAlarmCallBack>();
            timer = new System.Timers.Timer();
            timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
            timer.Interval = 3000;
            timer.Start();
            return ret;
        }

        void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            //這里可修改成獲取真實設備數據的方法
            //本實例定時返回一個設備
            Alarm alarm = new Alarm()
            {
                DeviceNo = Guid.NewGuid().ToString(),
                AlarmTime = DateTime.Now
            };
            client.ReceiveAlarmData(alarm);
        }
    }
}

站點LxWcf_Web :

 同樣是一個空的Asp.net 網站,需要添加引用LxContranc和LxService兩個類庫,並添加一個wcf服務文件命名為DuplexSrv.svc,用於進行服務的發布,對其鼠標右擊選擇查看標記,將代碼修改為:

<%@ ServiceHost Language="C#" Debug="true" Service="LxService.AlarmService" %>

由於我們還采用IIS宿主改網站,信道依然采用netTcp方式,因此對Web.config文件中配置如下:

Web.config 配置
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>

  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="LxBehavior">
          <serviceMetadata httpGetEnabled="false" />
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <netTcpBinding>
        <binding name="LxBinding">
          <security mode="None" />
        </binding>
      </netTcpBinding>
    </bindings>
    <services>
      <service name="LxService.AlarmService" behaviorConfiguration="LxBehavior">
        <endpoint address="" binding="netTcpBinding" bindingConfiguration="LxBinding" contract="LxContract.IAlarmSrv" />
        <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
      </service>
    </services>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
</configuration>

本次我們還利用第二篇介紹的IIS宿主netTcp綁定方式WCF的方法部署該網站,這里就不再進行貼圖和說明了,具體參見 這里如何部署服務

SilverlightDuplex 客戶端

由於我們已經成功在IIS中配置好了LxWcf_Web 這個發布WCF的站點,我們可以在Silverlight項目中添加服務引用,就可以找到此服務,命名為Wcf.Duplex。

MainPage.xaml 布局代碼:
<UserControl 
    xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"  x:Class="SilverlightDuplex.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="480" d:DesignWidth="640">

    <Grid x:Name="LayoutRoot" Background="Bisque" Height="300" Width="500">
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="22"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Button x:Name="btnOk" Height="24" Width="120" Content="點擊獲取告警數據" />
        <TextBlock x:Name="tbInfo" Grid.Row="1" Foreground="Red" 
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"/>
        <sdk:DataGrid x:Name="dgAlarm" Grid.Row="2" AutoGenerateColumns="False">
            <sdk:DataGrid.Columns>
                <sdk:DataGridTextColumn Header="設備編號" Width="0.5*" Binding="{Binding DeviceNo}"/>
                <sdk:DataGridTextColumn Header="告警時間" Width="0.5*" Binding="{Binding AlarmTime,StringFormat=yyyy-MM-dd HH:mm:ss}"/>
            </sdk:DataGrid.Columns>
        </sdk:DataGrid>
    </Grid>
</UserControl>
MainPage.xaml.cs 代碼
using System;
using System.Windows;
using System.Windows.Controls;
using System.Collections.ObjectModel;
using SilverlightDuplex.Wcf.Duplex;

namespace SilverlightDuplex
{
    public partial class MainPage : UserControl
    {
        ObservableCollection<Alarm> listAlarm = null;

        public MainPage()
        {
            InitializeComponent();
            listAlarm = new ObservableCollection<Alarm>();
            this.btnOk.Click += new RoutedEventHandler(btnOk_Click);
            this.Loaded += new RoutedEventHandler(MainPage_Loaded);

        }

        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            this.dgAlarm.ItemsSource = listAlarm;
        }

        void btnOk_Click(object sender, RoutedEventArgs e)
        {
            AlarmSrvClient proxyclient = new AlarmSrvClient();
            proxyclient.RequestAlarmDataCompleted += new EventHandler<RequestAlarmDataCompletedEventArgs>(proxyclient_RequestAlarmDataCompleted);
            proxyclient.ReceiveAlarmDataReceived += new EventHandler<ReceiveAlarmDataReceivedEventArgs>(proxyclient_ReceiveAlarmDataReceived);
            proxyclient.RequestAlarmDataAsync();
        }

        //服務器端回調獲取告警完成
        void proxyclient_ReceiveAlarmDataReceived(object sender, ReceiveAlarmDataReceivedEventArgs e)
        {
            if (e.Error == null)
            {
                listAlarm.Add(e.alarm);
            }
        }

        //請求獲取告警數據完成
        void proxyclient_RequestAlarmDataCompleted(object sender, RequestAlarmDataCompletedEventArgs e)
        {
            if (e.Error == null)
            {
                this.tbInfo.Text = e.Result.ToString();
            }
        }
    }
}

 

至此代碼編寫完畢,我們來運行一下Silverlight客戶端,右擊該項目--調試--啟動新實例,Silverlight程序運行起來之后,點擊獲取告警數據按鈕,我們就會發現服務器端每隔3秒中給該客戶端下發一條告警數據。

補充一點:回調契約中的[OperationContract(IsOneWay = true)] 有什么用呢? 這是防止調用wcf產生回調死鎖異常的一種解決方法,還有一種解決方法就是對服務的行為進行設定,可以為類AlarmService增加

[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Reentrant)]來解決死鎖的問題。不過在Silverlight中貌似只能用[OperationContract(IsOneWay = true)],否則在添加引用后,生成的時候就會發生錯誤。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM