此文分為二個部分
三、伺服端程式(續)
上一篇寫到伺服器的 Listen 函式 Server(),當Socket一開始接收到Client的連線要求時,由於BeginAccept定義了非同步的方法:OnClientConnect
接下來我們要來研究實作 OnClientConnect() 函式
// 定義最大Clients 常數 const int MAX_CLIENTS=20; // 定義每個連線的工作類別域變數 Socket陣列 private Socket[] workerSocket = new Socket[MAX_CLIENTS]; public void OnClientConnect(IAsyncResult asyn) { try{ //宣告並定義空頻道的ID=-1,表示無空頻道資料 int Empty_channel_ID = -1; //若主Socket為空則跳出 if (mainSocket == null) return; Socket temp_Socket = mainSocket.EndAccept(asyn); //取得遠端節點的EndPoint EndPoint RemoteEP = temp_Socket.RemoteEndPoint; Empty_channel_ID = FindEmptyChannel(); if (Empty_channel_ID == -1) throw new NoSocketAvailableException(); //將方才暫存的Socket交給空的 Socket接收 workerSocket[Empty_channel_ID] = temp_Socket; //將暫存的Socket設為空 temp_Socket = null; WaitForData(workerSocket[Empty_channel_ID]); } catch (ObjectDisposedException) { …處理已釋放記憶體的資源例外處理略... } catch (SocketException) { …因TCP Socket造成的例外處理略... } //自定了一個錯誤的Exception類型,當找不到空頻道表示所有頻道都被佔用 catch (NoSocketAvailableException err) { …無可用的頻道例外處理略... } finally{ //將方才關閉的主要Socket重新接收新的連線 mainSocket.BeginAccept(new AsyncCallback(OnClientConnect), null); } }
第15行 將主要正在 listening 的主要Socket轉交給另一個臨時的Socket變數,並且結束接受此一客戶端的連線
第18行 傳回空頻道,函式FindEmptyChannel() 列於後,主要是找尋未佔用的Socket(Channel)
第19行 自定了一個錯誤的Exception類型,當找不到空頻道表示所有頻道都被佔用,則會丟出一個 NoSocketAvailableException() 的錯誤
第24行,重新叫用 WaitForData 開始接收資料
//自定錯誤類型,當所有頻道都被佔用時叫用,其中只覆寫了Exception.Message 屬性 public class NoSocketAvailableException : System.Exception { new public string Message = "所有的頻道都已佔滿,請先釋放其他的頻道"; } // 尋找第一個空頻道的函式,傳回Channel ID或是-1 全被佔滿 private int FindEmptyChannel() { for (int i = 0; i < MAX_CLIENTS; i++) { if (m_workerSocket[i] == null || !m_workerSocket[i].Connected) { return i; } } return -1; }
上面的OnClientConnect()函式中,當Server 接受 Client 的連線後,有指定當該Socket有資料傳入時,要叫用接受資料WaitForData()函式:
//宣告AsyncCallback類別的變數 pfnWorkerCallBack public AsyncCallback pfnWorkerCallBack; public void WaitForData(System.Net.Sockets.Socket soc) { try{ //當pfnWorkerCallBack物件尚未實體化時,進行實體化 if (pfnWorkerCallBack == null) { pfnWorkerCallBack = new AsyncCallback(OnDataReceived); } //自行定義的型別 SocketPacket,附於此小節尾,內容只有一個Socket類和一個int。 SocketPacket theSocPkt = new SocketPacket(); //指定此一建立連線之Socket soc 給定義的 theSocPkt theSocPkt.m_currentSocket = soc; soc.BeginReceive(theSocPkt.dataBuffer, 0, theSocPkt.dataBuffer.Length, SocketFlags.None, pfnWorkerCallBack, theSocPkt); } catch (SocketException) { …例外處理略... } } //自型定義的物件封包的類別 public class SocketPacket { //目前Activating之Socket public System.Net.Sockets.Socket m_currentSocket; public byte[] dataBuffer = new byte[1500]; //接受資料陣列 }
第9行,假設已連線的RCC客戶端要傳送資料給RCS時,所指定的回呼函式-OnDataReceive,這也是C#中的delagate型別
第17行,定義當Socket 接收到資料時要交給BeginReceive() 這個函數。參數第一個為存放的位置,為byte[],傳入的資料會置於此陣列中;第二參數為啟始位置、 第三為一次置入的長度;第四為封包的旗標,例如標記為廣播封包 之類,第五參數為開始 接受資料時叫用的回呼函式,由於pfnWorkerCallBack已實體化指定OnDataReceived為回呼函式, 所以當有 客戶端將資料傳進來時,OnDataReceived函式會被叫用;第六為狀態的參數, 這個是用戶自訂,當回呼函式OnDataReceived被叫用 時,此參數的值會被傳遞到OnDataReceived, 因此雖然SocketPacket theSocPkt宣告的是區域變數,但是回呼函式仍會曉得是哪一個Socket在傳送資料。
當建立連線的 Socket 有資料傳入時,要叫用的回呼函式為OnDataReceived() 。
此回呼函式有一參數類型為IasyncResult,因為該類型是.NET中很特別的類型,稱之為介面(Interface),簡單的說,介面算是一個只有定義沒有實作項的類別。由於BeginReceive 方法被叫用時的傳入的最未個參數是自定的theSocPkt物件,會轉型成IAsyncResult.AsyncState 傳入,因此宣告的IasyncResult類變數asyn,則asyn.AsyncState 可取得符合或包含非同步作業資訊的使用者定義的物件。
下方將實作出OnDataReceived() 函式,由於此函式必需將收到的資料分析及編碼處理,所以原函式稍長,故僅節錄出和TCP非同步連線有關之部分於下:
public void OnDataReceived(IAsyncResult asyn) { try{ SocketPacket socketData = (SocketPacket)asyn.AsyncState;//取得接受的資料 string msg = "";//宣告及定義訊息字串 int iRx = 0;//宣告及定義訊息長度 iRx = socketData.m_currentSocket.EndReceive(asyn); byte[] databuff = socketData.dataBuffer; msg = Encoding.UTF8.GetString(databuff); // ...分析處理部分略,主要msg訊息的利用和重組... WaitForData(socketData.m_currentSocket); } catch (SocketException) { …例外處理略... } }
第8行,叫用 EndReceive 完成指定的非同步接收作業, 參數asyn 識別要完成的非同步接收作業,並要從其中擷取最終結果。
第9行,將取出的部分轉成 UTF8的編碼方式,有關編碼方式轉換可依需求自行修改。
第13行,重新叫用 WaitForData 重新開始接收資料
以上幾個 函式就完成了 非同步方法 TCP socket ,這裡將其整理一下:
Server() 啟動監聽
OnClientConnect(IAsyncResult) 當客戶端建立連線時叫用
WaitForData(Socket) 建立連線等待資料傳入
OnDataReceived(IAsyncResult) 已連線連線並有資料傳入時叫用
FindEmptyChannel() 尋找未用的頻道
祝各位使用愉快
參考資料
[1] Asynchronous Socket Programming in C#
[2] http://www.codeguru.com/csharp/csharp/cs_network/sockets/article.php/c7695/#more
原文 2010-04-02 00:52:54