在上篇文章中介紹了一下WCF中的客戶端到服務器端的單向通知,在實際應用中,還經常使用服務器端到客戶端的單向通知。例如,在聊天室里,我們需要把某人的發言廣播給每一個人。對於這種單向通知,我們一般稱為回調。本文就以一個簡單的聊天室為例,介紹一下如何實現回調。
interface IMessageCallback
{
[OperationContract(IsOneWay = true)]
void OnMessageAdded(string message, DateTime timestamp);
}
這個接口並不是服務,因此沒有ServiceContract標志,但其回調函數形式需要被公布,因此接口函數有OperationContract標志,另外,這個也是一個單向通知,因此有IsOneWay = true設置(注:這里的IsOneWay不是必須的,可以獲取回調函數的返回值的)。
[ServiceContract(CallbackContract = typeof(IMessageCallback))]
public interface IService1
{
[OperationContract]
void AddMessage(string message);
[OperationContract]
void Subscribe();
[OperationContract]
void Unsubscribe();
}
除了客戶端上報消息的接口外,這里也加了兩個接口函數Subscribe和Unsubscribe,用來注冊回調和刪除回調,實現了一個典型的觀察者模式。(PS:這兩個接口並不是必須的,主要是為了方便演示回調的實現過程)
接口聲明完成后,給了一個簡單的實現(這個只是示例代碼,線程安全,最佳實踐神馬的都沒有考慮,不要用於實際項目中)。
public class Service1 : IService1
{
static List<IMessageCallback> subscribers = new List<IMessageCallback>();
public void AddMessage(string message)
{
subscribers.ForEach(callback =>
{
if (((ICommunicationObject)callback).State == CommunicationState.Opened)
callback.OnMessageAdded(message, DateTime.Now);
else
subscribers.Remove(callback);
});
}
public void Subscribe()
{
var callback = OperationContext.Current.GetCallbackChannel<IMessageCallback>();
subscribers.Add(callback);
}
public void Unsubscribe()
{
var callback = OperationContext.Current.GetCallbackChannel<IMessageCallback>();
subscribers.Remove(callback);
}
}
從上述代碼中可以發現,回調的基本使用方式如下:
-
從 OperationContext.GetCallbackChannel中獲取回調通道 對象,該對象實現了回調接口和 ICommunicationObject 接口。
-
根據 IcommunicationObject.State 屬性查看其是否可用,也可以注冊 IcommunicationObject. Closed事件 主動響應客戶端退出。
- 調用回調對象的回調函數接口即可實現回調通知
編譯並運行上述服務后,會發現一個服務無法啟動的錯誤。
原因說得很清楚,要使用CallbackContract,則需要底層協議支持雙工(因為需要從服務器端主動通知客戶端)。但目前使用的BasicHttpBinding由於是基於Http協議,不支持雙工(另外一個基於Http協議的WSHttpBinding也不支持雙工),要改成支持雙工的協議。目前WCF中支持雙工的協議有如下幾種:
-
基於TCP協議的NetTcpBinding
-
基於管道的NetNamedPipeBinding
-
基於Http協議的wsDualHttpBinding,雖然Http協議本身不支持雙工,但是wsDualHttpBinding通過創建兩個不同方向的Http連接來實現了雙向傳輸。
為了簡單,我這里就改成了wsDualHttpBinding。
由於WCF測試客戶端不支持回調的測試,因此這里我們就需要手動實現客戶端代碼:
static void Main(string[] args)
{
var context = new System.ServiceModel.InstanceContext(new Callback());
var client = new WcfClient.Service.Service1Client(context);
client.Subscribe();
while (true)
{
var input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input))
break;
client.AddMessage(input);
}
client.Unsubscribe();
client.Close();
}
class Callback:IService1Callback
{
public void OnMessageAdded(string message, DateTime timestamp)
{
Console.WriteLine(">>> Receive Message {0} {1}", message, timestamp);
}
}
編寫這個代碼時就會發現:現在Client的構造函數需要傳入一個參數了,在這個參數中就可以指定實現了回調接口的對象,從而響應回調通知。運行客戶端多個實例,發送消息,每個消息都會通知到所有客戶端,與我們預期結果一致。