746
技術社區[雲棲]
WCF後續之旅(2): 如何對Channel Layer進行擴展——創建自定義Channel
中,我們通過一個直接借助BasicHttpBinding對象實現Client和Server端進行通信的例子,對WCF channel layer進行了一個大致上的介紹。由此引出了一些列通信相關的概念和對象,比如Channel,Output channel, Input channel,Request channel, Reply Channel,Duplex channel, Channel Shape,Channel manager,Channel factory, Channel listener, Binding element 等。通過這些元素,我們很容易地實現對WCF channel layer進行擴展。
對channel layer進行擴展一般適用於當你的需求通過現有的Binding,或者channel不能實現,而需要自定義一些channel來實現你所需的功能。不如現在的WCF係統定義的Channel中沒有實現對Message body的壓縮功能。你可以就需要將此功能定義到一個custom channel中,然後將其注入到channel stack中。一般來說,僅僅創建custom channel是不夠的,因為在runtime, channel是通過Channel manager進行創建的,所以你需要創建對應的Channel factory(如何對發送方進行擴展)或者Channel listener(如果對接受方進行擴展)。而Channel factory和channel listener最終又是通過Binding element進行創建的,所以你還需要創建相應的Binding element。(Binding element=〉Channel factory&Channel listener=>Channel)
一、ICommunicationObject 和 CommunicationObject
我們知道WCF channel layer的絕大部分對象,比如Channel,Channel factory,Channel listener,從功能上講都是用於通信(Communication)的對象,對傳統的communication object,比如socket,他們往往都具有通過狀態和狀態轉化規則(狀態機:State machine)。這些狀態包括等等。為了統一管理這些狀態和狀態之間的轉化,WCF定義個一個特殊的Interface:
1: public interface ICommunicationObject
2: {
3: // Events
4: event EventHandler Closed;
5: event EventHandler Closing;
6: event EventHandler Faulted;
7: event EventHandler Opened;
8: event EventHandler Opening;
9:
10: // Methods
11: void Abort();
12: IAsyncResult BeginClose(AsyncCallback callback, object state);
13: IAsyncResult BeginClose(TimeSpan timeout, AsyncCallback callback, object state);
14: IAsyncResult BeginOpen(AsyncCallback callback, object state);
15: IAsyncResult BeginOpen(TimeSpan timeout, AsyncCallback callback, object state);
16: void Close();
17: void Close(TimeSpan timeout);
18: void EndClose(IAsyncResult result);
19: void EndOpen(IAsyncResult result);
20: void Open();
21: void Open(TimeSpan timeout);
22:
23: // Properties
24: CommunicationState State { get; }
25: }
- 屬性:State, 得到當前的狀態,返回值是一個CommunicationState 枚舉。
- 方法:同步、異步Open和Close方法。
- 事件:通過注冊這些狀態相關的Event,當時對象轉化到對應的狀態時執行相應操作。
WCF定義了一個abstract class: 直接實現了該Interface。CommunicationObject的實現統一的State machine。WCF channel layer的很多的class都直接或者間接的繼承了這個class。你也可以讓你的class繼承該class。當你讓你自己的class繼承CommunicationObject的時候,在override 掉base相應的method的時候,強烈建議你先調用base對應的方法,CommunicationObject會幫你進行相應的State轉換和觸發相應的事件。
二、Channel 和Channel Shape
中,我們討論過了。在不同的消息交換模式(MEP)中,發送方和接受方的Channel扮演的角色是不相同的。我們並把這種不同MEP中消息交互雙方Channel的結構差異表述為。我們有四種不同的Channel shape:Datagram、Request/reply、DuplexP2P。不同Channel shape中Channel的結構性差性通過實現不同的Channel interface來體現。
對於 channel shape,采用了One-way的MEP。發送方的channel 必須實現IOutputChannel interface。該Interface的方法成員主要集中在用於發送message的方法(同步/異步):
1: public interface IOutputChannel : IChannel, ICommunicationObject
2: {
3: // Methods
4: IAsyncResult BeginSend(Message message, AsyncCallback callback, object state);
5: IAsyncResult BeginSend(Message message, TimeSpan timeout, AsyncCallback callback, object state);
6: void EndSend(IAsyncResult result);
7: void Send(Message message);
8: void Send(Message message, TimeSpan timeout);
9:
10: // Properties
11: EndpointAddress RemoteAddress { get; }
12: Uri Via { get; }
13: }
與之相應是IInputChannel inteface,該Interface用於Datagram channel shape中接收方的channel定義。其主要方法成員主要集中在用於接收Message的Receive方法(同步/異步):
1: public interface IInputChannel : IChannel, ICommunicationObject
2: {
3: // Methods
4: IAsyncResult BeginReceive(AsyncCallback callback, object state);
5: IAsyncResult BeginReceive(TimeSpan timeout, AsyncCallback callback, object state);
6: IAsyncResult BeginTryReceive(TimeSpan timeout, AsyncCallback callback, object state);
7: IAsyncResult BeginWaitForMessage(TimeSpan timeout, AsyncCallback callback, object state);
8: Message EndReceive(IAsyncResult result);
9: bool EndTryReceive(IAsyncResult result, out Message message);
10: bool EndWaitForMessage(IAsyncResult result);
11: Message Receive();
12: Message Receive(TimeSpan timeout);
13: bool TryReceive(TimeSpan timeout, out Message message);
14: bool WaitForMessage(TimeSpan timeout);
15:
16: // Properties
17: EndpointAddress LocalAddress { get; }
18: }
注:無論對於同步或者異步方法,一般由兩個重載,一個接收一個 作為參數,表是Send或者Receive允許的時間範圍。而另一個沒有該參數的方式,並不是建議你使用一個無限的TimeSpan,而是使用一個可配置的默認時間段(實際上是對象對應的屬性)
不同於Datagram channel shape, channel shape下交互雙方的Channel具有不同的行為。發送方的Channel實現。該interface的方麵成員主要集中在一些用於向接收方進行請求的方法(同步/異步):
1: public interface IRequestChannel : IChannel, ICommunicationObject
2: {
3: // Methods
4: IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state);
5: IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state);
6: Message EndRequest(IAsyncResult result);
7: Message Request(Message message);
8: Message Request(Message message, TimeSpan timeout);
9:
10: // Properties
11: EndpointAddress RemoteAddress { get; }
12: Uri Via { get; }
13: }
同理,對於接收方的IReplyChannel則主要定義了一些用於的方法:
1: public interface IReplyChannel : IChannel, ICommunicationObject
2: {
3: // Methods
4: IAsyncResult BeginReceiveRequest(AsyncCallback callback, object state);
5: IAsyncResult BeginReceiveRequest(TimeSpan timeout, AsyncCallback callback, object state);
6: IAsyncResult BeginTryReceiveRequest(TimeSpan timeout, AsyncCallback callback, object state);
7: IAsyncResult BeginWaitForRequest(TimeSpan timeout, AsyncCallback callback, object state);
8: RequestContext EndReceiveRequest(IAsyncResult result);
9: bool EndTryReceiveRequest(IAsyncResult result, out RequestContext context);
10: bool EndWaitForRequest(IAsyncResult result);
11: RequestContext ReceiveRequest();
12: RequestContext ReceiveRequest(TimeSpan timeout);
13: bool TryReceiveRequest(TimeSpan timeout, out RequestContext context);
14: bool WaitForRequest(TimeSpan timeout);
15:
16: // Properties
17: EndpointAddress LocalAddress { get; }
18: }
1: public interface IDuplexChannel : IInputChannel, IOutputChannel, IChannel, ICommunicationObject
2: {
3: }
三、創建Custom Channel
為了讓大家對WCF channel layer有一個深刻的認識,以及掌握如何有效地對其進行擴展。我在整篇文章中穿插介紹一個具體的Sample:。
這個Sample將基於我們最為常用的 channel shape。所以我們需要創建兩個Channel,一個是用於發送方的實現了的Channel,而另一個則是實現了的用於接收方的Channel。
為了簡單起見,在我定義的channel的每個方法僅僅打印出相應的方法名稱而已(這樣做不但簡單,還有的一個好處,那就是當我最後將其應用到具體的Messaging場景中,可以)。
1: namespace Artech.ChannleStackExplore.Channels
2: {
3: public class MyRequestChannel :ChannelBase, IRequestChannel
4: {
5: private IRequestChannel InnerChannel
6: {get;set;}
7:
8: public MyRequestChannel(ChannelManagerBase channleManager, IRequestChannel innerChannel)
9: : base(channleManager)
10: {
11: this.InnerChannel = innerChannel;
12: }
13:
14: #region ChannelBase Members
15: protected override void OnAbort()
16: {
17: Console.WriteLine("MyRequestChannel.OnAbort()");
18: this.InnerChannel.Abort();
19: }
20:
21: protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
22: {
23: Console.WriteLine("MyRequestChannel.OnBeginClose()");
24: return this.InnerChannel.BeginClose(timeout, callback, state);
25: }
26:
27: protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
28: {
29: Console.WriteLine("MyRequestChannel.OnBeginOpen()");
30: return this.InnerChannel.BeginOpen(timeout, callback, state);
31: }
32:
33: protected override void OnClose(TimeSpan timeout)
34: {
35: Console.WriteLine("MyRequestChannel.OnClose()");
36: this.Close(timeout);
37: }
38:
39: protected override void OnEndClose(IAsyncResult result)
40: {
41: Console.WriteLine("MyRequestChannel.OnEndClose()");
42: this.InnerChannel.EndClose(result);
43: }
44:
45: protected override void OnEndOpen(IAsyncResult result)
46: {
47: Console.WriteLine("MyRequestChannel.OnEndOpen()");
48: this.InnerChannel.EndOpen(result);
49: }
50:
51: protected override void OnOpen(TimeSpan timeout)
52: {
53: Console.WriteLine("MyRequestChannel.OnOpen()");
54: this.InnerChannel.Open(timeout);
55: }
56: #endregion
57:
58: #region IRequestChannel Members
59:
60: public IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state)
61: {
62: Console.WriteLine("MyRequestChannel.BeginRequest()");
63: return this.BeginRequest(message, timeout, callback, state);
64: }
65:
66: public IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state)
67: {
68: Console.WriteLine("MyRequestChannel.BeginRequest()");
69: return this.InnerChannel.BeginRequest(message, callback, state);
70: }
71:
72: public Message EndRequest(IAsyncResult result)
73: {
74: Console.WriteLine("MyRequestChannel.EndRequest()");
75: return this.InnerChannel.EndRequest(result);
76: }
77:
78: public EndpointAddress RemoteAddress
79: {
80: get
81: {
82: Console.WriteLine("MyRequestChannel.RemoteAddress");
83: return this.InnerChannel.RemoteAddress;
84: }
85:
86: }
87:
88: public Message Request(Message message, TimeSpan timeout)
89: {
90: Console.WriteLine("MyRequestChannel.Request()");
91: return this.InnerChannel.Request(message, timeout);
92: }
93:
94: public Message Request(Message message)
95: {
96: Console.WriteLine("MyRequestChannel.Request()");
97: return this.InnerChannel.Request(message);
98: }
99:
100: public Uri Via
101: {
102: get
103: {
104: Console.WriteLine("MyRequestChannel.Via)");
105: return this.InnerChannel.Via;
106: }
107:
108: }
109:
110: #endregion
111: }
112: }
這裏唯一需要注意的一點是:在實際的運行環境中,我們的channel僅僅了位於Channel stack的某個環節。該channel和其他的一些channel組成一個管道,這個管道裏流淌是。所以當一個Channel執行了它相應的操作的時候,需要將message傳到下一個channel作進一步處理。所有我們的Channel需要下一個Channel的應用,這個Channel就是我們的字段,該成員在構造函數中指定。
1: private IRequestChannel InnerChannel
2: {get;set;}
3:
4: public MyRequestChannel(ChannelManagerBase channleManager, IRequestChannel innerChannel)
5: : base(channleManager)
6: {
7: this.InnerChannel = innerChannel;
8: }
1: namespace Artech.ChannleStackExplore.Channels
2: {
3: public class MyReplyChannel: ChannelBase, IReplyChannel
4: {
5: private IReplyChannel InnerChannel
6: { get; set; }
7:
8: public MyReplyChannel(ChannelManagerBase channelManager, IReplyChannel innerChannel):base(channelManager)
9: {
10: this.InnerChannel = innerChannel;
11: }
12:
13: ChannelBase Members
14:
15: IReplyChannel Members
16: }
17: }
4. 創建Custom Channel Factory & Channel Listener
通過上一篇文章的介紹,我們知道Channel是通過Channel Manager來創建並管理的,在發送方的Channel Manager被稱為。對於Channel factory,除了定義了兩個Interface之外(IChannelFactoryIChannelFactory<TChannel>
1: public interface IChannelFactory : ICommunicationObject
2: {
3: // Methods
4: T GetProperty<T>() where T : class;
5: }
6:
7: public interface IChannelFactory<TChannel> : IChannelFactory, ICommunicationObject
8: {
9: // Methods
10: TChannel CreateChannel(EndpointAddress to);
11: TChannel CreateChannel(EndpointAddress to, Uri via);
12: }
1: namespace Artech.ChannleStackExplore.Channels
2: {
3: public class MyChannelFactory<TChannel> : ChannelFactoryBase<TChannel>
4: {
5: private IChannelFactory<TChannel> InnerChannelFactory
6: { get; set; }
7:
8: public MyChannelFactory(BindingContext context)
9: {
10: this.InnerChannelFactory = context.BuildInnerChannelFactory<TChannel>();
11: }
12:
13: protected override TChannel OnCreateChannel(EndpointAddress address, Uri via)
14: {
15: Console.WriteLine("MyChannelFactory<TChannel>.OnClose()");
16: TChannel innerChannel = this.InnerChannelFactory.CreateChannel(address, via);
17: return (TChannel)(object)(new MyRequestChannel(this, innerChannel as IRequestChannel));
18: }
19:
20: protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
21: {
22: Console.WriteLine("MyChannelFactory<TChannel>.OnBeginOpen()");
23: return this.InnerChannelFactory.BeginOpen(timeout, callback, state);
24: }
25:
26: protected override void OnEndOpen(IAsyncResult result)
27: {
28: Console.WriteLine("MyChannelFactory<TChannel>.OnEndOpen()");
29: this.InnerChannelFactory.EndOpen(result);
30: }
31:
32: protected override void OnOpen(TimeSpan timeout)
33: {
34: Console.WriteLine("MyChannelFactory<TChannel>.OnOpen()");
35: this.InnerChannelFactory.Open();
36: }
37: }
38: }
我們說過,和Channel stack一樣,Channel factory仍然是一個stack,原因很簡單,一個個的Channel需要相應的channel factory來創建。同Channel一樣,。不過不通於Channel的是,下一個Channel factory不時在構造函數直接指定的,而是通過構造函數中BindingContext 對象的創建。
1: private IChannelFactory<TChannel> InnerChannelFactory
2: { get; set; }
3:
4: public MyChannelFactory(BindingContext context)
5: {
6: this.InnerChannelFactory = context.BuildInnerChannelFactory<TChannel>();
7: }
注:BindingContext 的兩個最重要的方法就是BuildInnerChannelFactory和
1: namespace Artech.ChannleStackExplore.Channels
2: {
3: public class MyChannelListener<TChannel> : ChannelListenerBase<TChannel> where TChannel : class, IChannel
4: {
5: private IChannelListener<TChannel> InnerChannelListener
6: { get; set; }
7:
8: public MyChannelListener(BindingContext context)
9: {
10: this.InnerChannelListener = context.BuildInnerChannelListener<TChannel>();
11: }
12:
13: protected override TChannel OnAcceptChannel(TimeSpan timeout)
14: {
15: Console.WriteLine("MyChannelListener<TChannel>.OnAcceptChannel()");
16: TChannel innerChannel = this.InnerChannelListener.AcceptChannel(timeout);
17: return new MyReplyChannel(this, innerChannel as IReplyChannel) as TChannel;
18: }
19:
20: protected override IAsyncResult OnBeginAcceptChannel(TimeSpan timeout, AsyncCallback callback, object state)
21: {
22: Console.WriteLine("MyChannelListener<TChannel>.OnBeginAcceptChannel()");
23: return this.InnerChannelListener.BeginAcceptChannel(timeout, callback, state);
24: }
25:
26: protected override TChannel OnEndAcceptChannel(IAsyncResult result)
27: {
28: Console.WriteLine("MyChannelListener<TChannel>.OnEndAcceptChannel()");
29: TChannel innerChannel = this.InnerChannelListener.EndAcceptChannel(result);
30: return new MyReplyChannel(this, innerChannel as IReplyChannel) as TChannel;
31: }
32:
33: protected override IAsyncResult OnBeginWaitForChannel(TimeSpan timeout, AsyncCallback callback, object state)
34: {
35: Console.WriteLine("MyChannelListener<TChannel>.OnBeginWaitForChannel()");
36: return this.InnerChannelListener.BeginWaitForChannel(timeout, callback, state);
37: }
38:
39: protected override bool OnEndWaitForChannel(IAsyncResult result)
40: {
41: Console.WriteLine("MyChannelListener<TChannel>.OnEndWaitForChannel()");
42: return this.InnerChannelListener.EndWaitForChannel(result);
43: }
44:
45: protected override bool OnWaitForChannel(TimeSpan timeout)
46: {
47: Console.WriteLine("MyChannelListener<TChannel>.OnWaitForChannel()");
48: return this.InnerChannelListener.WaitForChannel(timeout);
49: }
50:
51: public override Uri Uri
52: {
53: get
54: {
55: Console.WriteLine("MyChannelListener<TChannel>.Uri");
56: return this.InnerChannelListener.Uri;
57: }
58:
59: }
60:
61: protected override void OnAbort()
62: {
63: Console.WriteLine("MyChannelListener<TChannel>.OnAbort()");
64: this.InnerChannelListener.Abort();
65: }
66:
67: protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
68: {
69: Console.WriteLine("MyChannelListener<TChannel>.OnBeginClose()");
70: return this.InnerChannelListener.BeginClose(timeout, callback, state);
71: }
72:
73: protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
74: {
75: Console.WriteLine("MyChannelListener<TChannel>.OnBeginOpen()");
76: return this.InnerChannelListener.BeginOpen(timeout, callback, state);
77: }
78:
79: protected override void OnClose(TimeSpan timeout)
80: {
81: Console.WriteLine("MyChannelListener<TChannel>.OnClose()");
82: this.InnerChannelListener.Close(timeout);
83: }
84:
85: protected override void OnEndClose(IAsyncResult result)
86: {
87: Console.WriteLine("MyChannelListener<TChannel>.OnEndClose()");
88: this.InnerChannelListener.EndClose(result);
89: }
90:
91: protected override void OnEndOpen(IAsyncResult result)
92: {
93: Console.WriteLine("MyChannelListener<TChannel>.OnEndOpen()");
94: this.InnerChannelListener.EndOpen(result);
95: }
96:
97: protected override void OnOpen(TimeSpan timeout)
98: {
99: Console.WriteLine("MyChannelListener<TChannel>.OnOpen()");
100: this.InnerChannelListener.Open(timeout);
101: }
102: }
103: }
五、創建Custom Binding Element
我們知道,而Binding由一係列的組成。我們上麵創建的Channel factory和Channel listener需要最終通過對應的BindingElement應用到Binding中才能最終發揮作用。我們就來創建這個BindingElement:MyBindingElement。
1: namespace Artech.ChannleStackExplore.Channels
2: {
3: public class MyBindingElement:BindingElement
4: {
5: public override BindingElement Clone()
6: {
7: return new MyBindingElement();
8: }
9:
10: public override T GetProperty<T>(BindingContext context)
11: {
12: return context.GetInnerProperty<T>();
13: }
14:
15: public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
16: {
17: Console.WriteLine("MyBindingElement.BuildChannelFactory()");
18: return new MyChannelFactory<TChannel>(context) as IChannelFactory<TChannel>;
19: }
20:
21: public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
22: {
23: Console.WriteLine("MyBindingElement.BuildChannelListener()");
24: return new MyChannelListener<TChannel>(context) as IChannelListener<TChannel>;
25: }
26: }
27: }
六、創建Custom Binding
我們進入了最後的階段,創建一個Custom Binding。MyBinding繼承Binding。 在方法中將我們的Binding element(MyBindingElement),連同其他必須的Binding element添加到BindingElementCollection 中。
1: namespace Artech.ChannleStackExplore.Channels
2: {
3: public class MyBinding:Binding
4: {
5: public override BindingElementCollection CreateBindingElements()
6: {
7: BindingElementCollection elemens = new BindingElementCollection();
8: elemens.Add(new TextMessageEncodingBindingElement());
9: elemens.Add(new MyBindingElement());
10: elemens.Add(new HttpTransportBindingElement());
11: return elemens.Clone();
12: }
13:
14: public override string Scheme
15: {
16: get
17: {
18: return "http";
19: }
20: }
21: }
22: }
注:對BindElement的組裝可以通過configuration來實現。
七、使用Custom Binding
1: namespace Server
2: {
3: class Program
4: {
5: static void Main(string[] args)
6: {
7: MyBinding binding = new MyBinding();
8: IChannelListener<IReplyChannel> channelListener= binding.BuildChannelListener<IReplyChannel>(new Uri("https://127.0.0.1:8888/messagingviabinding"));
9: channelListener.Open();
10:
11: while (true)
12: {
13: IReplyChannel channel= channelListener.AcceptChannel(TimeSpan.MaxValue);
14: channel.Open();
15: RequestContext context = channel.ReceiveRequest(TimeSpan.MaxValue);
16:
17: Console.WriteLine("Receive a request message:\n{0}", context.RequestMessage);
18: Message replyMessage = Message.CreateMessage(MessageVersion.Soap12WSAddressing10, "https://artech.messagingviabinding", "This is a mannualy created reply message for the purpose of testing");
19: context.Reply(replyMessage);
20: channel.Close();
21: }
22: }
23: }
24: }
1: namespace Client
2: {
3: class Program
4: {
5: static void Main(string[] args)
6: {
7: MyBinding binding = new MyBinding();
8: IChannelFactory<IRequestChannel> channelFactory = binding.BuildChannelFactory<IRequestChannel>();
9: channelFactory.Open();
10:
11: IRequestChannel channel = channelFactory.CreateChannel(new EndpointAddress("https://127.0.0.1:8888/messagingviabinding"));
12: channel.Open();
13:
14: Message requestMessage = Message.CreateMessage(MessageVersion.Soap12WSAddressing10, "https://artech.messagingviabinding", "This is a mannualy created reply message for the purpose of testing");
15: Message replyMessage = channel.Request(requestMessage);
16: Console.WriteLine("Receive a reply message:\n{0}", replyMessage);
17: channel.Close();
18: channelFactory.Close();
19: Console.Read();
20: }
21:
22: }
23: }
通過上麵的輸出結果,你很直觀的了解到了整個程序執行過程中,我們的定義在Channel,Channel factory,Channel listener和Binding element的方法是如何被依次執行的。
WCF後續之旅:
WCF後續之旅(1): WCF是如何通過Binding進行通信的
WCF後續之旅(2): 如何對Channel Layer進行擴展——創建自定義Channel
WCF後續之旅(3): WCF Service Mode Layer 的中樞—Dispatcher
WCF後續之旅(4):WCF Extension Point 概覽
WCF後續之旅(5): 通過WCF Extension實現Localization
WCF後續之旅(6): 通過WCF Extension實現Context信息的傳遞
WCF後續之旅(7):通過WCF Extension實現和Enterprise Library Unity Container的集成
WCF後續之旅(8):通過WCF Extension 實現與MS Enterprise Library Policy Injection Application Block 的集成
WCF後續之旅(9):通過WCF的雙向通信實現Session管理[Part I]
WCF後續之旅(9): 通過WCF雙向通信實現Session管理[Part II]
WCF後續之旅(10): 通過WCF Extension實現以對象池的方式創建Service Instance
WCF後續之旅(11): 關於並發、回調的線程關聯性(Thread Affinity)
WCF後續之旅(12): 線程關聯性(Thread Affinity)對WCF並發訪問的影響
WCF後續之旅(13): 創建一個簡單的WCF SOAP Message攔截、轉發工具[上篇]
WCF後續之旅(13):創建一個簡單的SOAP Message攔截、轉發工具[下篇]
WCF後續之旅(14):TCP端口共享
WCF後續之旅(15): 邏輯地址和物理地址
WCF後續之旅(16): 消息是如何分發到Endpoint的--消息篩選(Message Filter)
WCF後續之旅(17):通過tcpTracer進行消息的路由
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-30 17:33:34