IOCP相關的一些總結

1:在IOCP中投遞WSASend返回WSA_IO_PENDING的時候,表示異步投遞已經成功,但是稍後發送纔會完成。這其中涉及到了三個緩衝區。
網卡緩衝區,TCP/IP層緩衝區,程序緩衝區。
情況一:調用WSASend發送正確的時候(即立即返回,且沒有錯誤),TCP/IP將數據從程序緩衝區中拷貝到TCP/IP層緩衝區中,然後不鎖定該程序緩衝區,由上層程序自己處理。TCP/IP層緩衝區在網絡合適的時候,將其數據拷貝到網卡緩衝區,進行真正的發送。
情況二:調用WSASend發送錯誤,但是錯誤碼是WSA_IO_PENDING的時候,表示此時TCP/IP層緩衝區已滿,暫時沒有剩餘的空間將程序緩衝區的數據拷貝出來,這時系統將鎖定用戶的程序緩衝區,按照書上說的WSASend指定的緩衝區將會被鎖定到系統的非分頁內存中。直到TCP/IP層緩衝區有空餘的地方來接受拷貝我們的程序緩衝區數據才拷貝走,並將給IOCP一個完成消息。
情況三:調用WSASend發送錯誤,但是錯誤碼不是WSA_IO_PENDING,此時應該是發送錯誤,應該釋放該SOCKET對應的所有資源。

2:在IOCP中投遞WSARecv的時候,情況相似。
情況一:調用WSARecv正確,TCP/IP將數據從TCP/IP層緩衝區拷貝到緩衝區,然後由我們的程序自行處理了。清除TCP/IP層緩衝區數據。
情況二:調用WSARecv錯誤,但是返回值是WSA_IO_PENDING,此時是因爲TCP/IP層緩衝區中沒有數據可取,系統將會鎖定我們投遞的WSARecv的buffer,直到TCP/IP層緩衝區中有新的數據到來。
情況三:調用WSARecv錯誤,錯誤值不是WSA_IO_PENDING,此時是接收出錯,應該釋放該SOCKET對應的所有資源。

在以上情況中有幾個非常要注意的事情:
系統鎖定非分頁內存的時候,最小的鎖定大小是4K(當然,這個取決於您系統的設置,也可以設置小一些,在註冊表裏面可以改,當然我想這些數值微軟應該比我們更知道什麼合適了),所以當我們投遞了很多WSARecv或者WSASend的時候,不管我們投遞的Buffer有多大(0除外),系統在出現IO_PENGDING的時候,都會鎖定我們4K的內存。這也就是經常有開發者出現WSANOBUF的情況原因了。

我們在解決這個問題的時候,要針對WSASend和WSARecv做處理
1:投遞WSARecv的時候,可以採用一個巧妙的設計,先投遞0大小Buf的WSARecv,如果返回,表示有數據可以接收,我們開啓真正的recv將數據從TCP/IP層緩衝區取出來,直到WSA_IO_PENGDING.
2:對投遞的WSARecv以及WSASend進行計數統計,如果超過了我們預定義的值,就不進行WSASend或者WSARecv投遞了。
3:現在我們應該就可以明白爲什麼WSASend會返回小於我們投遞的buffer空間數據值了,是因爲TCP/IP層緩衝區小於我們要發送的緩衝區,TCP/IP只會拷貝他剩餘可被Copy的緩衝區大小的數據走,然後給我們的WSASend的已發送緩衝區設置爲移走的大小,下一次投遞的時候,如果TCP/IP層還未被髮送,將返回WSA_IO_PENGDING。
4:在很多地方有提到,可以關閉TCP/IP層緩衝區,可以提高一些效率和性能,這個從上面的分析來看,有這個可能,要實際的網絡情況去實際分析了。

 


==================

關於數據包在應用層亂序問題就不多說了(IOCP荒廢了TCP在傳輸層辛辛苦苦保證的有序)。

這無關緊要,因爲iocp要管理上千個SOCKET,每個SOCKET的讀請求、寫請求分別保證串行即可。

 

=============

關於GetQueuedCompletionStatus的返回值判斷:

我給超時值傳的是0,直接測試,無須等待。

這裏我們關心這幾個值:

第二個參數所傳回的byte值

第三個參數所傳回的complete key值 ——PER HANDLE DATA

第四個參數所傳回的OVERLAPPED結構指針 ——PER IO DATA

系統設置的ERROR值。

 

超時情況下,byte值返回0,per handle data值是-1,per io data爲NULL

 

1.如果返回FALSE

    one : iocp句柄在外部被關閉。

   WSAGetLastError返回6(無效句柄),byte值返回0,per handle data值是-1,per io data爲NULL

 

    two: 我們主動close一個socket句柄,或者CancelIO(socket)(且此時有未決的操作)

    WSAGetLastError返回995(由於線程退出或應用程序請求,已放棄 I/O 操作)

   byte值爲0,

   per handle data與per io data正確傳回。

 

   three:對端強退(且此時本地有未決的操作)

   WSAGetLastError返回64(指定的網絡名不再可用)

  byte值爲0,per handle data與per io data正確傳回 

 

2.如果返回TRUE【此時一定得到了你投遞的OVERLAP結構】

    one:  我接收到對端數據,然後準備再投遞接收請求;但此期間,對端關閉socket。

   WSARecv返回錯誤碼10054:遠程主機強迫關閉了一個現有的連接。

TODO TODO

   從網上搜到一個做法,感覺很不錯:

如果返回FALSE, 那麼:如果OVERLAP爲空,那一定是發生了錯誤(注意:請排除TIMEOUT錯誤);

如果OVERLAP不爲空,有可能發生錯誤。不用管它,這裏直接投遞請求;如果有錯,WSARecv將返回錯誤。關閉連接即可。

 

 

============

關於closesocket操作:

 

 The closesocket function will initiate cancellation on the outstanding I/O operations, but that does not mean that an application will receive I/O completion for these I/O operations by the time the closesocket function returns. Thus, an application should not cleanup any resources (WSAOVERLAPPED structures, for example) referenced by the outstanding I/O requests until the I/O requests are indeed completed.

 

在IOCP模式下,如果調用closesocket時有未決的pending   IO,將導致socket被重置,所以有時會出現數據丟失。正統的解決方式是使用shutdown函數(指定SD_SEND標誌),注意這時可能有未完成的發送pengding   IO,所以你應該監測是否該連接的所有是否已完成(也許你要用一個計數器來跟蹤這些pending   IO),僅在所有send   pending   IO完成後調用shutdown。

 

MSDN推薦的優雅關閉socket:

  1. Call WSAAsyncSelect to register for FD_CLOSE notification.
  2. Call shutdown with how=SD_SEND.
  3. When FD_CLOSE received, call recv until zero returned, or SOCKET_ERROR.
  4. Call closesocket.

 

 FD_CLOSE being posted after all data is read from a socket. An application should check for remaining data upon receipt of FD_CLOSE to avoid any possibility of losing data.

 

 

 

 

 

 

 

 

對每個使用AcceptEx接受的連接套結字使用setsockopt設置SO_UPDATE_ACCEPT_CONTEXT選項,這個選項原義是把listen套結字一些屬性(包括socket內部接受/發送緩存大小等等)拷貝到新建立的套結字,卻可以使後續的shutdown調用成功。

 

/* SO_UPDATE_ACCEPT_CONTEXT is required for shutdown() to work fine*/

       setsockopt( sockClient,

                            SOL_SOCKET,

                            SO_UPDATE_ACCEPT_CONTEXT,

                            (char*)&m_sockListen,

                            sizeof(m_sockListen) ) ;

如果是調用AcceptEX接收的連接 不設置該選項的話,隨後的shutdown調用
將返回失敗, WSAGetLastError() returns 10057 -- WSANOTCONN 


2012.10.24

用智能指針重構了網絡庫,替換了裸指針。

但是發現IOCP如下一個問題:


1. 收到14字節數據
2012-10-25[02_02_05_906[DBG]:OnRecv : Worker thread [6268], socket = 11256, bytes = 14

2.再次投遞RECV請求,發生錯誤,因爲對端已經關閉
2012-10-25[02_02_05_906[DBG]:Fatal error when post recv, error 10054, socket = 11256
2012-10-25[02_02_05_906[DBG]:Socket is set invalid 11256

3.於是準備回收資源,結束RECV請求;
2012-10-25[02_02_05_906[DBG]:EndRecv : Worker thread [6268], socket = 11256

4.但此時overlap結構仍然是掛起狀態?
2012-10-25[02_02_05_906[DBG]:2EndRecv socket 11256, now recv overlappe is complete ? 0


真是奇怪,明明投遞RECV操作失敗,爲什麼還要將PER_IO_DATA置爲STATUS_PENDING狀態?

發佈了38 篇原創文章 · 獲贊 0 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章