(转)关于WSAEWOULDBLOCK
生活随笔
收集整理的這篇文章主要介紹了
(转)关于WSAEWOULDBLOCK
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
(轉)關于WSAEWOULDBLOCK
http://www.cnblogs.com/chengxin1982/archive/2009/12/24/1631067.html
首先搜索了一下論壇里關于send時產生WSAEWOULDBLOCK時的處理辦法,得出如下結論:
? ?1.產生這個錯誤只是說明out buffer已經滿了,不代表出錯.
? ?2.可以等待FD_WRITE消息,此時將沒有發送完成的數據再次發送出去.
? ?但還是有疑問,我的問題如下:
? ?1.我上面的兩條結論正確嗎?
? ?2.關于在FD_WRITE里把沒有發送完的數據發送完,這一點如何做到呢?因為
? ?int nSended = send(mysock, myBuf, myLen, 0);
? ?如果此時產生WSAEWOULDBLOCK,那么nSended肯定是SOCKET_ERROR,也就是-1了.此時nSended不能代表已經發送成功的字節數,那么在myBuf這個緩沖區里,究竟有多少字節是已經發送出去的,有多少數據是沒有發送出去的呢?
? ?
? ?在我的程序時,我想在得到WSAEWOULDBLOCK時,將沒有發送完的數據緩沖起來,等到FD_WRITE時,再將這些數據發送出去.所以還請大俠們指點一下,在這種情況下,如何知道有多少數據是被發送了的.
? ?:_)
要確定一點:send函數中的這個myBuf是應用程序提供的一個const類型的字符串(就以字符串為例吧),并不是所謂的“緩沖區”。我覺得其實不是緩沖區已滿,而是發送窗口已滿,緩沖區是系統動態分配的,一般來說是足夠用的,不大容易出現緩沖區已滿的現象。
我覺得過程是這樣的:send函數先把數據傳給Socket核心處理模塊,核心處理模塊負責管理這些需要發送出去的數據的內存堆棧,然后試著將這些數據包交付協議層的發送窗口,如果窗口大小足夠,則核心驅動模塊將數據包完全交付給窗口去發送,并刪除暫存在自己內存堆棧上的數據;如果窗口大小不夠(導致這種情況的原因有很多,比如接收端接收緩存已滿;至于這個“窗口大小不夠”這個消息到底是核心驅動模塊經自己判斷生成的,還是由協議層告知核心驅動模塊的,這點我也不大清楚,也想搞明白),則將能交付的數據盡最大量交給窗口去發送,并將無法發送出去的那些數據暫存在核心處理模塊的內存堆棧上,并同時向上給send函數發送一個發送窗口已滿的消息,然后send函數便處于BLOCK狀態。
可以確定的是,send函數不會直接和協議層打交道,而是通過核心驅動模塊來和協議層交互(廢話,呵呵)
另外,我覺得知道多少數據被發送是沒有意義的,因為一旦將數據通過發送窗口發送出去,核心驅動模塊就會將剩余的數據馬上交付空的發送窗口,這個時間非常短,等你得到了發送數據的大小時說不定你要發送的所有數據已經發送出去了,從而返回你指定發送的數據大?gt;>H綣愕男巳ぴ謨誚郵斬說膔ecv函數處于暫停接收狀態(通過設置斷點)而要求send函數返回發送窗口里的數據大小,我也有同樣的興趣,呵呵
?好!!講解的好,讀了兩遍,明白什么意思了...
呵呵...以前我一直認為send是直接跟協議層打交道的,但想一想有點不大現實,經這么一解釋,霍然開朗型...
看來得去把<計算機網絡>找出來,加強點原理性的東東了:)
多謝多謝:)
?
--------------------------------------------------------------------------------
不過還有一個問題,很簡單的一個程序,如下:
? ?for(int i=0; i<=1000; i++)
? ?{
? ? ? int nSended = send(mySocket, myBuf, myLen, 0);
? ?}
按照你的說法,send函數把myBuf這個字節數組上的內容交給了核心處理模塊,那么,如果i==500的時候,nSended變成了SOCKET_ERROR,且WSAGetLastError() == WSAEWOULDBLOCK,而此時這個XUN環并沒有判斷,還會進行接下來的500個循環,那是不是接下來的500個循環中,send()也都把數據交給核心處理模塊了嗎?那如果這個循環接下來還有50000個呢?"核心處理模塊的內存堆棧"有這么大嗎?
? ?一旦WSAEWOULDBLOCK產生,那么接下來的send函數是如何動作的呢?是把待發送的數據放到"核心處理模塊的內存堆棧"上了呢?還是其它什么動作呢?
?
--------------------------------------------------------------------------------
后面的500個send都出一樣的錯。WSAGetLastError() == WSAEWOULDBLOCK
?
--------------------------------------------------------------------------------
這個問題就要搞清楚TIMEOUT和BLOCK的區別了。表面看上去這兩個選項似乎沒什么差別,都是暫停send函數的運行,其實里面有很大的差別。
先說說TIMEOUT狀態下的一種情形:首先,send函數將數據交付給核心驅動模塊,后者將其暫存于它“掌管”的內存里,然后再將這些數據交付給發送窗口去發送。我們現在只考慮發送窗口大小不足的情況,一旦出現這種情況,send函數便處于TIMEOUT狀態(仍然是依靠下層的核心驅動模塊給它消息)。這種狀態是這樣的:如果設置了循環發送,每次send函數都會正常運行,也就是說它仍在不停地往核心驅動模塊的緩存里寫數據,此時所謂的TIMEOUT是指核心驅動模塊的緩存在等待(因為發送窗口已滿,所以核心驅動模塊內存中的數據必須呆在那里等待發送),而不是send在等待,send的TIMEOUT狀態只是映射了核心驅動模塊內存的一個狀態而已。關于這點,也是我測試時明白的,當接收端將斷點設在recv函數上時,通過查看發送端的任務管理器,發現發送進程所占用的內存越來越大,這就說明send函數是在不停地往發送緩存里寫數據。我發的一篇問題討論貼里說了這種情況,不過我的問題重點不在這兒,就不詳細說明了。另外,這種情況不丟包,等接收端的程序跳過斷點后,send函數所要發送的數據全部到達了接收端,這就說明send函數沒有發生錯誤。
再說說BLOCK時的情況,我想說過TIMEOUT后,BLOCK的情況就很容易推斷出來了吧?此時,send函數才是真正地停止了運行,just stop!它會停止繼續向發送緩存里寫數據, 以后每次調用它時(因為有循環)都會發出警告說“WSAEWOULDBLOCK”,正因為send函數不得不停下來,所以“錯”才稱其為“錯”。相對的,上面那種情況下的TIMEOUT并不是錯,它屬于正常情況。
關于這個問題很關鍵一個想法就是,要從廣義的角度來看待“正常”這個詞,而我們平時所說的“不正常”在程序設計中并不代表錯誤。
?
--------------------------------------------------------------------------------
上面的都是我個人的一些看法,樓主和各位看官如果覺得哪里不對請馬上指出來。謝謝謝謝~~~
?
--------------------------------------------------------------------------------
foxwing大哥講得頗有道理,雖然我沒有從"發送窗口"和"out buffer"等這個層次去考慮send的執行原理,但是你講的跟我的理解,還有我試驗下來的結果一致,不像其它人講的,一旦發生WSAEWOULDBLOCK時,數據是否發送出去已經處于不可預測的狀態.
? ? 不過我發這個貼子是為了解決一個實際的問題,即是,如果我需要不停地發送數據(就像我上面的那個循環),當其中一個send返回SOCKET_ERROR,且GetLastError()==WSAEWOULDBLOCK時,我們采用什么方法比較合理.
? ? 當前可以像其它人講的,每個人有每個人的做法,這種答案不能給任何人以任何幫助.
? ? 我的解決辦法是這樣的:
? ? 1.首次發生WSAEWOULDBLOCK時,應該記錄下此時的狀態,從此時起,就不應該再調用send函數了,而是將待發送的數據以自己的方式存儲起來.
? ? 2.等待FD_WRITE消息產生時,依次發送自己緩存過的數據,如果所有的數據均發送完,則說明下次可以繼續調用send來發送數據了.
? ? 3.首次發生WSAEWOULDBLOCK時的那次send調用,其要發送的數據已經全部被發送出去了,該次發送的數據不需要緩存.
? ? 這樣的說法有何不妥?希望foxwing大哥,或者其它大哥大姐們出來評論一下:)
?
--------------------------------------------------------------------------------
說明我的想法之前,我先糾正一下上面我犯的幾個錯誤,也許說明這些錯誤對樓主也有幫助。且聽我慢慢道來,嘿嘿
我的犯的最大錯誤是,沒有搞清楚“模式”和“選項”,BLOCK是一種模式,而TIMEOUT只是一個在特定模式下的選項。
先說模式,一般SOCKET默認是BLOCK(阻斷)模式,這種模式對send函數來說是同步的,而NONBLOCK(非阻斷)模式對send函數來說則是異步的。
樓主說send函數返回的錯誤是WSAEWOULDBLOCK,那么即是說,將SOCKET模式設置成了NONBLOCK,因為WSAEWOULDBLOCK在文檔里是這么定義的:The socket is marked as nonblocking and the requested operation would block. 這句是說,socket被設置成了非阻斷模式,然而程序員所要求的操作卻將要導致阻斷。這時,才會報WSAEWOULDBLOCK錯誤。
文檔里還有一句話:If no buffer space is available within the transport system to hold the data to be transmitted, send will block unless the socket has been placed in a nonblocking mode. 這句是說,如果傳輸系統已經沒有緩沖區來存儲發送數據(我仍然理解為發送窗口已滿),那么,除非將SOCKET設置成了NONBLOCK(非阻斷)模式,否則send函數就會BLOCK(阻斷)。這句話是什么意思呢?我的理解是,NONBLOCK(非阻斷)模式下永遠不會發生BLOCK(阻斷)的情況。這就明確了我上面一篇貼子里的錯誤,那時我說,“此時,send函數才是真正地停止了運行,just stop!它會停止繼續向發送緩存里寫數據, 以后每次調用它時(因為有循環)都會發出警告說“WSAEWOULDBLOCK””,我說這句話是以SOCKET為阻斷模式下為前提的,確實,send函數這個時候是停止了運行,也停止了繼續向緩存里寫數據,但是,程序將永遠停留在這個send調用上,send函數也不會返回,于是后面的循環也不可能發生,直到發送窗口有了空間并將核心驅動模塊緩存里的所有數據(注1)填入其中,程序才會跳過剛才的send調用而接著執行循環。導致這種一直阻斷的原因大多是由于接收端的問題,所以我在我的程序里設置了TIMEOUT的選項,讓send等待一段時間,等超過這段時間后如果還是阻斷,那么就報TIMEOUT錯誤,好跳過這個send調用,繼續執行循環。
[注1] 注意,是所有數據,在阻斷模式下,如果要求發送的數據不能一次全部填入發送窗口,那么send就會阻斷。文檔里有這么兩句話:
? ? On nonblocking stream oriented sockets, the number of bytes written can be between 1 and the requested length.
? ? If no error occurs, send returns the total number of bytes sent, which can be less than the number indicated by len for nonblocking sockets.?
這兩句的意思是,只有在面向流的非阻斷模式下,send才可能會返回1到要求發送數據大小之間的一個數。言外之意,阻斷模式下要不就成功返回要求發送的所有數據的大小,要不就一個字節也不發送并且永遠停留在這次send調用上,再沒有其他的可能性了。這才是BLOCK(阻斷)的真正含義,所謂的“同步”也正是這個意思——執行完才能過去,否則沒門兒!
好,現在就可以分析一下樓主的解決方案了。
先分析第一條:“首次發生WSAEWOULDBLOCK時,應該記錄下此時的狀態,從此時起,就不應該再調用send函數了,而是將待發送的數據以自己的方式存儲起來.”既然發生了WSAEWOULDBLOCK錯誤,那么就說明樓主將SOCKET設置成了非阻斷模式,這種模式有這個好處:哪怕發送窗口還有1byte的空間,那么send就將1byte的數據填入發送窗口,從而正常返回1,未發送出去的數據就交給核心驅動模塊去發送(和本次send調用就沒什么關系了),后面的循環也得以繼續執行,這就是NONBLOCK模式下所謂的“異步”!其實是看起來像異步。而樓主所說的“將待發送的數據以自己的方式存儲起來”是沒有必要的,因為系統會自動存儲這些未發送的數據,等待發送窗口有了空間再將其發送出去。
后面的兩條方案是在SOCKET為阻斷模式下才有必要實施的,但是,阻斷模式下系統也會自動把本次要發送的所有數據暫存下來,等待發送窗口有了空間就能將其發送出去,然后send就會返回指定發送的數據大小,一切也都是自動的,不需要人為控制。而且,等待FD_WRITE消息是個消極的措施(因為如果導致BLOCK的因素只是暫時的話,系統遲早會將所有數據發送出去,那時候自然就會得到FD_WRITE的消息),我認為這個時候最好將選項設為TIMEOUT,不會因為本次send的阻斷而影響了后續send的執行[注2],這就相當于人為“制造”了一個NONBLOCK(非阻斷)模式下的發送機制,
[注2]當然,這樣是要消耗內存的。因為,如果上次的阻斷一直沒有得到解決(這個解決過程是系統自動完成的),后續的send調用理所當然仍然還是阻斷,而我們后續的千千萬萬個send函數仍然在向本地緩存里寫數據,占用內存就是在自然不過的事情了。(這時只好祈禱接收端趕快處理好它的問題,好讓我們的“士兵”不要老擠在“護城河”里出不去了,呵呵)。
就這些了,上面說的可能過于羅嗦,但為了把我的意思表達清楚,也不得不這么做了,呵呵,如果有什么不同的見解,還是希望提出來,大家一起分析討論。謝謝~
?
--------------------------------------------------------------------------------
還有一點忘記說明了,那就是NONBLOCK模式下仍然可能發生阻斷(雖然可能性非常非常小),比如調用send函數時發送窗口完全滿了,連填入一個字節的空間都沒了,這時,即使是在非阻斷模式也會發生阻斷,當然,這時的阻斷是作為一個錯誤來告訴send調用者的,即是WSAEWOULDBLOCK。
這個時候(非阻斷模式下報出WSAEWOULDBLOCK錯誤時)非阻斷模式特有的“異步”行為就玩不轉了,從而等待發送窗口有了新的空間后再繼續玩它的“異步”游戲。
不過說到這兒我又產生一個疑問,那就是告訴調用者發生了WSAEWOULDBLOCK錯誤有什么意義呢?難道只是單純地為了告訴send調用者SOCKET暫時必須中止“異步”的游戲?這好像沒什么特別的用處吧。。。又或者系統會將本次send調用所要求發送的數據清空,從而要求調用者必須重新調用send函數,以獲得消除阻斷的空間和時間?
還要繼續研究研究。。。
?
--------------------------------------------------------------------------------
現在看來我在2005-03-30 11:35:00 的回復還是有些問題
樓主的“首次發生WSAEWOULDBLOCK時,應該記錄下此時的狀態,從此時起,就不應該再調用send函數了,而是將待發送的數據以自己的方式存儲起來.”還需要再仔細想想,如果在非阻斷模式下出現了這種情況,是否需要“以自己的方式存儲起來”并重新發送還是值得仔細推敲一下的。
give me some time...
?
--------------------------------------------------------------------------------
勝讀十年書啊,foxwing大哥的觀點讓我對socket這個網絡編層接口的位置認識越來越清晰了,謝過先.
我說的WSAEWOULDBLOCK這種情況是在異步通信模式下產生的,這點我沒有說明白,不好意思...:)
還有個問題:
"哪怕發送窗口還有1byte的空間,那么send就將1byte的數據填入發送窗口,從而正常返回1,未發送出去的數據就交給核心驅動模塊去發送(和本次send調用就沒什么關系了),后面的循環也得以繼續執行,這就是NONBLOCK模式下所謂的“異步”!"
我對這段話中的"后面的循環也得以繼續執行"理解不是很清楚,后面的循環繼續執行的話,會引發什么動作呢?是send同樣也把數據交給核心驅動模塊,然后自己返回嗎?
按道理,前面一個send返回值小于待發送的字節數,對后面那個send而言,發送窗口是滿的這種可能性很大,那么它要發送的數據沒有一個字節是放到發送窗口上的,因而我要問了,后面那個send為何不返回0而是返回SOCKET_ERROR,即是-1呢?這樣做有何意義嗎?不都是把數據交給核心驅動模塊了嗎?只不過一個是放了1個字節到發送窗口上,一個沒有放數據到發送窗口上而已.
不知道我的問題清不清楚,我的假設情況是這樣的,程序如下:
? ?char *myBuf; ? ? ? ? ? ? ? ? ? ? ? ? ? ?//指向100M字節的存儲區
? ?int myLen = 100*1024*1024; ? ? ? ? ? ? ?//要發送的數據長度為100M
? ?int nRet = 0;
? ?nRet = send(mySock, myBuf, myLen, 0); ? //此時nRet=1,雖然nRet<myLen?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//但系統會保證myLen個字節都發送出去
? ?nRet = send(mySock, myBuf, myLen, 0); ? //此時nRet=SOCKET_ERROR, 而WSAGetLastError()
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//是WSAEWOULDBLOCK
如果我的myBuf中有100M的數據要發送,那么第一個send返回1而第二個send引發WSAEWOULDBLOCK的可能性是有的,因為畢竟是100M的數據啊...:)
問題是:
1.那對第二個send來說,它要求發送的100M數據發送了多少呢?第二個send也是把數據交給了核心驅動模塊就返回了呢,還是說它根本就沒有做任何動作就返回SOCKET_ERROR了?
2.如果第二個send并沒有把數據交給核心驅動模塊去發送(畢竟它返回的是SOCKET_ERROR嘛),也就是說它什么動作都沒有做,那我們肯定要把第二個send要發送的數據保存起來,等之后再發送了,對不?
?
--------------------------------------------------------------------------------
哈...foxwing大哥,不想和你在同時發貼,不然我上面的那大段字就不用打的這么XIN苦了,因為問題貼到答案的后面了...哈哈...你的疑問也正是我想問你的啊...
我在實際應用中發現了這個問題,就是發生WSAEWOULDBLOCK時,之后待發送的數據應該怎么辦,我看MSDN出沒得到一個明確的答案...希望大哥可以給提點提點.我已經根據我的理解寫出了代碼,正在測試,但不知道對不對,等幾個小時后吧,測出來沒有問題,再把我的思路也貼上來,你也給個評價...
?
--------------------------------------------------------------------------------
看來我還得練下打字速度了...:(
?
--------------------------------------------------------------------------------
現在就只討論非阻斷模式吧,仔細一想,非阻斷模式下還是經常會碰到阻斷的情況的。這個時候唯一的解決方法就是樓主所說的必須把要發送的數據緩存起來,在程序中調用send函數,直到成功后再繼續執行循環,但是這種處理方法帶來的結果是效率低下。仔細想想這種方式,其實這就和阻斷模式下的情況沒多大差別了,只是非阻斷模式下更不容易遇到阻斷罷了——因為它會“肢解”數據。
非阻斷模式下發生WSAEWOULDBLOCK時,呆在緩存區里要發送的整塊數據都會被free掉,在程序中,就只能采取這種措施了:
if(nRet == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
{
? ? while(WSAGetLastError() == WSAEWOULDBLOCK)
? ? {
? ? ? ?send(...);
? ? }
}
如果不采取這種措施,那么本次要發送的數據就不會發出去,因為在發生錯誤時本地緩沖區就已經將這些數據清空了。而且如果一直阻塞的話,接下去正常循環中的send調用也都會發生錯誤。
一般來講,最好不要采取這種方式,而選擇另一種I/O模型——select模型。這種模型的效率更高些。
?
--------------------------------------------------------------------------------
在非阻斷模式下,如果不采取上述的措施,那么會導致這樣一個結果:假設要發送的數據分別是1、2、3、4、5、6、7、8、9,又假設在發送3時發生第一次阻斷,如果網絡本身良好的話,那么接收端接收到的可能是1、2、5、6、8、9,當然這也是一種假設的情況。從接收端接收數據的情況來看,發生過3次阻斷,第一次是3,接下來的4也發生了阻斷,從5開始由于系統解決了阻斷問題,所以發送正常了,但發送7的時候,又產生了阻斷,所以7也沒發送出去,而這之后阻斷又被解決了……
這就是非阻斷模式存在的一個問題,當然這個例子有點過于理想化了,但比較容易說清楚非阻斷模式的運行機制。
如果樓主想徹底搞清楚send函數是如何和底層打交道的,那么就看一下《TCP/IP詳解 第二卷:實現》,其中的sosend函數基本上可以說明Windows下send函數的運行機制。我也正在看這塊內容,因為有代碼,所以感覺很好,不像我上面那一大堆只是說說而已,呵呵。
?
--------------------------------------------------------------------------------
我上面的代碼太傻,改改 :)
while(nRet == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
{
? ? nRet = send(...);
} 與50位技術專家面對面20年技術見證,附贈技術全景圖
http://www.cnblogs.com/chengxin1982/archive/2009/12/24/1631067.html
首先搜索了一下論壇里關于send時產生WSAEWOULDBLOCK時的處理辦法,得出如下結論:
? ?1.產生這個錯誤只是說明out buffer已經滿了,不代表出錯.
? ?2.可以等待FD_WRITE消息,此時將沒有發送完成的數據再次發送出去.
? ?但還是有疑問,我的問題如下:
? ?1.我上面的兩條結論正確嗎?
? ?2.關于在FD_WRITE里把沒有發送完的數據發送完,這一點如何做到呢?因為
? ?int nSended = send(mysock, myBuf, myLen, 0);
? ?如果此時產生WSAEWOULDBLOCK,那么nSended肯定是SOCKET_ERROR,也就是-1了.此時nSended不能代表已經發送成功的字節數,那么在myBuf這個緩沖區里,究竟有多少字節是已經發送出去的,有多少數據是沒有發送出去的呢?
? ?
? ?在我的程序時,我想在得到WSAEWOULDBLOCK時,將沒有發送完的數據緩沖起來,等到FD_WRITE時,再將這些數據發送出去.所以還請大俠們指點一下,在這種情況下,如何知道有多少數據是被發送了的.
? ?:_)
要確定一點:send函數中的這個myBuf是應用程序提供的一個const類型的字符串(就以字符串為例吧),并不是所謂的“緩沖區”。我覺得其實不是緩沖區已滿,而是發送窗口已滿,緩沖區是系統動態分配的,一般來說是足夠用的,不大容易出現緩沖區已滿的現象。
我覺得過程是這樣的:send函數先把數據傳給Socket核心處理模塊,核心處理模塊負責管理這些需要發送出去的數據的內存堆棧,然后試著將這些數據包交付協議層的發送窗口,如果窗口大小足夠,則核心驅動模塊將數據包完全交付給窗口去發送,并刪除暫存在自己內存堆棧上的數據;如果窗口大小不夠(導致這種情況的原因有很多,比如接收端接收緩存已滿;至于這個“窗口大小不夠”這個消息到底是核心驅動模塊經自己判斷生成的,還是由協議層告知核心驅動模塊的,這點我也不大清楚,也想搞明白),則將能交付的數據盡最大量交給窗口去發送,并將無法發送出去的那些數據暫存在核心處理模塊的內存堆棧上,并同時向上給send函數發送一個發送窗口已滿的消息,然后send函數便處于BLOCK狀態。
可以確定的是,send函數不會直接和協議層打交道,而是通過核心驅動模塊來和協議層交互(廢話,呵呵)
另外,我覺得知道多少數據被發送是沒有意義的,因為一旦將數據通過發送窗口發送出去,核心驅動模塊就會將剩余的數據馬上交付空的發送窗口,這個時間非常短,等你得到了發送數據的大小時說不定你要發送的所有數據已經發送出去了,從而返回你指定發送的數據大?gt;>H綣愕男巳ぴ謨誚郵斬說膔ecv函數處于暫停接收狀態(通過設置斷點)而要求send函數返回發送窗口里的數據大小,我也有同樣的興趣,呵呵
?好!!講解的好,讀了兩遍,明白什么意思了...
呵呵...以前我一直認為send是直接跟協議層打交道的,但想一想有點不大現實,經這么一解釋,霍然開朗型...
看來得去把<計算機網絡>找出來,加強點原理性的東東了:)
多謝多謝:)
?
--------------------------------------------------------------------------------
不過還有一個問題,很簡單的一個程序,如下:
? ?for(int i=0; i<=1000; i++)
? ?{
? ? ? int nSended = send(mySocket, myBuf, myLen, 0);
? ?}
按照你的說法,send函數把myBuf這個字節數組上的內容交給了核心處理模塊,那么,如果i==500的時候,nSended變成了SOCKET_ERROR,且WSAGetLastError() == WSAEWOULDBLOCK,而此時這個XUN環并沒有判斷,還會進行接下來的500個循環,那是不是接下來的500個循環中,send()也都把數據交給核心處理模塊了嗎?那如果這個循環接下來還有50000個呢?"核心處理模塊的內存堆棧"有這么大嗎?
? ?一旦WSAEWOULDBLOCK產生,那么接下來的send函數是如何動作的呢?是把待發送的數據放到"核心處理模塊的內存堆棧"上了呢?還是其它什么動作呢?
?
--------------------------------------------------------------------------------
后面的500個send都出一樣的錯。WSAGetLastError() == WSAEWOULDBLOCK
?
--------------------------------------------------------------------------------
這個問題就要搞清楚TIMEOUT和BLOCK的區別了。表面看上去這兩個選項似乎沒什么差別,都是暫停send函數的運行,其實里面有很大的差別。
先說說TIMEOUT狀態下的一種情形:首先,send函數將數據交付給核心驅動模塊,后者將其暫存于它“掌管”的內存里,然后再將這些數據交付給發送窗口去發送。我們現在只考慮發送窗口大小不足的情況,一旦出現這種情況,send函數便處于TIMEOUT狀態(仍然是依靠下層的核心驅動模塊給它消息)。這種狀態是這樣的:如果設置了循環發送,每次send函數都會正常運行,也就是說它仍在不停地往核心驅動模塊的緩存里寫數據,此時所謂的TIMEOUT是指核心驅動模塊的緩存在等待(因為發送窗口已滿,所以核心驅動模塊內存中的數據必須呆在那里等待發送),而不是send在等待,send的TIMEOUT狀態只是映射了核心驅動模塊內存的一個狀態而已。關于這點,也是我測試時明白的,當接收端將斷點設在recv函數上時,通過查看發送端的任務管理器,發現發送進程所占用的內存越來越大,這就說明send函數是在不停地往發送緩存里寫數據。我發的一篇問題討論貼里說了這種情況,不過我的問題重點不在這兒,就不詳細說明了。另外,這種情況不丟包,等接收端的程序跳過斷點后,send函數所要發送的數據全部到達了接收端,這就說明send函數沒有發生錯誤。
再說說BLOCK時的情況,我想說過TIMEOUT后,BLOCK的情況就很容易推斷出來了吧?此時,send函數才是真正地停止了運行,just stop!它會停止繼續向發送緩存里寫數據, 以后每次調用它時(因為有循環)都會發出警告說“WSAEWOULDBLOCK”,正因為send函數不得不停下來,所以“錯”才稱其為“錯”。相對的,上面那種情況下的TIMEOUT并不是錯,它屬于正常情況。
關于這個問題很關鍵一個想法就是,要從廣義的角度來看待“正常”這個詞,而我們平時所說的“不正常”在程序設計中并不代表錯誤。
?
--------------------------------------------------------------------------------
上面的都是我個人的一些看法,樓主和各位看官如果覺得哪里不對請馬上指出來。謝謝謝謝~~~
?
--------------------------------------------------------------------------------
foxwing大哥講得頗有道理,雖然我沒有從"發送窗口"和"out buffer"等這個層次去考慮send的執行原理,但是你講的跟我的理解,還有我試驗下來的結果一致,不像其它人講的,一旦發生WSAEWOULDBLOCK時,數據是否發送出去已經處于不可預測的狀態.
? ? 不過我發這個貼子是為了解決一個實際的問題,即是,如果我需要不停地發送數據(就像我上面的那個循環),當其中一個send返回SOCKET_ERROR,且GetLastError()==WSAEWOULDBLOCK時,我們采用什么方法比較合理.
? ? 當前可以像其它人講的,每個人有每個人的做法,這種答案不能給任何人以任何幫助.
? ? 我的解決辦法是這樣的:
? ? 1.首次發生WSAEWOULDBLOCK時,應該記錄下此時的狀態,從此時起,就不應該再調用send函數了,而是將待發送的數據以自己的方式存儲起來.
? ? 2.等待FD_WRITE消息產生時,依次發送自己緩存過的數據,如果所有的數據均發送完,則說明下次可以繼續調用send來發送數據了.
? ? 3.首次發生WSAEWOULDBLOCK時的那次send調用,其要發送的數據已經全部被發送出去了,該次發送的數據不需要緩存.
? ? 這樣的說法有何不妥?希望foxwing大哥,或者其它大哥大姐們出來評論一下:)
?
--------------------------------------------------------------------------------
說明我的想法之前,我先糾正一下上面我犯的幾個錯誤,也許說明這些錯誤對樓主也有幫助。且聽我慢慢道來,嘿嘿
我的犯的最大錯誤是,沒有搞清楚“模式”和“選項”,BLOCK是一種模式,而TIMEOUT只是一個在特定模式下的選項。
先說模式,一般SOCKET默認是BLOCK(阻斷)模式,這種模式對send函數來說是同步的,而NONBLOCK(非阻斷)模式對send函數來說則是異步的。
樓主說send函數返回的錯誤是WSAEWOULDBLOCK,那么即是說,將SOCKET模式設置成了NONBLOCK,因為WSAEWOULDBLOCK在文檔里是這么定義的:The socket is marked as nonblocking and the requested operation would block. 這句是說,socket被設置成了非阻斷模式,然而程序員所要求的操作卻將要導致阻斷。這時,才會報WSAEWOULDBLOCK錯誤。
文檔里還有一句話:If no buffer space is available within the transport system to hold the data to be transmitted, send will block unless the socket has been placed in a nonblocking mode. 這句是說,如果傳輸系統已經沒有緩沖區來存儲發送數據(我仍然理解為發送窗口已滿),那么,除非將SOCKET設置成了NONBLOCK(非阻斷)模式,否則send函數就會BLOCK(阻斷)。這句話是什么意思呢?我的理解是,NONBLOCK(非阻斷)模式下永遠不會發生BLOCK(阻斷)的情況。這就明確了我上面一篇貼子里的錯誤,那時我說,“此時,send函數才是真正地停止了運行,just stop!它會停止繼續向發送緩存里寫數據, 以后每次調用它時(因為有循環)都會發出警告說“WSAEWOULDBLOCK””,我說這句話是以SOCKET為阻斷模式下為前提的,確實,send函數這個時候是停止了運行,也停止了繼續向緩存里寫數據,但是,程序將永遠停留在這個send調用上,send函數也不會返回,于是后面的循環也不可能發生,直到發送窗口有了空間并將核心驅動模塊緩存里的所有數據(注1)填入其中,程序才會跳過剛才的send調用而接著執行循環。導致這種一直阻斷的原因大多是由于接收端的問題,所以我在我的程序里設置了TIMEOUT的選項,讓send等待一段時間,等超過這段時間后如果還是阻斷,那么就報TIMEOUT錯誤,好跳過這個send調用,繼續執行循環。
[注1] 注意,是所有數據,在阻斷模式下,如果要求發送的數據不能一次全部填入發送窗口,那么send就會阻斷。文檔里有這么兩句話:
? ? On nonblocking stream oriented sockets, the number of bytes written can be between 1 and the requested length.
? ? If no error occurs, send returns the total number of bytes sent, which can be less than the number indicated by len for nonblocking sockets.?
這兩句的意思是,只有在面向流的非阻斷模式下,send才可能會返回1到要求發送數據大小之間的一個數。言外之意,阻斷模式下要不就成功返回要求發送的所有數據的大小,要不就一個字節也不發送并且永遠停留在這次send調用上,再沒有其他的可能性了。這才是BLOCK(阻斷)的真正含義,所謂的“同步”也正是這個意思——執行完才能過去,否則沒門兒!
好,現在就可以分析一下樓主的解決方案了。
先分析第一條:“首次發生WSAEWOULDBLOCK時,應該記錄下此時的狀態,從此時起,就不應該再調用send函數了,而是將待發送的數據以自己的方式存儲起來.”既然發生了WSAEWOULDBLOCK錯誤,那么就說明樓主將SOCKET設置成了非阻斷模式,這種模式有這個好處:哪怕發送窗口還有1byte的空間,那么send就將1byte的數據填入發送窗口,從而正常返回1,未發送出去的數據就交給核心驅動模塊去發送(和本次send調用就沒什么關系了),后面的循環也得以繼續執行,這就是NONBLOCK模式下所謂的“異步”!其實是看起來像異步。而樓主所說的“將待發送的數據以自己的方式存儲起來”是沒有必要的,因為系統會自動存儲這些未發送的數據,等待發送窗口有了空間再將其發送出去。
后面的兩條方案是在SOCKET為阻斷模式下才有必要實施的,但是,阻斷模式下系統也會自動把本次要發送的所有數據暫存下來,等待發送窗口有了空間就能將其發送出去,然后send就會返回指定發送的數據大小,一切也都是自動的,不需要人為控制。而且,等待FD_WRITE消息是個消極的措施(因為如果導致BLOCK的因素只是暫時的話,系統遲早會將所有數據發送出去,那時候自然就會得到FD_WRITE的消息),我認為這個時候最好將選項設為TIMEOUT,不會因為本次send的阻斷而影響了后續send的執行[注2],這就相當于人為“制造”了一個NONBLOCK(非阻斷)模式下的發送機制,
[注2]當然,這樣是要消耗內存的。因為,如果上次的阻斷一直沒有得到解決(這個解決過程是系統自動完成的),后續的send調用理所當然仍然還是阻斷,而我們后續的千千萬萬個send函數仍然在向本地緩存里寫數據,占用內存就是在自然不過的事情了。(這時只好祈禱接收端趕快處理好它的問題,好讓我們的“士兵”不要老擠在“護城河”里出不去了,呵呵)。
就這些了,上面說的可能過于羅嗦,但為了把我的意思表達清楚,也不得不這么做了,呵呵,如果有什么不同的見解,還是希望提出來,大家一起分析討論。謝謝~
?
--------------------------------------------------------------------------------
還有一點忘記說明了,那就是NONBLOCK模式下仍然可能發生阻斷(雖然可能性非常非常小),比如調用send函數時發送窗口完全滿了,連填入一個字節的空間都沒了,這時,即使是在非阻斷模式也會發生阻斷,當然,這時的阻斷是作為一個錯誤來告訴send調用者的,即是WSAEWOULDBLOCK。
這個時候(非阻斷模式下報出WSAEWOULDBLOCK錯誤時)非阻斷模式特有的“異步”行為就玩不轉了,從而等待發送窗口有了新的空間后再繼續玩它的“異步”游戲。
不過說到這兒我又產生一個疑問,那就是告訴調用者發生了WSAEWOULDBLOCK錯誤有什么意義呢?難道只是單純地為了告訴send調用者SOCKET暫時必須中止“異步”的游戲?這好像沒什么特別的用處吧。。。又或者系統會將本次send調用所要求發送的數據清空,從而要求調用者必須重新調用send函數,以獲得消除阻斷的空間和時間?
還要繼續研究研究。。。
?
--------------------------------------------------------------------------------
現在看來我在2005-03-30 11:35:00 的回復還是有些問題
樓主的“首次發生WSAEWOULDBLOCK時,應該記錄下此時的狀態,從此時起,就不應該再調用send函數了,而是將待發送的數據以自己的方式存儲起來.”還需要再仔細想想,如果在非阻斷模式下出現了這種情況,是否需要“以自己的方式存儲起來”并重新發送還是值得仔細推敲一下的。
give me some time...
?
--------------------------------------------------------------------------------
勝讀十年書啊,foxwing大哥的觀點讓我對socket這個網絡編層接口的位置認識越來越清晰了,謝過先.
我說的WSAEWOULDBLOCK這種情況是在異步通信模式下產生的,這點我沒有說明白,不好意思...:)
還有個問題:
"哪怕發送窗口還有1byte的空間,那么send就將1byte的數據填入發送窗口,從而正常返回1,未發送出去的數據就交給核心驅動模塊去發送(和本次send調用就沒什么關系了),后面的循環也得以繼續執行,這就是NONBLOCK模式下所謂的“異步”!"
我對這段話中的"后面的循環也得以繼續執行"理解不是很清楚,后面的循環繼續執行的話,會引發什么動作呢?是send同樣也把數據交給核心驅動模塊,然后自己返回嗎?
按道理,前面一個send返回值小于待發送的字節數,對后面那個send而言,發送窗口是滿的這種可能性很大,那么它要發送的數據沒有一個字節是放到發送窗口上的,因而我要問了,后面那個send為何不返回0而是返回SOCKET_ERROR,即是-1呢?這樣做有何意義嗎?不都是把數據交給核心驅動模塊了嗎?只不過一個是放了1個字節到發送窗口上,一個沒有放數據到發送窗口上而已.
不知道我的問題清不清楚,我的假設情況是這樣的,程序如下:
? ?char *myBuf; ? ? ? ? ? ? ? ? ? ? ? ? ? ?//指向100M字節的存儲區
? ?int myLen = 100*1024*1024; ? ? ? ? ? ? ?//要發送的數據長度為100M
? ?int nRet = 0;
? ?nRet = send(mySock, myBuf, myLen, 0); ? //此時nRet=1,雖然nRet<myLen?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//但系統會保證myLen個字節都發送出去
? ?nRet = send(mySock, myBuf, myLen, 0); ? //此時nRet=SOCKET_ERROR, 而WSAGetLastError()
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//是WSAEWOULDBLOCK
如果我的myBuf中有100M的數據要發送,那么第一個send返回1而第二個send引發WSAEWOULDBLOCK的可能性是有的,因為畢竟是100M的數據啊...:)
問題是:
1.那對第二個send來說,它要求發送的100M數據發送了多少呢?第二個send也是把數據交給了核心驅動模塊就返回了呢,還是說它根本就沒有做任何動作就返回SOCKET_ERROR了?
2.如果第二個send并沒有把數據交給核心驅動模塊去發送(畢竟它返回的是SOCKET_ERROR嘛),也就是說它什么動作都沒有做,那我們肯定要把第二個send要發送的數據保存起來,等之后再發送了,對不?
?
--------------------------------------------------------------------------------
哈...foxwing大哥,不想和你在同時發貼,不然我上面的那大段字就不用打的這么XIN苦了,因為問題貼到答案的后面了...哈哈...你的疑問也正是我想問你的啊...
我在實際應用中發現了這個問題,就是發生WSAEWOULDBLOCK時,之后待發送的數據應該怎么辦,我看MSDN出沒得到一個明確的答案...希望大哥可以給提點提點.我已經根據我的理解寫出了代碼,正在測試,但不知道對不對,等幾個小時后吧,測出來沒有問題,再把我的思路也貼上來,你也給個評價...
?
--------------------------------------------------------------------------------
看來我還得練下打字速度了...:(
?
--------------------------------------------------------------------------------
現在就只討論非阻斷模式吧,仔細一想,非阻斷模式下還是經常會碰到阻斷的情況的。這個時候唯一的解決方法就是樓主所說的必須把要發送的數據緩存起來,在程序中調用send函數,直到成功后再繼續執行循環,但是這種處理方法帶來的結果是效率低下。仔細想想這種方式,其實這就和阻斷模式下的情況沒多大差別了,只是非阻斷模式下更不容易遇到阻斷罷了——因為它會“肢解”數據。
非阻斷模式下發生WSAEWOULDBLOCK時,呆在緩存區里要發送的整塊數據都會被free掉,在程序中,就只能采取這種措施了:
if(nRet == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
{
? ? while(WSAGetLastError() == WSAEWOULDBLOCK)
? ? {
? ? ? ?send(...);
? ? }
}
如果不采取這種措施,那么本次要發送的數據就不會發出去,因為在發生錯誤時本地緩沖區就已經將這些數據清空了。而且如果一直阻塞的話,接下去正常循環中的send調用也都會發生錯誤。
一般來講,最好不要采取這種方式,而選擇另一種I/O模型——select模型。這種模型的效率更高些。
?
--------------------------------------------------------------------------------
在非阻斷模式下,如果不采取上述的措施,那么會導致這樣一個結果:假設要發送的數據分別是1、2、3、4、5、6、7、8、9,又假設在發送3時發生第一次阻斷,如果網絡本身良好的話,那么接收端接收到的可能是1、2、5、6、8、9,當然這也是一種假設的情況。從接收端接收數據的情況來看,發生過3次阻斷,第一次是3,接下來的4也發生了阻斷,從5開始由于系統解決了阻斷問題,所以發送正常了,但發送7的時候,又產生了阻斷,所以7也沒發送出去,而這之后阻斷又被解決了……
這就是非阻斷模式存在的一個問題,當然這個例子有點過于理想化了,但比較容易說清楚非阻斷模式的運行機制。
如果樓主想徹底搞清楚send函數是如何和底層打交道的,那么就看一下《TCP/IP詳解 第二卷:實現》,其中的sosend函數基本上可以說明Windows下send函數的運行機制。我也正在看這塊內容,因為有代碼,所以感覺很好,不像我上面那一大堆只是說說而已,呵呵。
?
--------------------------------------------------------------------------------
我上面的代碼太傻,改改 :)
while(nRet == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
{
? ? nRet = send(...);
} 與50位技術專家面對面20年技術見證,附贈技術全景圖
總結
以上是生活随笔為你收集整理的(转)关于WSAEWOULDBLOCK的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于VC网络编程中用 char 发送结构
- 下一篇: 使用Signature Tool自动生成