天天看點

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,

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 。

最後一個問題……沒用過

繼續閱讀