I:跨平台設計基礎
在windows下使用0字節的WSARecv/WSASend(讀寫)作為讀寫檢測,將IOCP作為一個通知模型,而"拋棄"它的異步模型。
即:把它當作epoll來用。使得(方便)網絡庫的設計(譬如socket的讀寫處理)在windows和linux下實現統一: 底層獲得讀寫通知,應用層(網絡庫中)自己處理讀寫。
II:單線程EventLoop
1:EventLoop是一個單線程的網絡IO循環,使用一個iocp(epoll)實例,管理多個DataSocket。
通過EventLoop的addConnection接口為它添加DataSocket(並傳入一個回調),並會在EventLoop接手它后觸發傳入的回調函數--處理鏈接建立之后的業務邏輯。
DataSocket 即為一個客戶端連接-會話,用戶可以為它設置事件回調函數。
setDataHandle:設置收到消息后的回調函數。
setDisConnectHandle:設置鏈接斷開后的回調函數。
當然我們也可以通過disConnect主動斷開會話的網絡連接。
2:EventLoop實現線程安全的wakeup和異步消息隊列,用於外部邏輯線程投遞異步操作(以及喚醒,以讓eventloop盡可能更快的處理請求)。
3:std::shared_ptr作為packet類型,用於將同一個packet投遞給多個客戶端DataSocket(避免分配多個消息),DataSocket 內置一個隊列,保存當前pending在它上的待發packet list。
之所以采用std::shared_ptr,是因為它自帶引用計數處理,可以方便的應對同一個消息包發送給多個客戶端時,消息包的分配和釋放問題(即此消息全部發送給它的目的客戶端,那么此消息就可以回收),
無需自己再寫一套消息包設計了。
注:目前linux下給客戶端flush發送網絡消息時,采用writev提高效率,但windows上沒有找到相關函數(WSASend不滿足要求),但可以在某些時候將消息memcpy到一個緩沖區,然后一次性send(以減少send系統調用次數))。
III: 封裝EventLoop的TCPServer
1:TCPServer處理Listen邏輯以及為新到的鏈接分配(通過EventLoop的addConnection接口)一個EventLoop。
2!:可從源碼中看到DataSocket的事件回調函數所附帶的參數是DataSocket*, 但是 TcpServer回調函數參數中,表示會話標識的類型為:int64 id,而非裸指針。
這是因為TCPServer多被用於多線程設計,此時會話的有效性(避免串話)(以及內存的有效性-野指針問題)需要保證,而裸指針並非安全的。
3:第二點說到TcpServer多用於多線程設計時,具體如下:
在它的回調函數中,我們可以將消息投遞到一個邏輯線程的消息隊列,並wakeup邏輯線程的EventLoop),當邏輯線程被喚醒后,從消息隊列中同步讀取消息,然后處理。
而當邏輯線程需要發送消息則使用: TCPServer的send接口,參數是一個int64_t id 表示要發送消息的會話,緊接着是一個Packet,表示消息內容。
當然!:TCPServer的回調函數中可以立即處理,而非投遞到別的線程進行協作,這樣用起來當然更簡單了。
譬如在某些網絡服務中,不需要很耗時的處理,而僅僅是IO密集型(比如網關), 那么建議直接在回調函數中進行處理(譬如轉發)。
注: 此網絡庫參考了 muduo:https://github.com/chenshuo/muduo
另外致謝:sniperhw:http://www.cnblogs.com/sniperhw 近幾年的指點
網絡庫代碼地址: https://github.com/IronsDu/accumulation-dev/tree/master/cpp_net
目前main.cpp實現的是一個ping pong測試(一個進程內:服務器用TCPServer,用多個客戶端線程跑各自的EventLoop)。
從main.cpp也可以看到 單線程EventLoop和TCPServer多線程加消息隊列的使用方式。
(VS版本至少 VS2013),Linux下簽出整個項目后進入/examples/DNet/DNet目錄, (我用的g++ 4.9版本 )再使用命令:
g++ -I./../../../cpp_common -I./../../../common eventloop.cpp datasocket.cpp TCPServer.cpp main.cpp -std=c++0x -lrt
TODO::代碼中有一些TODO,表示晦澀或者我不太確定沒問題~HOHO
另外我很期待后面要做的廣播測試(類似MMO的AOI,多個玩家同時移動,要廣播給周圍N個玩家)。
目前ping pong測試(客戶端和服務器在同一個進程內,編譯時不開任何優化)在我的機器(AMD Athlon(tm) 7750 Dual-Core Processor,1.3G Hz, CentOS 6.3):
1:TCPServer使用一個線程,並將收到的消息投遞到邏輯線程進行ping pong處理。
100個鏈接,消息包大小為4K,每秒吞吐為190M/s。
1000個鏈接,消息包大小為4K,每秒吞吐為135M/s。
10000個鏈接,消息包大小為4K,每秒吞吐為125M/s。
2:TCPServer使用一個線程,直接在自身的消息回調函數中進行ping pong處理。
100個鏈接,消息包大小為4K,每秒吞吐為315M/s。
1000個鏈接,消息包大小為4K,每秒吞吐為190M/s。
10000個鏈接,消息包大小為4K,每秒吞吐為160M/s。