自動目錄
此文分為二個部分
三、伺服端程式(續)
上一篇寫到伺服器的 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
