WCF 提供兩種基本的非同步通訊模式:OneWay (單向/發後不理) 與 Duplex (雙向/回呼)。前者適合「通知類」作業 (如記錄日誌、發送郵件),後者適合「伺服器主動推播」情境 (如即時訊息、股價行情、進度通知)。本篇完整示範兩種模式的契約定義、繫結選擇、宿主設定與用戶端呼叫方式。
| 特性 | OneWay (IsOneWay=true) | Duplex (CallbackContract) |
|---|---|---|
| 方向 | 用戶端 → 服務 (單向) | 用戶端 ↔ 服務 (雙向) |
| 回傳值 | 不可有 (void) | 可有,但建議搭配 IsOneWay=true |
| 例外處理 | 用戶端收不到例外 (需靠 IErrorHandler) | 雙向皆可拋回 FaultException |
| 支援繫結 | 所有繫結 | 需支援雙工:netTcpBinding, wsDualHttpBinding, netNamedPipeBinding, netMsmqBinding |
| 典型用途 | 日誌、通知、非關鍵作業 | 聊天室、即時行情、長時間作業進度回報 |
using System;
using System.ServiceModel;
namespace WCF.ServiceLib.Message
{
[ServiceContract]
public interface IOneWay
{
/// 同步呼叫:用戶端會等待並接收例外
[OperationContract]
void WithoutOneWay();
/// 非同步呼叫:用戶端發送後立即返回,不等待服務端處理完成
[OperationContract(IsOneWay = true)]
void WithOneWay();
}
public class OneWay : IOneWay
{
public void WithoutOneWay()
{
throw new Exception("同步模式拋出例外,用戶端可捕獲");
}
public void WithOneWay()
{
throw new Exception("OneWay 模式拋出例外,用戶端不會收到,服務端需自行記錄");
}
}
}
<service name="WCF.ServiceLib.Message.OneWay" behaviorConfiguration="MessageBehavior">
<endpoint address="" binding="basicHttpBinding" contract="WCF.ServiceLib.Message.IOneWay" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="https://localhost:12345/Message/OneWay/" />
</baseAddresses>
</host>
</service>
public class OneWayClient
{
// 同步:會拋出例外,MessageBox 顯示錯誤
public void CallWithoutOneWay()
{
var proxy = new MessageSvc.OneWay.OneWayClient();
try { proxy.WithoutOneWay(); proxy.Close(); }
catch (Exception ex) { MessageBox.Show(ex.ToString()); }
}
// OneWay:不拋出例外,MessageBox 不會彈出 (若服務端拋出例外,通道會進入 Faulted 狀態)
public void CallWithOneWay()
{
var proxy = new MessageSvc.OneWay.OneWayClient();
try { proxy.WithOneWay(); proxy.Close(); }
catch (Exception ex) { MessageBox.Show(ex.ToString()); } // 這裡捕獲不到服務端例外
}
}
重要提醒:OneWay 作業若在服務端拋出未處理例外,通道會進入Faulted狀態,後續呼叫會失敗。生產環境務必搭配IErrorHandler統一記錄錯誤並避免通道錯誤。
using System;
using System.ServiceModel;
namespace WCF.ServiceLib.Message
{
/// 用戶端需實作的回呼介面
public interface IDuplexCallback
{
[OperationContract(IsOneWay = true)]
void HelloDuplexCallback(string name);
}
/// 服務介面,指定 CallbackContract
[ServiceContract(CallbackContract = typeof(IDuplexCallback))]
public interface IDuplex
{
[OperationContract(IsOneWay = true)]
void HelloDuplex(string name);
}
public class Duplex : IDuplex
{
public void HelloDuplex(string name)
{
// 取得用戶端回呼通道
var callback = OperationContext.Current.GetCallbackChannel();
// 主動呼叫用戶端方法
callback.HelloDuplexCallback(name);
}
}
}
<service name="WCF.ServiceLib.Message.Duplex" behaviorConfiguration="MessageBehavior">
<!-- netTcpBinding 原生支援雙工,單一連線即可雙向 -->
<endpoint address="Message/Duplex"
binding="netTcpBinding"
contract="WCF.ServiceLib.Message.IDuplex" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="https://localhost:12345/Message/Duplex" />
<add baseAddress="net.tcp://localhost:54321/" />
</baseAddresses>
</host>
</service>
繫結選擇說明:wsDualHttpBinding會建立兩個 HTTP 連線 (一個請求、一個回呼),穿透防火牆較易但效能較差;netTcpBinding單一 TCP 連線雙向通訊,效能最佳,適合內網環境。
using System;
using System.ServiceModel;
using System.Windows.Forms;
namespace Client2.Message
{
/// 實作回呼介面
public class CallbackHandler : MessageSvc.Duplex.IDuplexCallback
{
public void HelloDuplexCallback(string name)
{
MessageBox.Show($"伺服器回呼:Hello {name}");
}
}
public class DuplexClient
{
public void CallDuplex(string name)
{
// InstanceContext 綁定回呼實例
var ctx = new InstanceContext(new CallbackHandler());
// DuplexClient 建構子需傳入 InstanceContext
var proxy = new MessageSvc.Duplex.DuplexClient(ctx);
proxy.HelloDuplex(name);
// 注意:OneWay 作業不需等待回傳,但通道需保持開啟以接收回呼
}
}
}
<client>
<endpoint address="net.tcp://localhost:54321/Message/Duplex"
binding="netTcpBinding"
contract="MessageSvc.Duplex.IDuplex" />
</client>
InstanceContext 存活,若物件被 GC 回收,回呼將失效。ICommunicationObject.Closed/Faulted 事件偵測用戶端斷線並清理資源。IDuplexCallback 時加入 [CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)] 允許重入,避免回呼中再呼叫服務端造成死結。netMsmqBinding 實現佇列傳輸,保証訊息不遺失。IErrorHandler 記錄錯誤並回傳自訂 Fault,防止通道 Faulted。[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.PerCall)]
public class Duplex : IDuplex { ... }
高併發情境下建議 PerCall + Multiple,並確保回呼實作為線程安全。