IOCP完成埠資料整理——"高大上",夠全,夠詳細
最經典:
一個簡單的IOCP(IO完成埠)伺服器/客戶端類
可伸縮的IO完成埠伺服器模型(IOCP)
Build your own cryptographically safeserver/client protocol
IOCP程式設計小結
英文文獻:
Multi-threaded Client/Server Socket Class
Full Multi-thread Client/Server SocketClass with ThreadPool
IOCPNet - Ultimate IOCP
Single Server With Multiple Clients : aSimple C++ Implementation
Simple client-server network using C++ andWindows Winsock
其他的:
漫步者de Blog
IOCP普及篇(一)
IOCP普及篇(二)
IOCP 完整
Windows完成埠程式設計
IOCP 的RECV和SEND
IOCP注意事項
IOCP模型總結(轉)
IOCP 中WSARecvFrom返回10045的問題
IOCP The WSAENOBUFS error problem
用空連線或IO負載很低的連線來測試併發效能根本沒有意義
微軟自己都承認,使用Winsock的網路應用,有效併發大於250左右作業系統效能就會開始急劇下降所以才搞出了IOCP機制。IOCP承載10000以上長連線並保持20000次IO/秒,一般沒啥問題
網路程式設計之完成埠(CSDN上摘抄的)
dkink的專欄
Winsock程式設計入門(1)修訂版-簡單的TCP伺服器
Winsock程式設計入門(2)修訂版-簡單的TCP客戶端
Winsock程式設計入門(3)修訂版-多執行緒TCP伺服器和客戶端
qqinbaby 的BLOG
基於多執行緒的客戶端/伺服器套接字類
使用新的執行緒池 API 提高可伸縮性
3.3 小節
講這麼點就完了?你一定認為我介紹的東西並沒有超過原書中的內容,實事上完成埠程式設計的精髓就是上面的程式碼和原書中的有關敘述。如果我再把他們完整的重複一遍,那又有什麼意思呢?根據我的經驗,設計網路伺服器的真正難點,不在於完成埠技術,所以我想利用小節把自己程式設計中的一些經驗告訴大家。
首先是伺服器的管理,一個伺服器首先要分析它的設計目標是應對很多的連線還是很大的資料傳送量。這樣在設計工作者執行緒時就可以最大限度的提高效能。管理客戶端方面,我們可以將客戶端的資料捆綁到Perhand-Data資料結構上,如果還有需要,可以建一個表來記錄客戶端的巨集觀情況。
在Ares引擎中,我將檔案傳送和大容量資料傳送功能也封裝進了伺服器和客戶端。我建議伺服器和客戶端都應該封裝這些功能,儘管我們並不是做FTP伺服器,但是當客戶端需要和伺服器交換檔案和大塊資料時,你會發現這樣做,靈活性和效能都能做得比用單純的FTP協議來更好,所以在你的伺服器和客戶端可以傳送資料包以後,把他們都做進去吧。
為了伺服器不被黑客攻擊,或被BUG弄崩潰,我們還需要認真設計伺服器的認證機制,以及密切注意程式中的溢位,一定要在每一個使用緩衝區的地方加上檢查程式碼。可以說並沒有現成的辦法來解決這個問題,不然就沒有人研究網路安全了,所以我們要做的是儘量減少錯誤,即使出現錯誤也不會造成太大損失,在發現錯誤的時候能夠很快糾正同類錯誤。
還有就是對客戶端情況的檢測,比如客戶端的正常和非正常斷開連線。如果不注意這一點,就會造成伺服器資源持續消耗而最終崩潰,因為我們的伺服器不可能總是重啟,而是要持續的執行,越久越好。還有比如客戶端斷開連線後又嘗試連線,但是在伺服器看來這個客戶“仍然線上“,這個時候我們不能單純的拒絕客戶端的連線,也不能單純的接收。
講了幾點伺服器設計中的問題,他們只是眾多問題中的一小部分,限於時間原因,在這個版本的文章中就說這麼多。你一定會發現,其實網路程式設計最困難和有成就的地方,並不是伺服器用了什麼模式等等,而是真正深入設計的時候碰到的眾多問題。正是那些沒有標準答案的問題,值得我們去研究和解決。
IOCP普及篇(一)[轉]
100分求完成埠傳送資料的方法
IOCP應用框架的設計
IOCP編寫心得
IOCP的封裝類[By Sodme]
程序伺服器模型和執行緒伺服器模型
淺析Delphi實現IOCP後的優化
DELPHI中完成埠(IOCP)的簡單分析(3)
fxh7622 的BLOG
小豬的網路程式設計+花貓的漫畫世界
牧野的Blog
關於socket的粘包 (2008-11-22 08:53:07)
以前也被這個困擾,折騰了很久。網上討論的也巨火,彷彿對socket的效能頗加質疑,總覺得不方便。有用各種方法延時的,有一收一發的,還有檢測傳送結果的,反正貌似就要跟這個粘包的socket幹上了...
我以前著這麼試過,能解決問題,但換來的是效能成倍的下降,現在總算是想通了,我講講我的理解。
首先要承認,TCP的特性就是流傳輸,所以粘包不是一個不能理解和接受的事實,自動的分片和連續的傳輸反而是為了提高效能而設計的,這個要承認。要處理粘包的情況,八成是為了接收有結構的資料吧,要麼怕收到半個,要麼就是1個多,反正是對不上自己的結構,所以會出錯。
其實想想,人家連在一起了,你就真的沒法處理了嗎?我的做法是,你在記憶體裡再開一個緩衝區,把收到的資料不管是半個還是1個半,都按順序拼到緩衝區裡,然後你從緩衝區裡找出一個一個完成的結構,這不就完了。
這樣socket就不用再延時了,可以一直髮,效能是有保證的,而我們也省心些,就可以把注意力從讓人頭疼的網路,轉移到對自己記憶體資料的處理上來了。
心跳包的接收處理
請教一下IOCP和執行緒池搭配使用
IOCP在有客戶端連到伺服器的時候,怎麼檢索到有哦幾個客戶端連線到iocp的呢?
使用了IOCP的ECHO程式的疑惑
理解I/O完成埠模型 (感謝 PiggyXP 和 nonocast)
完成埠的構架圖(tcp和udp的討論)
多年前寫的一個iocp應用框架
zougangx blog:
socket IO完成埠模型詳解
完成埠模型IOCP詳解(一)
socket IO完成埠模型詳解(例子)
利用WinInet類進行TCP/IP通訊
CreateIoCompletionPort和完成埠
Windows Sockets 2.0:使用完成埠高效能,可擴充套件性Winsock服務程式
完成埠
用完成埠開發大響應規模的Winsock應用程式
Socket 錯誤分析及錯誤碼
編寫完成埠網路伺服器的一些說明
--------------------------------------------------------------------------------
1. AcceptEx:
BOOL
PASCAL FAR
AcceptEx (
INSOCKET sListenSocket,
INSOCKET sAcceptSocket,
INPVOID lpOutputBuffer,
INDWORD dwReceiveDataLength,
INDWORD dwLocalAddressLength,
INDWORD dwRemoteAddressLength,
OUT LPDWORD lpdwBytesReceived,
INLPOVERLAPPED lpOverlapped
);
用來發起一個非同步的呼叫, 接受客戶端將要發出的連線請求. 與 accept 不同的是, 你必須先手動建立一個 socket 提供給 AcceptEx, 用來接受連線 ( accept 是內在地建立一個 socket 接受連線, 並返回值 ). 而且, accept 建立的 socket 會自動繼承監聽 socket 的屬性, AcceptEx 卻不會. 因此如果有必要, 在 AcceptEx 成功接受了一個連線之後, 我們必須呼叫:
setsockopt( hAcceptSocket, SOL_SOCKET,SO_UPDATE_ACCEPT_CONTEXT, ( char*)&( hListenSocket ), sizeof( hListenSocket ) );
來做到這一點.
AcceptEx 允許在接受連線的同時接收對方發來的第一組資料, 這當然是出於效能的考慮. 但是這時候 AcceptEx 最少要接收到一個位元組的資料才會返回, 一旦碰到惡意連線它就永遠不會返回了. 關閉這項功能的方式是: 把引數 dwReceiveDataLength 至為 0, 開啟則相反. 當然了 , 如果一定要啟用這個功能, 我們也有防禦的辦法. 啟動一個執行緒定時地檢測每一個 AcceptEx 是否已經連線, 連線時間為多久, 以此判斷對方是否是恐怖分子:
int iSecs;
int iBytes = sizeof( int );
getsockopt( hAcceptSocket, SOL_SOCKET, SO_CONNECT_TIME, (char*)&iSecs, &iBytes );
iSecs 為 -1 表示還未建立連線, 否則就是已經連線的時間.
呼叫 AcceptEx 的方式:
#include <Mswsock.h> // for WSAID_ACCEPTEX
typedef BOOL ( WINAPI * PFNACCEPTEX ) (SOCKET, SOCKET, PVOID, DWORD, DWORD, DWORD, LPDWORD, LPOVERLAPPED );
PFNACCEPTEX pfnAcceptEx;
DWORD dwBytes;
GUID guidAcceptEx = WSAID_ACCEPTEX;
::WSAIoctl( hListenSocket,SIO_GET_EXTENSION_FUNCTION_POINTER, &guidAcceptEx, sizeof( guidAcceptEx ),&pfnAcceptEx, sizeof( pfnAcceptEx ), &dwBytes, NULL, NULL );
DWORD uAddrSize = sizeof( SOCKADDR_IN ) +16;
DWORD uDataSize = 0;
BOOL bRes = pfnAcceptEx( hListenSocket,hAcceptSocket, buffer, uDataSize, uAddrSize, uAddrSize, &uAddrSize, (LPWSAOVERLAPPED )overlapped );
其中, buffer 和 overlapped 要根據你自己的用途來定了,這裡只是拿來充數.
一旦 AcceptEx 呼叫完成 ( 通過完成埠通知你 ), 接下來的步驟就是 1. 上面講的SO_UPDATE_ACCEPT_CONTEXT;
2. 將 hAcceptSocket 繫結到完成埠.
--------------------------------------------------------------------------------
2. TransmitFile
TransmitFile 顧名思義是用來進行檔案傳輸的函式, 全自動無需干涉的. 在 Win NT 專業版/家庭版上無法發揮全部效能. 在這裡只討論它的一個功能: 關閉一個 SOCKET 並再次初始化它. 建立一個 SOCKET 是很耗時的, 因此這麼做可以節約大量時間:
#include <Mswsock.h> // for WSAID_TRANSMITFILE
typedef BOOL ( WINAPI * PFNTRANSMITFILE ) (SOCKET, HANDLE, DWORD, DWORD, LPOVERLAPPED, LPTRANSMIT_FILE_BUFFERS, DWORD );
PFNACCEPTEX pfnTransmitFile;
DWORD dwBytes;
GUID guidTransmitFile = WSAID_TRANSMITFILE;
::WSAIoctl( hListenSocket,SIO_GET_EXTENSION_FUNCTION_POINTER, &guidTransmitFile,
sizeof(guidTransmitFile ), &pfnTransmitFile,
sizeof(pfnTransmitFile ), &dwBytes, NULL, NULL );
pfnTransmitFile( hAcceptSocket, NULL, 0, 0,NULL, NULL, TF_DISCONNECT | TF_REUSE_SOCKET );
經過這個函式處理的SOCKET 可以作為接受連線的 socket 提交給 AcceptEx 再次使用. 當這樣的 socket 接受連線成功後, 如果往完成埠上繫結會出錯 - 因為上次接受連線成功時已經繫結過了, 這個錯誤可以忽略.
--------------------------------------------------------------------------------
3. FD_ACCEPT
即使我們在程式啟動時發起了再多的 AcceptEx , 也有可能碰到數目不夠使用者連不上來的情況. 在 Win2000 或更高版本的系統上, 我們可以通過 WSAEventSelect 註冊一個 FD_ACCEPT 事件. 當 AcceptEx 數目不足以應付大量的連線請求時, 這個事件會被觸發. 於是我們就可以發出更多的 AcceptEx, 而且我們還可以抽空辨別一下 AcceptEx 為什麼這麼快就用光了, 是不是碰上攻擊者了( 辨別方法見上文所述 ) ?
HANDLE hAcceptExThreadEvent =::CreateEvent( NULL, TRUE, FALSE, _T("AcceptExThreadEvent") );
::WSAEventSelect( hListenSocket,hAcceptExThreadEvent, FD_ACCEPT );
DWORD WINAPI AcceptExThread( LPVOIDlpParameter )
{
// 負責保證有足夠多的 AcceptEx 可以接受連線請求的執行緒
for( UINT i = 0; i < 10; i ++ ) // 程式啟動時發起的AcceptEx
{
pfnAcceptEx( hListenSocket, ... );
}
while( TRUE )
{
DWORD dwRes =::WaitForSingleObject( hAcceptExThreadEvent, INFINITE );
if( dwRes == WAIT_FAILED )
{
break;
}
::ResetEvent( hAcceptExThreadEvent );
if( m_sbWaitForExit )
{ // 當然, 退出執行緒也是這個 Event 通知
break;
}
pfnAcceptEx( hListenSocket, ... );
//
// ... 在此檢查是否被攻擊
//
}
return 0;
}
要說明的是,WSAEventSelect() 所需的 WSAEVENT 和 CreateEvent() 所建立的 EVENT 是通用的.
--------------------------------------------------------------------------------
4. WSASend 和 WSARecv
預設情況下, 每一個 socket 在系統底層都擁有一個傳送和接收緩衝區.
我們傳送資料時, 實際上是把資料複製到傳送緩衝區中, 然後 WSASend 返回. 但是如果傳送緩衝區已經滿了, 那麼我們在 WSASend 中指定的緩衝區就會被鎖定到系統的非分頁記憶體池中, WSASend 返回 WSA_IO_PENDING. 一旦網路空閒下來, 資料將會從我們提交的緩衝區中直接被髮送出去, 與 " 我們的緩衝區->傳送緩衝區->網路 " 相比節省了一次複製操作.
WSARecv 也是一樣, 接收資料時直接把接收緩衝區中的資料複製出來. 如果接收緩衝區中沒有資料, 我們在 WSARecv 中指定的緩衝區就會被鎖定到系統的非分頁記憶體池以等待資料, WSARecv 返回 WSA_IO_PENDING. 如果網路上有資料來了, 這些資料將會被直接儲存到我們提供的緩衝區中.
如果一個伺服器同時連線了許多客戶端, 對每個客戶端又呼叫了許多 WSARecv, 那麼大量的記憶體將會被鎖定到非分頁記憶體池. 鎖定這些記憶體時是按照頁面邊界來鎖定的, 也就是說即使你WSARecv 的快取大小是 1 位元組, 被鎖定的記憶體也將會是 4k. 非分頁記憶體池是由整個系統共用的 , 如果用完的話最壞的情況就是系統崩潰. 一個解決辦法是, 使用大小為 0 的緩衝區呼叫 WSARecv. 等到呼叫成功時再換用非阻塞的 recv 接收到來的資料, 直到它返回 WSAEWOULDBLOCK 表明資料已經全部讀完. 在這個過程中沒有任何記憶體需要被鎖定, 但壞處是效率稍低.