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:
- Call WSAAsyncSelect to register for FD_CLOSE notification.
- Call shutdown with how=SD_SEND.
- When FD_CLOSE received, call recv until zero returned, or SOCKET_ERROR.
- 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狀態?