Duplex 實現客戶端與服務端雙向通訊。開發簡易聊天室時,常遇到「服務端如何向特定客戶端發送訊息」的問題。常見解法是用 Singleton 保存客戶端 Session,或維護 Dictionary 對應客戶資訊。但研讀 WCF 官方範例後發現,許多開發者忽略了 WCF 核心價值——「服務」導向設計。
圖 1:正確的用例圖——服務端提供聊天服務,不參與聊天內容
圖 2:錯誤的用例圖——服務端直接參與聊天互動
關鍵差異:服務端應專注於提供穩定服務,而非成為業務參與者。這是 SOA 架構思維的體現——明確服務邊界、定義訊息格式、分清角色職責。
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IChatCallback))]
interface IChat {
[OperationContract(IsOneWay = false, IsInitiating = true, IsTerminating = false)]
string[] Join(string name);
[OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
void Say(string msg);
[OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
void Whisper(string to, string msg);
[OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = true)]
void Leave();
}
interface IChatCallback {
[OperationContract(IsOneWay = true)]
void Receive(string senderName, string message);
[OperationContract(IsOneWay = true)]
void ReceiveWhisper(string senderName, string message);
[OperationContract(IsOneWay = true)]
void UserEnter(string name);
[OperationContract(IsOneWay = true)]
void UserLeave(string name);
}
服務類別 ChatService 定義委派與事件實現訊息廣播:
private void BroadcastMessage(ChatEventArgs e) {
ChatEventHandler temp = ChatEvent;
if (temp != null) {
foreach (ChatEventHandler handler in temp.GetInvocationList()) {
handler.BeginInvoke(this, e, new AsyncCallback(EndAsync), null);
}
}
}
myEventHandler = new ChatEventHandler(MyEventHandler);
callback = OperationContext.Current.GetCallbackChannel<IChatCallback>();
ChatEventArgs e = new ChatEventArgs { msgType = MessageType.UserEnter, name = name };
BroadcastMessage(e); // 先廣播「使用者進入」
ChatEvent += myEventHandler; // 再訂閱事件,避免自己收到自己的進入通知
private void MyEventHandler(object sender, ChatEventArgs e) {
try {
switch (e.msgType) {
case MessageType.Receive:
callback.Receive(e.name, e.message); break;
case MessageType.ReceiveWhisper:
callback.ReceiveWhisper(e.name, e.message); break;
case MessageType.UserEnter:
callback.UserEnter(e.name); break;
case MessageType.UserLeave:
callback.UserLeave(e.name); break;
}
} catch { Leave(); }
}
public void Whisper(string to, string msg) {
ChatEventArgs e = new ChatEventArgs {
msgType = MessageType.ReceiveWhisper,
name = this.name,
message = msg
};
try {
ChatEventHandler chatterTo;
lock (syncObj) {
chatterTo = chatters[to]; // 從字典查找目標委派
}
chatterTo.BeginInvoke(this, e, new AsyncCallback(EndAsync), null);
} catch (KeyNotFoundException) { /* 目標不存在 */ }
}
關鍵:維護 Dictionary<string, ChatEventHandler> chatters 對應使用者名稱與委派實例,實現精準單點傳遞。
proxy = new ChatProxy(site);
IAsyncResult iar = proxy.BeginJoin(myNick, new AsyncCallback(OnEndJoin), null);
Uri uri = new Uri(ConfigurationManager.AppSettings["addr"]);
ServiceHost host = new ServiceHost(typeof(NikeSoftChat.ChatService), uri);
host.Open();
Console.WriteLine($"Chat service listen on endpoint {uri}");
Console.WriteLine("Press ENTER to stop chat service...");
Console.ReadLine();
host.Abort(); host.Close();
服務端僅提供穩定服務,聊天參與者皆為客戶端使用者。
聊天室雖小,卻完美展示 SOA 核心:服務定義邊界、契約優先、角色職責分離。正如學習 OOP 需建立物件思維,採用 WCF 必須建立服務導向思維。