Windows SOCKET 緩存/緩衝區 相關了解

Windows NT和Windows 2000的套接字架構

對於開發大響應規模的Winsock應用程序而言,對Windows NT和Windows 2000的套接字架構有基本的瞭解是很有幫助的。

與其他操作系統不同的是,WinNT和Win2000的傳輸協議層並不直接給應用程序提供socket風格的接口,不接受應用程序的直接訪問。而是實現了更多的通用API,稱爲傳輸驅動接口(Transport Driver Interface,TDI).這些API把WinNT的子系統從各種各樣的網絡編程接口中分離出來。然後,通過Winsock內核模式驅動提供了sockets方法(在AFD.SYS裏實現)。這個驅動負責連接和緩衝管理,對應用程序提供socket風格的編程接口。AFD.SYS則通過TDI和傳輸協議驅動層交流數據。

 

1:誰來負責管理緩衝區?

如上所說,對於使用socket接口和傳輸協議層交流的應用程序來說,AFD.SYS負責緩衝區的管理。也就是說,當一個程序調用send或WSASend函數發送數據的時候,數據被複制到AFD.SYS的內部緩衝裏(大小根據SO_SNDBUF設置),然後send和WSASend立刻返回。之後數據由AFD.SYS負責發送到網絡上,與應用程序無關。當然,如果應用程序希望發送比SO_SNDBUF設置的緩衝區還大的數據,WSASend函數將會被堵塞,直到所有數據均被髮送完畢爲止。

同樣,當從遠地客戶端接受數據的時候,如果應用程序沒有提交receive請求,而且線上數據沒有超出SO_RCVBUF設置的緩衝大小,那麼AFD.SYS就把網絡上的數據複製到自己的內部緩衝保存。當應用程序調用recv或WSARecv函數的時候,數據即從AFD.SYS的緩衝複製到應用程序提供的緩衝區裏。

在大多數情況下,這個體系工作的很好。尤其是應用程序使用一般的發送接受例程不牽涉使用Overlapped的時候。開發人員可以通過使用setsockopt API函數把SO_SNDBUF和SO_RCVBUF這兩個設置的值改爲0關閉AFD.SYS的內部緩衝。但是,這樣做會帶來一些後果:

 

舉例來說,一個應用程序把SO_SNDBUF爲0把緩衝區(指AFD.SYS裏的緩衝)關閉,然後發出一個同步堵塞send()調用。在這樣的情況下,系統內核會把應用程序的緩衝區鎖定,直到接收方確認收到了整個緩衝區後send()調用才返回。似乎這是一種判定你的數據是否已經爲對方全部收到的簡潔的方法,實際上卻並非如此,這是很糟糕的。問題在於,即使遠端TCP通知數據已經收到,其實也根本不代表數據已經成功送給客戶端應用程序,比如對方可能發生資源不足的情況,導致AFD.SYS不能把數據拷貝給應用程序。另一個更要緊的問題是,在每個線程中每次只能進行一次發送調用,效率極其低下。

如果關閉接受緩衝(設置SO_RCVBUF的值爲0),也不能真正的提高效率。接受緩衝爲0迫使接受的數據在比winsock內核層更底層的地方被緩衝,同樣在調用recv的時候進行才進行緩衝複製,這樣你關閉AFD緩衝的根本意圖(避免緩衝複製)就落空了。關閉接收緩衝是沒有必要的,只要應用程序經常有意識的在一個連接上調用重疊WSARecvs操作,這樣就避免了AFD老是要緩衝大量的到來數據。

到這裏,我們應該清楚關閉緩衝的方法對絕大多數應用程序來說沒有太多好處的了。只要要應用程序注意隨時在某個連接上保持幾個WSARecvs重疊調用,那麼通常沒有必要關閉接收緩衝區。如果AFD.SYS總是有由應用程序提供的緩衝區可用,那麼它將沒有必要使用內部緩衝區。

    一個高性能的服務程序可以關閉發送緩衝,而不影響性能。這樣的程序必須確保它在同時執行多個重疊發送調用,而不是等待某個重疊發
送結束之後才執行另一個。這樣如果一個數據緩衝區數據已經被提交,那麼傳輸層就可以立刻使用該數據緩衝區。如果程序“串行”的執行Overlapped發送,就會浪費一個發送提交之後另一個發送執行之前那段時間。
2:資源的限制條件

強健性是每一個服務程序的一個主要設計目標。就是說,服務程序應該可以對付任何的突發問題,比如,客戶端請求的高峯,可用內存的暫時貧缺,以及其他可靠性問題。爲了平和的解決這些問題,開發人員必須瞭解典型的WindowsNT和Windows2000平臺上的資源約束。

最基本的問題是網絡帶寬。通常,使用用戶數據報協議(UDP)的應用程序都可能會比較注意帶寬方面的限制,以最大限度地減少包的丟失。
然而在使用TCP連接,服務器必須十分小心地制好注意不要濫用網絡資源。否則,TCP連接中將會出現大量重發和連接取消事件。具體的帶寬控制是跟具體程序相關的,超出了本文的討論範圍。

 

虛擬內存的使用也必須很小心地管理。通過謹慎地申請和釋放內存,或者應用lookaside lists(一個記錄申請並使用過的“空閒”內存的緩衝區)這樣可以使服務程序避免過多的反覆申請內存,並且保證系統中一直有儘可能多的空餘內存。(應用程序還可以使用SetWorkingSetSize這個Win32API函數來向系統請求增加該程序可用的物理內存。)

 

使用Winsock時還可能碰到另外兩個非直接的資源不足情況。

第一個是頁面鎖定限制。無論應用程序發起send還是receive操作,也不管AFD.SYS的緩衝是否被禁止,數據所在的緩衝都會被鎖定在物理內存裏。因爲內核驅動要訪問該內存的數據,在訪問期間該內存區域都不能交換出去(解鎖)。在大部分情況下,這不會產生任何問題。但是操作系統必須確認還有可用的可分頁內存來提供給其他程序。這樣做的目的是防止一個有錯誤操作的程序請求鎖定所有的物理RAM,而導致系統崩潰。這意味着,應用程序必須有意識的避免導致過多頁面鎖定,使該數量達到或超過系統限制。

在WinNT和Win2000中,系統允許的總共的內存鎖定的限制大概是物理內存的1/8。這只是粗略的估計,不能作爲一個準確的計算數據。只是需要知道,有時重疊IO操作會發生ERROR_INSUFFICIENT_RESOURCE失敗,這是因爲可能同時有太多的send/receives操作在進行中。程序應該注意避免這種情況。

另一個的資源限制情況是,程序運行時,系統達到非分頁內存池的限制。所謂非分頁池是一塊永遠不被交換出去的內存區域。WinNT和Win2000的驅動從指定的非分頁內存池中申請內存。這個區域裏分配的內存不會被扇出,因爲它包含了多個不同的內核對象可能需要訪問的數據,而有些內核對象是不能訪問已經扇出的內存的。一旦系統創建了一個socket (或打開一個文件),一定數目的非分頁內存就被分配了。另外,綁定(binding)和連接socket也會導致額外的非分頁內存池的分配。更進一步的說,一個I/O請求,比如send或receive,也是分配了很少的一點非分頁內存池的(爲了跟蹤I/O操作的進行,包含必須信息的一個很小的結構體被分配了)。積少成多,最後還是可能導致問題。因此操作系統限制了非分頁內存的數量。在winNT和win2000平臺上,每個連接分配的非分頁內存的準確數量是不相同的,在未來的windows版本上也可能保持差異。如果你想延長你的程序的壽命,就不要打算在你的程序中精確的計算和控制你的非分頁內存的數量。

 

雖然不能準確計算,但是程序在策略上要注意避免衝擊非分頁限制。當系統的非分頁池內存枯竭,一個跟你的程序完全無關的的驅動都有可能出問題,因爲它無法正常的申請到非分頁內存。最壞的情況下,會導致整個系統崩潰。比如那些第三方設備或系統本身的驅動。切記:在同一臺計算機上,可能還有其他的服務程序在運行,同樣在消耗非分頁內存。開發人員應該用最保守的策略估算資源,並基於此策略開發程序。

資源約束的解決方案是很複雜的,因爲事實上,當資源不足的情況發生時,可能不會有特定的錯誤代碼返回到程序。程序在調用函數時可能可以得到類似WSAENOBUFS或

ERROR_INSUFFICIENT_RESOURCES的這種一般的返回代碼。如何處理這些錯誤呢,首先,合理的增加程序的工作環境設置(Working set,如果想獲得更多信息,請參考MSDN裏John Robbins關於 Bugslayer的一章)。如果仍然不能解決問題,那麼你可能遇上了非分頁內存池限制。那麼最好是立刻關閉部分連接,並期待情況恢復正常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章