OverLapped I/O Socket 的問題

OverLapped I/O Socket 的問題,請教了!-Delphi知識大全
 wsasend 異步投遞一個發送請求,爲了簡單lpBuffers 參數只用了一個wsabuf結構,如果一次投遞一個50M左右的請求可以成功,但是當我投遞一個200M的請求時返回
WSAENOBUFS(10055)錯誤,大概意思是“windows socket提供者報告一個緩衝區死鎖”
我想應該是請求太大了,我的問題是:
1、我是應該把這個大塊數據分解,然後投遞若干個 wsasend請求呢還是將數據分解
到若干個wsabuf數組元素中一次投遞?主要還是對lpBuffers這個參數的意義沒理解。:(
2、不管是那種方法,數據分解成多大合適?最大值是多少?什麼地方有相關的解釋?

1:呵呵,都會有這個過程的。
可以多次提交多個異步請求,大小自己測一下就知道了,並不是BUF越大速度越快。根據網絡情況來測一下吧。
2:誰能給解釋一下“windows socket提供者報告一個緩衝區死鎖”是什麼意思,什麼原因引起的?
我感覺異步i/o好象不應該限制請求的大小,如果有限制的話應該在相關文檔中有說明啊
感興趣的朋友幫忙頂一下,謝謝!
3:啊,迷糊:
怎麼我這裏 10055 錯誤的說明是:“系統緩衝區空間不足或隊列已滿,不能執行套接字上的操作。”呀,有什麼區別不?
4:我也搞不清楚,我是在wsasend出錯後getlasterror返回10055,就是WSAENOBUFS,對於WSAENOBUFS 錯誤msdn中的解釋是這樣的
WSAENOBUFS The Windows Sockets provider reports a buffer deadlock.
5:啊,那你就按我上面的解釋處理吧,是同一個錯誤的不同回答,相對來講,我這個更明確些。
6:異步發送應該是把數據交給系統內核去發送,如果系統緩衝區空間不足或隊列已滿,那麼內核應該等待接受方接受數據騰出系統緩衝區繼續發送啊,怎麼會返回錯誤完事呢?
是不是我的理解錯誤,這個概念比較迷糊
7:緩衝區死鎖
在網絡中可能形成死鎖狀態的資源是緩衝區。

• 當一個方向傳輸的分組佔用了太多的緩衝資源時必然影響其他方向的分組有序流動,最終造成死鎖。

三種死鎖形式:

• 最簡單的一種死鎖是直接存儲—轉發死鎖。解決的方法是:如果不允許結點中的緩衝區全部分配給一個傳輸方向,或者對每一 3 傳輸方向都分配固定大小的緩衝區,這種死鎖就不會發生。

• 另外一種死鎖是間接存儲—轉發死鎖。解決方法:採用結構化的緩衝池技術可防止發生這種死鎖。在擁擠的民政部下,“低級的”分組被丟棄,網絡儘量把“高級的”分組送往它們的目的地。

• 最後的一種死鎖是重裝配死鎖。這種死鎖在 ARPANET 這樣的數據報網絡中最容易出現。 ARPANET 採用的緩衝區管理方法稱爲最小分配是最大限制的共享分配法。
8:(10055)
No buffer space available.
An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full.
9:asf.sys會把你的緩衝區鎖定爲未分頁內存,未分頁的理論最大值就是你的物理內存最大值
10:有意思,收藏!
11:to 元元她哥:
“asf.sys會把你的緩衝區鎖定爲未分頁內存”這句話不知怎麼來理解。我的理解因爲有
so_sndbuf 這個值的限制,asf.sys不應該把我的緩衝區全部緩衝到非分頁內存中啊。
另外說明一下,我投遞的緩衝區是一個映像文件,並且接受端接收到數據之後需要一個
確認,不知道這些因素是否有影響。
大蝦們,是時候出手了!!!
12:爲了提高高帶寬、高延遲網絡的性能,Windows 2000 TCP 支持RFC 1323 中定義的TCP 窗口縮放。通過在TCP 三次握手期間商定一個窗口縮放因子,支持T C P 接收窗口的大小可大於64KB 。它支持的接收窗口最大可達1GB 。
窗口縮放因子只在三次握手的前兩個段中出現。縮放因子是2的s次方,其中s 爲商定縮放因子。例如,對縮放因子爲3 的通告窗口大小65535,實際接收窗口大小爲524280 即8×65535 。

T C P 窗口縮放在缺省時是啓用的,並且只要連接的TCP窗口大小值設定爲大於6 4 K B 就自動使用

慢啓動算法
Windows 2000 TCP 支持慢啓動和擁塞避免算法。一個連接建立後,T C P 起先只緩慢地發送數據來估計連接的帶寬,以避免淹沒接收主機或通路上的其他設備和鏈路。發送窗口大小設爲2 個T C P 段,當兩個段都被應答後,窗口大小就擴大爲三個段。當三個段都被應答後,發送窗口大小再次擴大。如此進行,直到每次突發傳輸的數據量達到遠程主機所聲明的接收窗口大小。此時,慢啓動算法就不再用了,改用聲明的接收窗口進行流控制。

以上是在《Windows 2000 Server資源大全第3卷TCP/IP連網核心技術》摘下來的。

可以用網絡監視器監聽一下你的通信裏TCP SYN 段中的窗口縮放選項。看看它的發送窗口有多大。很可能是發送200M時,系統申請的窗口過大吧。
13:補充一句:我不是大蝦! [^][^]
14:to painboy:
按照你提供的資料理解,窗口的大小是逐漸擴大的,那麼出錯也應該是在發送過程中發生,但是我這裏的現象是wsasend 直接就返回錯誤了。
另外滑窗的大小也應該和接收端的receive的速度有關係,但是我的接收方還沒有執行receive,所以我覺得滑窗不可能太大。或者我對異步io流程的理解不對。
15:相信以下內容就是你想要的 :)


Socket 體系結構

Winsock2.0規範支持多種協議以及相關的支持服務。這些用戶模式服務支持可以基於其他現存服務提供者來擴展他們自己的功能。比如,一個代理層服務支持(LSP)可以把自己安裝在現存的TCP/IP服務頂層。這樣,代理服務就可以截取和重定向一個對底層功能的調用。

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

緩衝區由誰來管理

如上所說,對於使用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函數不會返回,直到連接的另一端收到整個緩衝區的數據爲止。這貌似一種挺不錯的方法,用來判斷是否你的數據已經被對方全部收取。但實際上,這是很糟糕的。問題在於:網絡層即使收到遠端TCP的確認,也不能保證數據會被安全交到客戶端應用程序那裏,因爲客戶端可能發生“資源不足”等情況,而導致應用程序無法從AFD.SYS的內部緩衝複製得到數據。而更重大的問題是:由於堵塞,程序在一個線程裏只能進行一次send操作,非常的沒有效率。

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

到這裏,我們應該清楚關閉緩衝的方法對絕大多數應用程序來說沒有太多好處的了。

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

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

最基本的問題是網絡帶寬。使用UDP協議進行發送的服務程序對此要求較高,因爲這樣的服務程序要求儘量少的丟包率。即使是使用TCP連接,服務器也必須注意不要濫用網絡資源。否則,TCP連接中將會出現大量重發和連接取消事件。具體的帶寬控制是跟具體程序相關的,超出了本文的討論範圍。

程序所使用的虛擬內存也必須小心。應該保守的執行內存申請和釋放,或許可以使用旁視列表(一個記錄申請並使用過的“空閒”內存的緩衝區)來重用已經申請但是被程序使用過,空閒了的內存,這樣可以使服務程序避免過多的反覆申請內存,並且保證系統中一直有儘可能多的空餘內存。(應用程序還可以使用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的一章)。如果仍然不能解決問題,那麼你可能遇上了非分頁內存池限制。那麼最好是立刻關閉部分連接,並期待情況恢復正常。
17:謝謝 painboy,你貼得資料我仔細看了一下,很有幫助。首先聲明,我的程序中沒有關閉發送緩衝,所以不可能是afd.sys的問題。我現在覺得問題可能使這段描述所說的情況“無論應用程序發起send還是receive操作,也不管AFD.SYS的緩衝是否被禁止,數據所在的緩衝都會被鎖定在物理內存裏”,但是對這句話的意思還是有些不理解:
當我wsasend時投遞的緩衝區會全部被鎖定在物理內存中嗎?如果是,那我的錯誤就很好理解了,但是adf.sys的so_sndbound限制是什麼?tcp的滑窗又限制的什麼?
大家在異步發送大的數據塊時一般怎麼處理?分成小塊麼?
wsasend/wsarecv的lpBuffers這個參數指向一個wsabuf數組,這個數組怎麼用?
18:Scatter, Gather and MSG_PARTIAL

The multiple buffer (WSABUF) input arguments for WSARecv()/WSARecvFrom() and WSASend()/WSASendto() provide support for scatter and gather operations (similar to those in the readv() and writev() functions in BSD Unix). The MSG_PARTIAL flag might also do this, but the specification isn't entirely clear what the intended use of the flag is, and current implementations don't support it (as described below).

These operations are designed for datagram sockets that preserve message boundaries, not for stream sockets that do not (so may not fill buffers), though they do seem to work with stream sockets. The advantage that the gather operation provides is that it can assemble a single outgoing datagram from multiple buffers--e.g. put together different fields--and the scatter operation can "parse" fixed field lengths in an incoming datagram into multiple buffers.

WSARecv()/WSARecvFrom(): Works fine -- scatters to input buffers on both Win95 and NT4 with datagram sockets. Stream sockets also work on both Win95 and NT4 SP3 in the testing I've done (which I would not recommend, since with a TCP byte stream the spec doesn't indicate that each buffer must be filled to the specified size on scatters, so behavior may be unpredictable under some circumstances with stream sockets).

WSASend()/WSASendTo(): Works fine -- gathers from output buffers on both Win95 if you use datagram sockets. It also works with datagram sockets on NT4 SP3, although it failed with 10040 - WSAEMSGSIZE, if the message was larger than the MTU, so required IP fragmentation (e.g. greater than 1472 on Ethernet). This also works with stream sockets, but a similar warning as given for scatters goes for gathers as well (there's no guarantee that all bytes will be sent)

 

以下是 WSASend在UNIX下的實現:

sockapi int __stdcall WSASend
(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
)
{
int rc;

TRACE("WSASend");

if (lpOverlapped != NULL) panic("Overlapped I/O not implemented in WSASend");
if (lpCompletionRoutine != NULL) panic("Completion routines not implemented in WSASend");

rc = writev(s, lpBuffers, dwBufferCount);
if (rc < 0) return -1;

if (lpNumberOfBytesSent) *lpNumberOfBytesSent = rc;
return 0;
}

 

WRITEV的解釋:
NAME
readv, writev - read or write data into multiple buffers

SYNOPSIS
#include

int readv(int filedes, const struct iovec *vector,
size_t count);

int writev(int filedes, const struct iovec *vector,
size_t count);

DESCRIPTION
The readv() function reads count blocks from the file
associated with the file descriptor filedes into the mul-
tiple buffers described by vector.

The writev() function writes at most count blocks
described by vector to the file associated with the file
descriptor vector.

The pointer vector points to a struct iovec defined in
as

struct iovect
{
void *iovbase; /* Starting address */
size_t iov_len; /* Number of bytes */
} ;

Buffers are processed in the order vector[0], vector[1],
... vector[count].

The readv() function works just like read(2) except that
multiple buffers are filled.

The writev() function works just like write(2) except that
multiple buffers are written out.


看完了以上說明後,相信你會將WSASEND裏面的dwBufferCount設爲1吧!:)
19:是不是應該這樣理解so_sndBuf的限制:

用WSASend發送一個N大小的內容,當N<=so_sndBuf時,ADF.SYS就直接將要發的內容複製到其緩衝區內,函數立即返回。當N>so_sndBuf時,這時系統會堵塞:先鎖定要發送的N空間,然後每次從N中讀so_sndBuf大小的內容去發送,直到發送完畢函數才返回。

但由於當N=200M時,太“小”了,系統無法把它裝進內存並鎖定,導致出錯。

……(這裏省去6000字) ^_^
看過好多例程,緩衝區大小取4至6K 。

最後一個問題……沒用過

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