高性能服务器开发-iocp
生活随笔
收集整理的這篇文章主要介紹了
高性能服务器开发-iocp
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
在windows下開發高性能服務器,通常采用的是iocp的模式。下面討論幾個常見的問題、解決方式。
1、WSANOBUF問題
假如我們有10000個客戶端socket連接,為了接收他們發送過來的數據,我們需要預先投遞10000個WSARecv。 假如每個異步讀需要應用層程序員提供10k的緩沖區,則一共需要的用戶緩沖區為 10000*10k=97M 內存。windows要求這97M數據必須被OS“鎖定”,意思大體是需要占用大量的OS的資源了。所以程序很可能會因為10000個客戶同時連接,而耗盡資源。WSAENOBUF錯誤同此有關。 解決方法是投遞0字節數請求的WSARecv。偽代碼如下:WSABUF DataBuf; DataBuf.len=0; DataBuf.buf=0; WSARecv(socket, &DataBuf, 1,...); 當有數據到來時,這個異步IO會從角色2線程中得到結果。由于它是0字節的讀,所以它沒有觸碰任何socket緩沖區的到來的任何數據。我們付出很小的成本(大約每個連接節省了10k)就能知道哪個客戶端的數據到來了。別小看了每個連接節省了這么點資源,連接數大了節約的總量就很可觀了。如果客戶端數量很少,這個技巧就沒什么意思了。
2、OnOrderRead的問題
3、多次投遞的處理
4、如何優雅的殺死線程(或者退出iocp)
PostQueuedCompletionStatus函數會向{IOCP完成隊列C}中push進去一條記錄。這樣角色2線程就能獲得這個“虛偽或模擬”的異步IO完成事件。為什么要“假冒”一條{IOCP完成隊列C}的條目呢?用處嗎,程序員自己去想吧(意思是用處多多了)。一般來說,我們用它“優雅的殺死角色2線程”。偽代碼如下:typedef struct { ???OVERLAPPED Overlapped; ???OP_CODE op_type;? ???... } PER_IO_DATA; PER_IO_DATA* PerIOData = ... PerIOData->op_type = OP_KILL; //操作類型是殺死線程 PostQueuedCompletionStatus(...PerIOData...);? //如果有N個角色2線程,則需要調用N次,這樣{IOCP完成隊列C}中才能有N個這個的條目
5、getqueuedcompletionstatus()是否需要互斥?
這個不需要的。
6、錯誤處理:包含995、64之類的
GetQueuedCompletionStatus函數的錯誤處理比較復雜。1 如果GetQueuedCompletionStatus返回false: 1.1 如果Overlapped指針非空 ????恭喜你,你投遞的異步IO獲得結果了,只不過是失敗的結果。好孬也終于回來個信兒了。 ????這可能是socket連接斷了等等。 ????1.1.1 如果GetLastError獲得的錯誤號為ERROR_OPERATION_ABORTED ??????????一定是有東西調用了CancelIO(socket)了。所有同這個socket相關的異步IO請求都會被取消。 ????1.1.2 如果GetLastError 獲得的錯誤號為其他的東西 ??????????可能是IO沒成功,如socket連接斷開了等等。 1.2 如果Overlapped指針空 ????這可不是好消息,因為這意味著IOCP本身有重大故障了。比如我們意外的把IOCP的句柄CloseHandle了。 ????1.2.1 如果GetLastError獲得的錯誤號為WAIT_TIMEOUT ??????????可能GetQueuedCompletionStatus設置的超時參數dwMilliseconds不是INFINITE。我們繼續調用GetQueuedCompletionStatus重新等待吧。 ????1.2.1 如果GetLastError獲得的錯誤號ERROR_ABANDONED_WAIT_0, 或者其他 ??????????IOCP本身都完蛋了,角色2線程應另找東家了,或者就地自我了斷算了。 2 如果GetQueuedCompletionStatus返回true: ??恭喜你,異步IO成功了。 ??通過lpNumberOfBytes, lpCompletionKey, and lpOverlapped這三個參數獲得詳細信息。 ??lpNumberOfBytes:實際傳輸的字節數。(可能比需要傳輸的字節數少) ??lpCompletionKey:這就是著名的PerHandleData,可以知道這是哪個socket連接的。 ??lpOverlapped: ??這就是著名的PER_IO_DATA, 同某次異步IO調用關聯, ????????比如某次WSASend(Overlapped參數=0x123)調用,這里能重新拿到lpOverlapped==0x123。 我們可以根據這個指針,得知這個IO結果是對應著哪次WSASend()調用的結果。 ????????
我滿以為這個錯誤處理天衣無縫,直到有一次測試。我對一個socke投遞了100個WSARecv。當我故意把客戶端關閉后,這些異步IO不出意外的都在角色2線程的GetQueuedCompletionStatus函數處獲得結果了。令我吃驚的是,GetQueuedCompletionStatus返回為TRUE!!!,并且GetLastError()返回值是0!!! 令我欣慰的是lpNumberOfBytes值為0(否則真見鬼了)。所以看到GetQueuedCompletionStatus返回true,不要高興的太早了。
2.1 把lpOverlapped指針解釋成PER_IO_DATA數據結構。如果PerIOData->op_type == OP_KILL,可能這個是PostQueuedCompletionStatus偽造的一個IO完成事件。 2.2 判斷是否(lpNumberOfBytes==0)。如果這個IO結果的確是某個WSAxxx()的結果,而不是PostQueuedCompletionStatus偽造的,則這個IO對應的socket可能斷了。 2.3 (lpNumberOfBytes>0) ,這才是真正的IO完成的事件呢。可能99.9%的機會,分支跑到這里的。 ? 【在同一個socket上一次投遞多個異步IO】 一次投遞多個WSASend(1234,&Buff1,...); WSASend(1234,&Buff2,...); ... 好像沒問題。 如果一次投遞多個WSARecv(1234,&Buff1,...);WSARecv(1234,&Buff2,...);好像有些需要闡明的問題。
第一:Windows保證按照你投遞WSARecv的順序,把網絡上到達的數據按先后順序放入Buff1,Buff2。 ??????如果網絡上到來的數據為 AAAAUUUU, 假設Buff1長度4,Buff2長度4, ??????則保證Buff1獲得AAAA,Buff2獲得UUUU 第二:如果有多個角色2線程,可能由于線程調度的“競爭條件race condition”, ??????某線程首先執行Buff2的完成處理過程。 ??????如果我在角色2線程中,打印出收到的數據,可能打印出如下結果:UUUUAAAA。這絕不是違反了TCP協議, ??????而是多線程的問題。其實解決方案很簡單。說者費事,上偽代碼 typedef struct { ???OVERLAPPED Overlapped; ???... ???int Package_Number; //我對每一次IO,夾帶本次調用順序號 ???... } PER_IO_DATA;
PER_IO_DATA* PerIOData1=... PerIOData1->Package_Number = 1 ; //第一次調用 WSARecv(1234, &Buff1,...PerIOData1...);
PER_IO_DATA* PerIOData2=... PerIOData1->Package_Number = 2 ; //第二次調用 WSARecv(1234, &Buff2,...PerIOData2...);
我們需要維護某種數據結構,記住我們發出了兩個WSARecv。 當收到IO結果后,程序需要判斷,只有1,2兩個調用都從角色2線程獲得結果后,才能按順序把Buff1和Buff2拼接,就是符合順序的AAAAUUUU。當然,還有其他更好的方式,這里只展示基本原理。
第三:真有必對同一個socket一次投遞多個WSARecv嗎? ??????這個問題同【IOCP系統資源耗盡的問題】,不矛盾。我們假設在投遞多個WSARecv時,已經預見到網絡上將到來某個socket的大量數據。?根據網絡資料介紹,這樣可以充分發揮多CPU并發運算的能力。我想在雙核CPU機器上,一個CPU處理Buff1,同時另一個CPU處理Buff2。 ??????如果是少量客戶端連接,每個連接可能突然發生大量數據的傳送,這個做法可能能加快從Socket緩沖區拷貝數據到應用程序Buff的速度(個人揣測)。 ??????如果是大量客戶端(10000)連接,每個連接傳送的數據量很少,這個做法我個人認為沒什么意義。我想CPU數量就2個,不會輕易就閑下來吧? ??????有一個重要原因,需要投遞多個buffer給windows。假如我預計到某個socket一次傳過來2M的數據,而 我沒有2M大小的buffer,我只有1M大小的buffer。我需要先調用一次WSARecv,等待收完這1M數據后,再發一個 WSARecv。或者我用其他方法,提供給windows系統2個1M的buff。
第四:假設我們真需要一次投遞多個Buff,接收數據,有必要用多次WSARecv調用嗎? ??????這里有個可能的替代做法,上偽代碼: ??????char *raw1 = new char[BUFF_SIZE]; ??????WSABUF[2] wsabuf; ??????wsabuf[0].buf?= raw1 ; ??????wsabuf[0].len = BUFF_SIZE;
??????char *raw2 = new char[BUFF_SIZE]; ??????wsabuf[1].buf?= raw2 ; ??????wsabuf[1].len = BUFF_SIZE;
??????WSARecv(1234, &wsabuf, 2 ... ); ? ??????//重點在參數2上,指示了WSABUF結構體的個數是2個。一般大量IOCP的例子里這個參數都是1 ?????? ??????這個方法我認為更簡單,不知道是我自己“2”還是網上的其他人“2”,一次發出多個WSARecv,把這些分散的IO收集起來也是費事的事。UNIX系統的scatter-gather IO類似于這個機制 ? 結論:如果出現995等錯誤,就可能是socket本身出現了問題,這時候需要在錯誤的地方將這個socket釋放; 7、如何釋放overlapped的數據、防止多次釋放?? 如果出現錯誤,我們在App層釋放,但這個消息會在getqueuedcompletionstatus()也出現,這樣就可能出現多次釋放,產生錯誤。 解決方式是:統一處理資源的釋放,在getqueuedcompletionstatus()來做處理。 ? 8、accept, wsaaccept, acceptex的區別? 經常在win下面做網絡通信的開發,除了accept外還真是很少用另外2個。看來是有必要了解清楚。 簡單的理解為: accept: 遵循bsd規范,是標準的bsd socket。僅僅是接受連接來的socket,是同步的操作; WSAAccept: 是微軟對accept的封裝,提供了條件判斷函數,也是同步操作; AcceptEx:支持先創建socket,然后associate;支持一次Accept同時能得到客戶端發送來的第一次數據;支持IOCP,異步操作。
總結
以上是生活随笔為你收集整理的高性能服务器开发-iocp的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 勒索病毒傀儡进程脱壳
- 下一篇: 异步socket优雅的关闭-Cancel