零字节WSASend,WSARecv
其中lpBuffers,dwBufferCount兩個參數是可以同時為空和0 的.
默認情況下,操作系統為每一個套接字分配兩個緩沖區分別用于緩沖發送數據和接受數據,所謂緩沖就是:
- 應用層的發送數據先拷貝到發送緩沖區,然后再由操作系統發送出去.
- 遠端發過來的數據先放在接受緩沖區,等待應用層調用讀操作,把這些數據取走.
這兩個緩沖區由操作系統管理,并且屬于內核地址空間,是非分頁的(Non-paged pool ).
?
傳統模式下,我們直接調用這個兩個API進行重疊IO操作,并傳遞我們的應用層緩沖區地址,這個時候操作系統典型的處理方法如下:
- 發送:應用層調用WSASend,這個時候如果套接字發送緩沖區由足夠的空間,操作系統將把應用層提交的數據拷貝過來,這個操作立即完成并返回成功.如果套接字的緩沖區滿了,應用層提交的那塊內存就會被操作系統鎖住,并且返回一個WSA_IO_PENDING的錯誤,在發送緩沖區的數據處理完畢后,操作系統將直接將發送應用層緩沖區的數據,不再拷貝到緩沖區,這個時候應用層將會收到完成通知.如果緩沖區仍然有空間,但是也不夠存放應用層請求的數據,仍然按照前一種情況處理.
- 接收:應用層調用WSARecv,很有已經有數據在套接字接收緩沖區中了,這個時候操作系統會直接將數據拷貝過來,這個調用將返回成功,同時系統投遞一個完成通知,應用在處理這個通知的時候將會知道本次操作的數據量.另一種情況是接受緩沖區沒有數據,應用層提交的緩沖區就會被鎖住,并且得到WSA_IO_PENDING錯誤,一旦這個連接收到了數據,操作系統會直接把數據拷貝用戶緩沖區,并發出完成通知.
無論是發還是收,一旦應用層內存被鎖住,這塊內存就不能從物理內存分頁出去.操作系統會限制這些被鎖住的內存的數量,一旦達到這個限制,就會返回WSAENOBUFS錯誤.如果應用層在每一個連接上發起大量重疊IO請求,隨著連接數的增長,很可能就達到這個限制的值.一方面是因為重疊IO操作數量上的增長,另一方面是因為當前系統的分頁單位是固定的,即使應用層只有一個字節的操作請求,操作系統仍然需要付出一頁(一般是4K)的代價.
?如果服務器希望能處理非常多并發連接,可以在每個連接的讀請求時投遞一個0字節的讀操作,即在WSARecv的時候為lpBuffers和 dwBufferCount分別傳遞NULL和0參數.這樣做就不會存在內存鎖定帶來的資源緊張問題,因為沒有內存需要被鎖定,一旦有數據被收到,操作系 統就會投遞完成通知.這個時候服務端就可以去套接字接受緩沖區取數據了,有兩種方法可以得知到底有多少數據可以讀,一種是通過ioctlsocket結合 FIONREAD參數去"查詢",另一種就是一直讀,直到得到WSAEWOULDBLOCK錯 誤,就表示沒有數據可讀了.另一方面在發送數據的時候,仍然可以采用這種方案,原因在于對端的應用可能效率非常低下,或者陷入了某個死循環,導致對方的網 絡IO層遲遲不調用recv/WSARecv,受TCP協議本身的限制,服務端需要發送的數據就會一直PENDING,進而導致內存被內核鎖住.采用0字 節發送方式后,應用層先投遞一個空的WSASend,表示希望發送數據,操作系統一旦判斷這個連接可以寫了,會投遞一個完成通知,此時便可以放心投遞數 據,并且發送緩沖區的大小是可知的,不會存在內存鎖定的問題.
這種方案適合最大化并發量,但也存在短處,首先就是數據發送和接受的時候有一個數據拷貝的代價,從網絡上收到的數據 并不是直接放到應用層提交的緩沖區里.另外一個代價就是每一次讀和寫要經過一個先請求后實施的操作,而傳統的方案是要一步到位.但正是這些差異避免了對系 統資源嚴重占用.
提到windows平臺上的高性能IO操作,就不得不提IOCP(完成端口),上面的方案是完全適合ICOP模型 的.順帶提一下在這種模型下對同一個套接字投遞多個讀和寫操作的情況,IOCP可以保證多個同一個句柄上的多個重疊操作在數據處理上是有序的,也就是說先 提交的重疊操作先處理,但是不保證你收到的完成通知是有序的.
總結
以上是生活随笔為你收集整理的零字节WSASend,WSARecv的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: getsockname与getpeern
- 下一篇: IOCP中在WSASend以及WSARe