為WCF增加UDP綁定(實踐篇)
這兩天忙著系統其它功能的開發,沒顧上寫日志。本篇所述皆圍繞為WCF增加UDP綁定(儲備篇)中講到的微軟示例,該示例我已上傳到網盤。
上篇說道,綁定是由若干綁定元素有序組成,為WCF增加UDP綁定其實就是為綁定增加UDP傳輸綁定元素,最終目的是在信道棧中生成UDP傳輸信道。因此我們定義一個類UdpTransportBindingElement,它繼承自TransportBindingElement表明這是傳輸相關的綁定元素。示例中該類還實現了IPolicyExportExtension和IWsdlExportExtension接口,關于這兩個接口,我知道它們的作用,但看到代碼卻是一頭霧水,我認為要能理解這塊內容首先要對WSDL相關的各種規范做一詳細了解,目前我先不考慮。對這有興趣的朋友可閱讀蔣大牛的元數據架構體系全景展現。以前總覺得WCF不外如是,不成想這水也太特碼深了(WCF相關的概念有很多,包括ChannelDispatcher、ListenerHandler、ChannelHandler等,這些和消息傳輸關系不大)。
UdpTransportBindingElement主要負責綁定管理類的創建工作,服務器端和客戶端分別創建的是UdpChannelListener和UdpChannelFactory。UdpChannelFactory的實現比較簡單,我們重點看UdpChannelListener實現。網上資料都有類似這么一段描述:ChannelListener負責監聽消息,一旦消息抵達則使用對應的Channel接收消息。在UDP解決方案中,消息監聽依賴System.Net.Sockets.Socket類,示例中調用該類的BeginReceiveFrom方法進行異步監聽。
1 void StartReceiving(object state)
2 {
3 Socket listenSocket = (Socket)state;
4 IAsyncResult result = null;
5
6 try
7 {
8 lock (ThisLock)
9 {
10 if (base.State == CommunicationState.Opened)
11 {
12 EndPoint dummy = CreateDummyEndPoint(listenSocket);
13 byte[] buffer = this.bufferManager.TakeBuffer(maxMessageSize);
14 result = listenSocket.BeginReceiveFrom(buffer, 0, buffer.Length,
15 SocketFlags.None, ref dummy, this.onReceive, new SocketReceiveState(listenSocket, buffer));
16 }
17 }
18
19 if (result != null && result.CompletedSynchronously)
20 {
21 ContinueReceiving(result, listenSocket);
22 }
23 }
24 catch (Exception e)
25 {
26 Debug.WriteLine("Error in receiving from the socket.");
27 Debug.WriteLine(e.ToString());
28 }
29 }
需要關注Socket.BeginReceiveFrom方法,MSDN有一段描述:當應用程序調用 BeginReceiveFrom 時,系統將會使用單獨的線程來執行指定的回調方法,并將在 EndReceiveFrom 上一直阻塞到 Socket 讀取數據或引發異常為止。但示例中有判斷該方法是否同步執行的代碼,因此我認為該方法并不會每次都用異步的方式來進行數據的接收,具體請看我的另一篇文章關于IAsyncResult接口的CompletedSynchronously屬性。
不出意外,當數據抵達后采用Socket.EndReceiveFrom獲取。我看到這里的時候覺得有點不對勁,不是說“ChannelListener負責監聽消息,一旦消息抵達則使用對應的Channel接收消息”嗎?既然ChannelListener都把數據接收完成了,還要Channel何用。此處似乎Channel真的只是走個過場,它存在的意義只是因為WCF框架需要它。所以我們還是需要將已接收的消息從ChannelListener傳遞給Channel,于是我翻看代碼,希望找到一個event,在消息接收完畢后觸發,以便Channel能實時得到消息可用的信號,結果沒找到哪怕一個event的聲明,于是我苦惱了。
1 public interface IInputChannel : IChannel, ICommunicationObject
2 {
3 EndpointAddress LocalAddress { get; }
4
5 IAsyncResult BeginReceive(AsyncCallback callback, object state);
6 IAsyncResult BeginReceive(TimeSpan timeout, AsyncCallback callback, object state);
7 IAsyncResult BeginTryReceive(TimeSpan timeout, AsyncCallback callback, object state);
8 IAsyncResult BeginWaitForMessage(TimeSpan timeout, AsyncCallback callback, object state);
9 Message EndReceive(IAsyncResult result);
10 bool EndTryReceive(IAsyncResult result, out Message message);
11 bool EndWaitForMessage(IAsyncResult result);
12 Message Receive();
13 Message Receive(TimeSpan timeout);
14 bool TryReceive(TimeSpan timeout, out Message message);
15 bool WaitForMessage(TimeSpan timeout);
16 }
從IInputChannel的定義可以看到,什么時候接收消息,什么時候等待,是不可控的,因為這些方法可以外部調用(一般是WCF框架自己調用)。簡單地說,就是IInputChannel本身獲取消息采用的是拉模式,而非推模式。為什么WCF框架不使用event模式(推模式)呢,我沒有深入研究過,有知道的朋友還望賜教。這些方法調用時間不知,但若有可用消息時,不論何時調用Receive方法,我們都應該返回正確的Message,換句話說,從消息抵達到獲取消息,可能有個時間差,這段時間內,消息數據不能丟失,于是我們應該有個臨時存儲消息數據的地方。考慮到消息數量和處理順序,先入先出隊列是個不錯的選擇。不出所料,示例中有個InputQueue類,該類相當復雜,不過萬變不離其宗,UdpChannelListener將接收到的消息存入InputQueue,UdpInputChannel從InuptQueue中Receive消息。弄清楚這個,我便釋然了。
上述模式只是實現消息從ChannelListener到Channel傳遞的其中一種方式,只要按規范實現WCF框架提供的關鍵接口,我們可以使用能想到的任何方式,比如使用event將消息接收工作轉移到Channel類(更符合WCF對Channel的職責說明),當然接收到的消息仍舊需要用Receive方法去某個地方讀取,因此消息的臨時存儲仍然必不可少。
由于最近比較忙,就先寫到這里吧。示例中最核心的就是ChannelListener、InputQueue和AsyncResult類,AsyncResult類的作用也在關于IAsyncResult接口的CompletedSynchronously屬性中有過闡述。若是全面鋪開就太多了,有興趣的朋友可以下載代碼自己研究,有什么心得體會歡迎一起討論。
注意用4G上網:這個地址是由網絡中的GGSN或PGW分配的,是一個私網地址,但經過Gi防火墻會轉換成公網IP再訪問Internet。這樣的話,手機側看到的就是一個私網地址,而在互聯網看來(比如站在微信服務器的角度來看),手機還有一個對應的公網IP。
轉載請注明本文出處:http://www.rzrgm.cn/newton/archive/2012/11/29/2793931.html

浙公網安備 33010602011771號