最近項目比較忙,這第三篇更新的比較晚,今日補上。今天我們來演示一下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)

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; } } }

using System.ServiceModel; namespace LxContract { public interface IAlarmCallBack { [OperationContract(IsOneWay = true)] void ReceiveAlarmData(Alarm alarm); } }

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契約的具體操作)

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文件中配置如下:

<?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。

<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>

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)],否則在添加引用后,生成的時候就會發生錯誤。