Ascent網(wǎng)絡(luò)模塊
Author: Kevin Lynx
Ascent是WoW的服務(wù)器模擬器,你可以從它的SVN上獲取它的全部代碼,并從它的WIKI頁面獲取架構(gòu)起整個服務(wù)器的相關(guān)步驟。
基本架構(gòu):
Ascent網(wǎng)絡(luò)模塊核心的幾個類關(guān)系如下圖所示:

ThreadBase屬于Ascent線程池模塊中的類,它實現(xiàn)了一個job類,當(dāng)其被加入到線程池中開始執(zhí)行時,線程池管理器會為其分配一個線程(如果有線程資源)并多態(tài)調(diào)用到ThreadBase派生類的run函數(shù)。
SocketWorkerThread用以代表IOCP網(wǎng)絡(luò)模型中的一個工作者線程,它會從IOCP結(jié)果隊列里取出異步IO的操作結(jié)果。這里的IOCP使用的完成鍵是Socket對象指針。SocketWorkerThread獲取到IO操作結(jié)果后,根據(jù)獲得的完成鍵將結(jié)果通知給具體的Socket對象。(Socket的說明見后面)
ListenSocket代表一個監(jiān)聽套接字。該網(wǎng)絡(luò)模塊其實只是簡單地將socket中的概念加以封裝。也就說,它依然把一個套接字分為兩種類型:監(jiān)聽套接字和數(shù)據(jù)套接字(代表一個網(wǎng)絡(luò)連接)。所謂的監(jiān)聽套接字,是指只可以在該套接字上進(jìn)行監(jiān)聽操作;而數(shù)據(jù)套接字則只可以在此套接字上進(jìn)行發(fā)送、接收數(shù)據(jù)的操作。
Socket代表我上面說的數(shù)據(jù)套接字。ListenSocket是一個類模板,為這個模板指定的模板參數(shù)通常是派生于Socket的類。其實這里使用了這個小技巧隱藏了工廠模式的細(xì)節(jié)。因為ListenSocket被放在一個單獨的線程里運作,當(dāng)其接受到一個新的網(wǎng)絡(luò)連接時,就創(chuàng)建一個Socket派生類對象。(ListenSocket類如何知道這個派生類的類名?這就是通過類模板的那個模板參數(shù))
上層模塊通常會派生Socket類,實現(xiàn)一些IO操作的回調(diào)。也就說,當(dāng)某個IO操作完成后,會通過Socket基類讓上層模塊獲取通知。
SocketMgr是一個全局單件類。它主要負(fù)責(zé)一些網(wǎng)絡(luò)庫的全局操作(例如winsock庫的初始化),它還維護(hù)了一個容器,保存所有的Socket對象。這其實是它的主要作用。
運作之一,接收新的連接:
接收新的網(wǎng)絡(luò)連接是通過ListenSocket實現(xiàn)的。在創(chuàng)建一個ListenSocket對象時,你需要指定它的模板參數(shù)。這個參數(shù)通常是一個派生于Socket的類。如下:
ascent-logonserver/Main.cpp
ListenSocket<AuthSocket> * cl = new ListenSocket<AuthSocket>(host.c_str(), cport);
AuthSocket派生于Socket。創(chuàng)建ListenSocket時構(gòu)造函數(shù)指定監(jiān)聽IP和監(jiān)聽端口。
因為ListenSocket派生于ThreadBase,屬于線程池job,因此要讓ListenSocket工作起來,只需要將其加入到線程池管理器:
ascent-logonserver/Main.cpp
ThreadPool.ExecuteTask(cl);
ListenSocket開始運作起來后,會阻塞式地WSAAccept。如果WSAAccept返回一個有效的套接字,ListenSocket就創(chuàng)建一個Socket派生類對象(類型由模板參數(shù)指定),在上面舉的例子中,也就是AuthSocket:
ascent-logonserver/ ListenSocketWin32.h
socket = new T(aSocket); //創(chuàng)建AuthSocket并保存網(wǎng)絡(luò)套接字aSocket

socket->SetCompletionPort(m_cp);//保存完成端口對象

socket->Accept(&m_tempAddress); //關(guān)聯(lián)到完成端口等

Accept函數(shù)最終會將新創(chuàng)建的Socket對象保存到SocketMgr對象內(nèi)部維護(hù)的容器里。在這里,還會回調(diào)到上層模塊的OnConnect函數(shù),從而實現(xiàn)信息捕獲。
運作之二,接收數(shù)據(jù)
在windows平臺下,該網(wǎng)絡(luò)模塊使用的是IOCP模型,屬于異步IO。當(dāng)接收新的連接時,即發(fā)出WSARecv的IO操作。在工作者線程中,也就是SocketWorkerThread中,會根據(jù)IOCP完成鍵得到Socket對象指針,然后根據(jù)不同的IO操作結(jié)果多態(tài)回調(diào)到Socket派生類對應(yīng)的函數(shù)。例如如果是WSARecv完成,則調(diào)用到AuthSocket::OnRead函數(shù)(上述例子)。OnRead函數(shù)直接可以獲取到保存數(shù)據(jù)的緩沖區(qū)指針。事實上,每一個Socket對象在被創(chuàng)建時,就會自動創(chuàng)建接收緩沖區(qū)以及發(fā)送緩沖區(qū)。
運作之三,發(fā)送數(shù)據(jù)
分析到這里,我們可以看出,該網(wǎng)絡(luò)模塊實現(xiàn)得很一般。在接受數(shù)據(jù)部分,網(wǎng)絡(luò)工作者線程回調(diào)到對應(yīng)的Socket對象,Socket直接對數(shù)據(jù)進(jìn)行上層邏輯處理。更好的做法是當(dāng)工作者線程回調(diào)到上層Socket(Socket的派生類)時,這里應(yīng)該簡單地將數(shù)據(jù)組織成上層數(shù)據(jù)包并放入上層數(shù)據(jù)包隊列,讓上層邏輯稍后處理,而不是讓網(wǎng)絡(luò)模塊自己去處理。這樣做主要是考慮到多線程模型。
同樣,該網(wǎng)絡(luò)模塊的發(fā)送模塊也是一樣,沒有緩沖機制。當(dāng)要發(fā)送數(shù)據(jù)時,直接調(diào)用到Socket的Send函數(shù)。該函數(shù)拷貝用戶數(shù)據(jù)到自己維護(hù)的發(fā)送緩沖區(qū),然后將自己的緩沖區(qū)指針直接提交給IOCP,WSASend發(fā)送。
結(jié)束
該網(wǎng)絡(luò)模塊實現(xiàn)的似乎有點簡陋,在該模塊之上也沒有數(shù)據(jù)校驗、數(shù)據(jù)加密的模塊(這些動作散亂地分布在最上層邏輯)。在架構(gòu)上也沒能很好地將概念區(qū)分開來,Socket套用了原始socket中的數(shù)據(jù)套接字,而不是我所希望的NetSession。可以圈點的地方在于該模塊很多地方使用了回調(diào)函數(shù)表,從而方便地實現(xiàn)事件傳送。