首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

WCF 進階之旅(二):擴展 Channel Layer —— 打造自訂 Channel

     編輯:關於.NET

在上一篇文章中,我們透過 BasicHttpBinding 直接實作 Client 與 Server 通訊的範例,初步認識了 WCF Channel Layer 的核心概念:Channel、Output/Input/Request/Reply/Duplex Channel、Channel Shape、Channel Manager、Channel Factory、Channel Listener、Binding Element 等。這些元件構成了 WCF 通訊管道的基礎,也讓我們能輕易地對 Channel Layer 進行擴展。

為何需要擴展 Channel Layer?

當現有的 Binding 或 Channel 無法滿足特殊需求時(例如 WCF 內建 Channel 不支援 Message Body 壓縮),就必須建立自訂 Channel 並注入 Channel Stack。但光建立 Custom Channel 是不夠的,因為 Runtime 時 Channel 是由 Channel Manager 建立,所以還需實作對應的 Channel Factory(發送端擴展)或 Channel Listener(接收端擴展),而它們最終又由 Binding Element 建立。整條鏈路為:Binding Element → Channel Factory & Channel Listener → Channel。

核心基礎:ICommunicationObject 與 CommunicationObject

WCF Channel Layer 的大多數物件(Channel、Factory、Listener)本質上都是通訊物件,擁有狀態機制(Creating、Created、Opening、Opened、Closing、Closed…)。為了統一管理,WCF 定義了 ICommunicationObject 介面,包含:

  • Property:State(回傳 CommunicationState 列舉)
  • Methods:同步/非同步 Open/Close/Abort
  • Events:Opening/Opened/Closing/Closed/Faulted

抽象類別 CommunicationObject 實作了此介面,統一處理狀態轉換與事件觸發。自訂類別繼承它時,強烈建議 override 方法先呼叫 base 方法,讓基底類別處理狀態流轉。

Channel 與 Channel Shape

依據訊息交換模式(MEP)不同,雙方 Channel 角色各異,這種結構差異稱為 Channel Shape,共四種:

ShapeMEPSender ChannelReceiver Channel
DatagramOne-wayIOutputChannelIInputChannel
Request/ReplyRequest/ReplyIRequestChannelIReplyChannel
DuplexDuplexIDuplexChannelIDuplexChannel
P2PDuplexIDuplexChannelIDuplexChannel

IDuplexChannel 本質上繼承 IInputChannel + IOutputChannel。

實作自訂 Channel:以 Request/Reply 為例

本範例建立兩個 Channel:

  • MyRequestChannel:實作 IRequestChannel,用於發送端
  • MyReplyChannel:實作 IReplyChannel,用於接收端

為了觀察呼叫順序,每個方法僅輸出方法名稱。關鍵在於 InnerChannel 欄位:Channel 位於 Stack 中某層,執行完自身邏輯後必須將訊息傳遞給下一層 Channel。

public class MyRequestChannel : ChannelBase, IRequestChannel {
    private IRequestChannel InnerChannel { get; set; }
    public MyRequestChannel(ChannelManagerBase mgr, IRequestChannel inner) : base(mgr) {
        this.InnerChannel = inner;
    }
    // 所有 IRequestChannel 方法:列印名稱 → 呼叫 InnerChannel 對應方法
    // 所有 ChannelBase 虛擬方法:列印名稱 → 呼叫 InnerChannel 對應方法
}

MyReplyChannel 結構完全相同,實作 IReplyChannel 的接收相關方法。

建立 Channel Factory 與 Channel Listener

Channel 由 Channel Manager 建立:發送端為 Channel Factory,接收端為 Channel Listener。兩者同樣形成 Stack。

MyChannelFactory<TChannel>

public class MyChannelFactory<TChannel> : ChannelFactoryBase<TChannel> {
    private IChannelFactory<TChannel> InnerChannelFactory { get; set; }
    public MyChannelFactory(BindingContext context) {
        this.InnerChannelFactory = context.BuildInnerChannelFactory<TChannel>();
    }
    protected override TChannel OnCreateChannel(EndpointAddress addr, Uri via) {
        Console.WriteLine("MyChannelFactory.OnCreateChannel()");
        TChannel inner = this.InnerChannelFactory.CreateChannel(addr, via);
        return (TChannel)(object)new MyRequestChannel(this, inner as IRequestChannel);
    }
    // Open/Close/Abort 虛擬方法同理:列印 → 轉呼叫 InnerChannelFactory
}

注意:InnerChannelFactory 透過 BindingContext.BuildInnerChannelFactory() 取得,而非建構式直接注入。

MyChannelListener<TChannel>

public class MyChannelListener<TChannel> : ChannelListenerBase<TChannel> where TChannel : class, IChannel {
    private IChannelListener<TChannel> InnerChannelListener { get; set; }
    public MyChannelListener(BindingContext context) {
        this.InnerChannelListener = context.BuildInnerChannelListener<TChannel>();
    }
    protected override TChannel OnAcceptChannel(TimeSpan timeout) {
        Console.WriteLine("MyChannelListener.OnAcceptChannel()");
        TChannel inner = this.InnerChannelListener.AcceptChannel(timeout);
        return new MyReplyChannel(this, inner as IReplyChannel) as TChannel;
    }
    // 其他 Accept/WaitForChannel 及 Open/Close/Abort 同理
}

建立自訂 Binding Element

Binding Element 是將 Factory/Listener 掛入 Binding 的關鍵:

public class MyBindingElement : BindingElement {
    public override BindingElement Clone() => new MyBindingElement();
    public override T GetProperty<T>(BindingContext context) => context.GetInnerProperty<T>();
    public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context) {
        Console.WriteLine("MyBindingElement.BuildChannelFactory()");
        return new MyChannelFactory<TChannel>(context) as IChannelFactory<TChannel>;
    }
    public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context) {
        Console.WriteLine("MyBindingElement.BuildChannelListener()");
        return new MyChannelListener<TChannel>(context) as IChannelListener<TChannel>;
    }
}

建立自訂 Binding

將 Binding Element 組裝進 BindingElementCollection,順序為:Encoding → Custom Element → Transport。

public class MyBinding : Binding {
    public override BindingElementCollection CreateBindingElements() {
        var elements = new BindingElementCollection();
        elements.Add(new TextMessageEncodingBindingElement());
        elements.Add(new MyBindingElement());
        elements.Add(new HttpTransportBindingElement());
        return elements.Clone();
    }
    public override string Scheme => "http";
}

實測:使用 Custom Binding 進行通訊

Server 端

var binding = new MyBinding();
var listener = binding.BuildChannelListener<IReplyChannel>(new Uri("https://127.0.0.1:8888/messagingviabinding"));
listener.Open();
while (true) {
    var channel = listener.AcceptChannel(TimeSpan.MaxValue);
    channel.Open();
    var ctx = channel.ReceiveRequest(TimeSpan.MaxValue);
    Console.WriteLine("收到請求:\n{0}", ctx.RequestMessage);
    var reply = Message.CreateMessage(MessageVersion.Soap12WSAddressing10, "https://artech.messagingviabinding", "手動建立的回覆訊息");
    ctx.Reply(reply);
    channel.Close();
}

Client 端

var binding = new MyBinding();
var factory = binding.BuildChannelFactory<IRequestChannel>();
factory.Open();
var channel = factory.CreateChannel(new EndpointAddress("https://127.0.0.1:8888/messagingviabinding"));
channel.Open();
var req = Message.CreateMessage(MessageVersion.Soap12WSAddressing10, "https://artech.messagingviabinding", "測試請求訊息");
var reply = channel.Request(req);
Console.WriteLine("收到回覆:\n{0}", reply);
channel.Close(); factory.Close();

執行結果會在 Console 依序印出 Channel、Factory、Listener、Binding Element 的各方法呼叫順序,清晰呈現整個 Stack 的運作流程。

Server 執行輸出 Client 執行輸出

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved