关于WSAEWOULDBLOCK和WSA_IO_PENDING错误
=================================WSAEWOULDBLOCK======================================
今天有朋友問我關(guān)于 Winsock 發(fā)送數(shù)據(jù)出錯(cuò)的問題,錯(cuò)誤代碼為 WSAEWOULDBLOCK。而剛好以前自己也遇到過這個(gè)問題,也研究過一下發(fā)生的原因,所以很順利的幫朋友解決了問題,但由于自己語言表達(dá)能力太弱,所以干脆把原因分析寫下來:“關(guān)于 Winsock Send 無法完成,返回 WSAEWOULDBLOCK 的原因分析和解決方法”,如下:
? 首先,Winsock 異常 10035 WSAEWOULDBLOCK (WSAGetLastError) 的意識是 Output Buffer 已經(jīng)滿了,無法再寫入數(shù)據(jù)。確切的說它其實(shí)不算是個(gè)錯(cuò)誤,出現(xiàn)這種異常的絕大部分時(shí)候其實(shí)都不存在 Output Buffer 已滿情況,而是處于一種“忙”的狀態(tài),而這種“忙”的狀態(tài)還很大程度上是由于接收方造成的。意思就是你要發(fā)送的對象,對方收的沒你發(fā)的快或者對方的接受緩沖區(qū)已被填滿,所以就返回你一個(gè)“忙”的標(biāo)志,而這時(shí)你再發(fā)多少數(shù)據(jù)都沒任何意義,所以你的系統(tǒng)就拋出個(gè) WSAEWOULDBLOCK 異常通知你,叫你別再瞎忙活了。
? 那么,我該怎么辦呢?網(wǎng)上有很多朋友的做法是遇到這種情況就 Sleep 一段時(shí)間,一般短暫停頓后 Output Buffer 就空出來了,那就又可以繼續(xù)發(fā)送了。不過我推薦另外的方法:根據(jù) MSDN 文檔所示,當(dāng)出現(xiàn) WSAEWOULDBLOCK 異常后直到空出 Output Buffer 時(shí),系統(tǒng)會發(fā)送一個(gè) FD_WRITE 給發(fā)送方。我們完全可以在等收到 FD_WRITE 消息后再重新發(fā)送從出現(xiàn)異常開始的數(shù)據(jù)包即可(該包需要全部重新發(fā)送)。
?
? 至此,該問題結(jié)案。最后順便提一下:FD_WRITE 消息會在至少三鐘情況下出現(xiàn),而上面只是其中的一種,所以我建議給 Socket 做個(gè)標(biāo)志判斷以便于規(guī)范性。
?
=================================WSA_IO_PENDING======================================
應(yīng)該是windows網(wǎng)絡(luò)編程第二版里面提到過?,F(xiàn)在整理一下。
1:在IOCP中投遞WSASend返回WSA_IO_PENDING的時(shí)候,表示異步投遞已經(jīng)成功,但是稍后發(fā)送才會完成。這其中涉及到了三個(gè)緩沖區(qū)。
網(wǎng)卡緩沖區(qū),TCP/IP層緩沖區(qū),程序緩沖區(qū)。
情況一:調(diào)用WSASend發(fā)送正確的時(shí)候(即立即返回,且沒有錯(cuò)誤),TCP/IP將數(shù)據(jù)從程序緩沖區(qū)中拷貝到TCP/IP層緩沖區(qū)中,然后不鎖定該程序緩沖區(qū),由上層程序自己處理。TCP/IP層緩沖區(qū)在網(wǎng)絡(luò)合適的時(shí)候,將其數(shù)據(jù)拷貝到網(wǎng)卡緩沖區(qū),進(jìn)行真正的發(fā)送。
情況二:調(diào)用WSASend發(fā)送錯(cuò)誤,但是錯(cuò)誤碼是WSA_IO_PENDING的時(shí)候,表示此時(shí)TCP/IP層緩沖區(qū)已滿,暫時(shí)沒有剩余的空間將程序緩沖區(qū)的數(shù)據(jù)拷貝出來,這時(shí)系統(tǒng)將鎖定用戶的程序緩沖區(qū),按照書上說的WSASend指定的緩沖區(qū)將會被鎖定到系統(tǒng)的非分頁內(nèi)存中。直到TCP/IP層緩沖區(qū)有空余的地方來接受拷貝我們的程序緩沖區(qū)數(shù)據(jù)才拷貝走,并將給IOCP一個(gè)完成消息。
情況三:調(diào)用WSASend發(fā)送錯(cuò)誤,但是錯(cuò)誤碼不是WSA_IO_PENDING,此時(shí)應(yīng)該是發(fā)送錯(cuò)誤,應(yīng)該釋放該SOCKET對應(yīng)的所有資源。
2:在IOCP中投遞WSARecv的時(shí)候,情況相似。
情況一:調(diào)用WSARecv正確,TCP/IP將數(shù)據(jù)從TCP/IP層緩沖區(qū)拷貝到緩沖區(qū),然后由我們的程序自行處理了。清除TCP/IP層緩沖區(qū)數(shù)據(jù)。
情況二:調(diào)用WSARecv錯(cuò)誤,但是返回值是WSA_IO_PENDING,此時(shí)是因?yàn)門CP/IP層緩沖區(qū)中沒有數(shù)據(jù)可取,系統(tǒng)將會鎖定我們投遞的WSARecv的buffer,直到TCP/IP層緩沖區(qū)中有新的數(shù)據(jù)到來。
情況三:調(diào)用WSARecv錯(cuò)誤,錯(cuò)誤值不是WSA_IO_PENDING,此時(shí)是接收出錯(cuò),應(yīng)該釋放該SOCKET對應(yīng)的所有資源。
在以上情況中有幾個(gè)非常要注意的事情:
系統(tǒng)鎖定非分頁內(nèi)存的時(shí)候,最小的鎖定大小是4K(當(dāng)然,這個(gè)取決于您系統(tǒng)的設(shè)置,也可以設(shè)置小一些,在注冊表里面可以改,當(dāng)然我想這些數(shù)值微軟應(yīng)該比我們更知道什么合適了),所以當(dāng)我們投遞了很多WSARecv或者WSASend的時(shí)候,不管我們投遞的Buffer有多大(0除外),系統(tǒng)在出現(xiàn)IO_PENGDING的時(shí)候,都會鎖定我們4K的內(nèi)存。這也就是經(jīng)常有開發(fā)者出現(xiàn)WSANOBUF的情況原因了。
我們在解決這個(gè)問題的時(shí)候,要針對WSASend和WSARecv做處理
1:投遞WSARecv的時(shí)候,可以采用一個(gè)巧妙的設(shè)計(jì),先投遞0大小Buf的WSARecv,如果返回,表示有數(shù)據(jù)可以接收,我們開啟真正的recv將數(shù)據(jù)從TCP/IP層緩沖區(qū)取出來,直到WSA_IO_PENGDING.
2:對投遞的WSARecv以及WSASend進(jìn)行計(jì)數(shù)統(tǒng)計(jì),如果超過了我們預(yù)定義的值,就不進(jìn)行WSASend或者WSARecv投遞了。
3:現(xiàn)在我們應(yīng)該就可以明白為什么WSASend會返回小于我們投遞的buffer空間數(shù)據(jù)值了,是因?yàn)門CP/IP層緩沖區(qū)小于我們要發(fā)送的緩沖區(qū),TCP/IP只會拷貝他剩余可被Copy的緩沖區(qū)大小的數(shù)據(jù)走,然后給我們的WSASend的已發(fā)送緩沖區(qū)設(shè)置為移走的大小,下一次投遞的時(shí)候,如果TCP/IP層還未被發(fā)送,將返回WSA_IO_PENGDING。
4:在很多地方有提到,可以關(guān)閉TCP/IP層緩沖區(qū),可以提高一些效率和性能,這個(gè)從上面的分析來看,有這個(gè)可能,要實(shí)際的網(wǎng)絡(luò)情況去實(shí)際分析了。
總結(jié)
以上是生活随笔為你收集整理的关于WSAEWOULDBLOCK和WSA_IO_PENDING错误的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: send函数阻塞
- 下一篇: 勒索病毒傀儡进程脱壳