青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

yehao's Blog

Winsock異步模式I/O模型WSAEventSelect的使用及FD_WRITE事件的觸發(fā)機(jī)制

http://oliver258.blog.51cto.com/750330/423813
1.Winsock同步阻塞方式的問題

在異步非阻塞模式下,像accept(WSAAccept),recv(recv,WSARecv,WSARecvFrom)等這樣的winsock函數(shù)調(diào)用后馬上返回,而不是等待可用的連接和數(shù)據(jù)。在阻塞模式下,server往往這樣等待client的連接:

while(TRUE)
{
    
//wait for a connection
     ClientSocket = accept(ListenSocket,NULL,NULL);
    if(ClientSocket == INVALID_SOCKET)
     {
         ERRORHANDLE
     }
     else
         DoSomething
}

上述代碼簡單易用,但是缺點(diǎn)在于如果沒有client連接的話,accept一直不會(huì)返回,而且即使accept成功創(chuàng)建會(huì)話套接字,在阻塞方式下,C/S間傳輸數(shù)據(jù)依然要將recv,send這類函數(shù)放到一個(gè)循環(huán)中,反復(fù)等待數(shù)據(jù)的到來,這種輪詢的方式效率很低。為此,Winsock提供了異步模式的5種I/O模型,這些模型會(huì)在有網(wǎng)絡(luò)事件(如socket收到連接請求,讀取收到的數(shù)據(jù)請求等等)時(shí)通過監(jiān)視集合(select),事件對(duì)象(WSAEventSelect,重疊I/O),窗口消息(WSAAsyncSelect),回調(diào)函數(shù)(重疊I/O),完成端口的方式通知程序,告訴我們可以“干活了”,這樣的話大大的提高了執(zhí)行效率,程序只需枕戈待旦,兵來將擋水來土掩,通知我們來什么網(wǎng)絡(luò)事件,就做相應(yīng)的處理即可。

2.WSAEventSelect模型的使用

WSAEventSelect模型其實(shí)很簡單,就是將一個(gè)事件對(duì)象同一個(gè)socket綁定并設(shè)置要監(jiān)視的網(wǎng)絡(luò)事件,當(dāng)這個(gè)socket有我們感興趣的網(wǎng)絡(luò)事件到達(dá)時(shí),ws2_32.dll就將這個(gè)事件對(duì)象置為受信狀態(tài)(signaled),在程序中等待這個(gè)事件對(duì)象受信后,根據(jù)網(wǎng)絡(luò)事件類型做不同的處理。如果對(duì)線程同步機(jī)制有些了解的話,這個(gè)模型很容易理解,其實(shí)就是CreateEvent系列的winsock版。

無代碼無真相,具體API的參數(shù)含義可以參考MSDN,MSDN上對(duì)這個(gè)模型解釋的非常詳盡。
// 使用WSAEventSelect的代碼片段,百度貼吧字?jǐn)?shù)限制,略去錯(cuò)誤處理及界面操作
    // 為了能和多個(gè)客戶端通信,使用兩個(gè)數(shù)組分別記錄所有通信的會(huì)話套接字
    // 以及和這些套接字綁定的事件對(duì)象
    // WSA_MAXIMUM_WAIT_EVENTS是系統(tǒng)內(nèi)部定義的宏,值為64

     SOCKET g_sockArray[WSA_MAXIMUM_WAIT_EVENTS];
     WSAEVENT g_eventArray[WSA_MAXIMUM_WAIT_EVENTS];

    // 事件對(duì)象計(jì)數(shù)器
    int nEventTotal = 0;

    // 創(chuàng)建監(jiān)聽套接字sListenSocket,并對(duì)其綁定端口和本機(jī)ip 代碼省去
     ........

    // 設(shè)置sListenSocket為監(jiān)聽狀態(tài)
     listen(sListenSocket, 5);

    // 創(chuàng)建事件對(duì)象,同CreateEvent一樣,event創(chuàng)建后被置為非受信狀態(tài)
     WSAEVENT acceptEvent = WSACreateEvent();

    // 將sListenSocket和acceptEvent關(guān)聯(lián)起來
    // 并注冊程序感興趣的網(wǎng)絡(luò)事件FD_ACCEPT 和 FD_CLOSE
    // 這里由于是在等待客戶端connect,所以FD_ACCEPT和FD_CLOSE是我們關(guān)心的
     WSAEventSelect(sListenSocket, acceptEvent, FD_ACCEPT|FD_CLOSE);

    // 添加到數(shù)組中
     g_eventArray[nEventTotal] = acceptEvent;
     g_sockArray[nEventTotal] = sListenSocket;    
     nEventTotal++;

    // 處理網(wǎng)絡(luò)事件
    while(TRUE)
     {
        // 由于第三個(gè)參數(shù)是 FALSE,所以 g_eventArray 數(shù)組中有一個(gè)元素受信 WSAWaitForMultipleEvents 就返回
        // 注意 返回值 nIndex 減去 WSA_WAIT_EVENT_0 的值才是受信事件在數(shù)組中的索引。
        // 如果有多個(gè)事件同時(shí)受信,函數(shù)返回索引值最小的那個(gè)。
        // 由于第四個(gè)參數(shù)指定 WSA_INFINITE ,所以沒有對(duì)象受信時(shí)會(huì)無限等待。
        int nIndex = WSAWaitForMultipleEvents(nEventTotal, g_eventArray, FALSE, WSA_INFINITE, FALSE);

        // 取得受信事件在數(shù)組中的位置。
         nIndex = nIndex - WSA_WAIT_EVENT_0;

        // 判斷受信事件 g_eventArray[nIndex] 所關(guān)聯(lián)的套接字 g_sockArray[nIndex] 的網(wǎng)絡(luò)事件類型
        // MSDN中說如果事件對(duì)象不是NULL, WSAEnumNetworkEvents 會(huì)幫咱重置該事件對(duì)象為非受信,方便等待新的網(wǎng)絡(luò)事件
        // 也就是說這里的 g_eventArray[nIndex] 變?yōu)榉鞘苄帕?,所以程序中不用再調(diào)用 WSAResetEvent了
        // WSANETWORKEVENTS 這個(gè)結(jié)構(gòu)中 記錄了關(guān)于g_sockArray[nIndex] 的網(wǎng)絡(luò)事件和錯(cuò)誤碼
         WSANETWORKEVENTS event;
         WSAEnumNetworkEvents(g_sockArray[nIndex], g_eventArray[nIndex], &event);

        // 這里處理 FD_ACCEPT 這個(gè)網(wǎng)絡(luò)事件
        // event.lNetWorkEvents中記錄的是網(wǎng)絡(luò)事件類型
        if(event.lNetworkEvents & FD_ACCEPT)
         {
            // event.iErrorCode是錯(cuò)誤代碼數(shù)組,event.iErrorCode[FD_ACCEPT_BIT] 為0表示正常
            if(event.iErrorCode[FD_ACCEPT_BIT] == 0)
             {
                // 連接數(shù)超過系統(tǒng)約定的范圍
                if(nEventTotal > WSA_MAXIMUM_WAIT_EVENTS)
                 {    
                     ErrorHandle...
                    continue;
                 }
                // 沒有問題就可以accept了
                 SOCKET sAcceptSocket = accept(g_sockArray[nIndex], NULL, NULL);

                // 新建的會(huì)話套接字用于C/S間的數(shù)據(jù)傳輸,所以這里關(guān)心FD_READ,FD_CLOSE,FD_WRITE三個(gè)事件
                 WSAEVENT event = WSACreateEvent();
                 WSAEventSelect(sAcceptSocket, event, FD_READ|FD_CLOSE|FD_WRITE);

                // 將新建的會(huì)話套接字及與該套接字關(guān)聯(lián)的事件對(duì)象添加到數(shù)組中
                 g_eventArray[nEventTotal] = event;
                 g_sockArray[nEventTotal] = sAcceptSocket;    
                 nEventTotal++;
             }

            //event.iErrorCode[FD_ACCEPT_BIT] != 0 出錯(cuò)了
             else
             {
                 ErrorHandle...
                break;
             }
         }


        // 這里處理FD_READ通知消息,當(dāng)會(huì)話套接字上有數(shù)據(jù)到來時(shí),ws2_32.dll會(huì)記錄該事件
         else if(event.lNetworkEvents & FD_READ)    
         {
            if(event.iErrorCode[FD_READ_BIT] == 0)
             {
                int nRecv = recv(g_sockArray[nIndex], buffer, nbuffersize, 0);
                if(nRecv == SOCKET_ERROR)                
                 {
                    // 為了程序更魯棒,這里要特別處理一下WSAEWOULDBLOCK這個(gè)錯(cuò)誤
                    // MSDN中說在異步模式下有時(shí)recv(WSARecv)讀取時(shí)winsock的緩沖區(qū)中沒有數(shù)據(jù),導(dǎo)致recv立即返回
                    // 錯(cuò)誤碼就是 WSAEWOULDBLOCK,但這時(shí)程序并沒有出問題,在有新的數(shù)據(jù)到來時(shí)recv還是可以讀到數(shù)據(jù)的
                    // 所以不能僅僅根據(jù)recv返回值是SOCKET_ERROR就認(rèn)為出錯(cuò)從而執(zhí)行退出操作。
                    //如果錯(cuò)誤碼不是WSAEWOULDBLOCK 則表示真的出錯(cuò)了
                    if(WSAGetLastError() != WSAEWOULDBLOCK)
                     {    
                         ErrorHandle...
                        break;
                     }
                 }
                // 沒出任何錯(cuò)誤
                 else
                     DoSomeThing...
             }

            // event.iErrorCode[FD_READ_BIT] != 0
             else
             {
                 ErrorHandle...
                break;
             }
         }


        // 這里處理FD_CLOSE通知消息
        // 當(dāng)連接被關(guān)閉時(shí),ws2_32.dll會(huì)記錄FD_CLOSE事件
         else if(event.lNetworkEvents & FD_CLOSE)
         {
            if(event.iErrorCode[FD_CLOSE_BIT] == 0)
             {
                 closesocket(g_sockArray[nIndex]);
                                 // 將g_sockArray[nIndex]從g_sockArray數(shù)組中刪除
                for(int j=nIndex; j<nEventTotal-1; j++)
                     g_sockArray[j] = g_sockArray[j+1];    
                 nEventTotal--;
             }

            // event.iErrorCode[FD_CLOSE_BIT] != 0
             else
             {
                 ErrorHandle...
                break;
             }
         }


        // 處理FD_WRITE通知消息
        // FD_WRITE事件其實(shí)就是ws2_32.dll告訴我們winsock的緩沖區(qū)已經(jīng)ok,可以發(fā)送數(shù)據(jù)了
        // 同recv一樣,send(WSASend)的返回值也要對(duì)SOCKET_ERROR特殊判斷一下 WSAEWOULDBLOCK
         else if(event.lNetworkEvents & FD_WRITE)        
         {
            //關(guān)于FD_WRITE的討論在下面。
         }
     }

    // 如果出錯(cuò)退出循環(huán) 則將套接字?jǐn)?shù)組中的套接字與事件對(duì)象統(tǒng)統(tǒng)解除關(guān)聯(lián)
    // 給WSAEventSelect的最后一個(gè)參數(shù)傳0可以解除g_sockArray[nIndex]和g_eventArray[nIndex]的關(guān)聯(lián)
    // 解除關(guān)聯(lián)后,ws2_32.dll將停止記錄g_sockArray[nIndex]這個(gè)套接字的網(wǎng)絡(luò)事件
    // 退出時(shí)還要關(guān)閉所有創(chuàng)建的套接字和事件對(duì)象

    for(int i = 0; i < nEventTotal; i++)
     {
         WSAEventSelect(g_sockArray[i], g_eventArray[i], 0);    
         closesocket(g_sockArray[i]);
         WSACloseEvent(g_eventArray[i]);
     }

     nEventTotal = 0;

     DoSomethingElse....

 

3.FD_WRITE 事件的觸發(fā)

常見的網(wǎng)絡(luò)事件中,F(xiàn)D_ACCEPT和FD_READ都比較好理解。一開始我唯一困惑的就是FD_WRITE,搞不清楚到底什么時(shí)候才會(huì)觸發(fā)這個(gè)網(wǎng)絡(luò)事件,后來仔細(xì)查了MSDN又看了一些文章并測試了下,終于搞懂了FD_WRITE的觸發(fā)機(jī)制。

下面是MSDN中對(duì)FD_WRITE觸發(fā)機(jī)制的解釋:

The FD_WRITE network event is handled slightly differently. An FD_WRITE network event is recorded when a socket is first connected with connect/WSAConnect or accepted with accept/WSAAccept, and then after a send fails with WSAEWOULDBLOCK and buffer space becomes available. Therefore, an application can assume that sends are possible starting from the first FD_WRITE network event setting and lasting until a send returns WSAEWOULDBLOCK. After such a failure the application will find out that sends are again possible when an FD_WRITE network event is recorded and the associated event object is set

FD_WRITE事件只有在以下三種情況下才會(huì)觸發(fā)

①client 通過connect(WSAConnect)首次和server建立連接時(shí),在client端會(huì)觸發(fā)FD_WRITE事件

②server通過accept(WSAAccept)接受client連接請求時(shí),在server端會(huì)觸發(fā)FD_WRITE事件

③send(WSASend)/sendto(WSASendTo)發(fā)送失敗返回WSAEWOULDBLOCK,并且當(dāng)緩沖區(qū)有可用空間時(shí),則會(huì)觸發(fā)FD_WRITE事件

①②其實(shí)是同一種情況,在第一次建立連接時(shí),C/S端都會(huì)觸發(fā)一個(gè)FD_WRITE事件。

主要是③這種情況:send出去的數(shù)據(jù)其實(shí)都先存在winsock的發(fā)送緩沖區(qū)中,然后才發(fā)送出去,如果緩沖區(qū)滿了,那么再調(diào)用send(WSASend,sendto,WSASendTo)的話,就會(huì)返回一個(gè) WSAEWOULDBLOCK的錯(cuò)誤碼,接下來隨著發(fā)送緩沖區(qū)中的數(shù)據(jù)被發(fā)送出去,緩沖區(qū)中出現(xiàn)可用空間時(shí),一個(gè) FD_WRITE 事件才會(huì)被觸發(fā),這里比較容易混淆的是 FD_WRITE 觸發(fā)的前提是 緩沖區(qū)要先被充滿然后隨著數(shù)據(jù)的發(fā)送又出現(xiàn)可用空間,而不是緩沖區(qū)中有可用空間,也就是說像如下的調(diào)用方式可能出現(xiàn)問題

else if(event.lNetworkEvents & FD_WRITE)
{
    if(event.iErrorCode[FD_WRITE_BIT] == 0)
     {
         send(g_sockArray[nIndex], buffer, buffersize);
         ....
     }
     else
     {
     }
}

問題在于建立連接后 FD_WRITE 第一次被觸發(fā), 如果send發(fā)送的數(shù)據(jù)不足以充滿緩沖區(qū),雖然緩沖區(qū)中仍有空閑空間,但是 FD_WRITE 不會(huì)再被觸發(fā),程序永遠(yuǎn)也等不到可以發(fā)送的網(wǎng)絡(luò)事件。

基于以上原因,在收到FD_WRITE事件時(shí),程序就用循環(huán)或線程不停的send數(shù)據(jù),直至send返回WSAEWOULDBLOCK,表明緩沖區(qū)已滿,再退出循環(huán)或線程。當(dāng)緩沖區(qū)中又有新的空閑空間時(shí),F(xiàn)D_WRITE 事件又被觸發(fā),程序被通知后又可發(fā)送數(shù)據(jù)了。

上面代碼片段中省略的對(duì) FD_WRITE 事件處理

 

else if(event.lNetworkEvents & FD_WRITE)
{
    if(event.iErrorCode[FD_WRITE_BIT] == 0)
     {
        while(TRUE)
         {
            // 得到要發(fā)送的buffer,可以是用戶的輸入,從文件中讀取等
             GetBuffer....
            if(send(g_sockArray[nIndex], buffer, buffersize, 0) == SOCKET_ERROR)
             {
                // 發(fā)送緩沖區(qū)已滿
                if(WSAGetLastError() == WSAEWOULDBLOCK)
                    break;
                 else
                     ErrorHandle...
             }
         }
     }
     else
     {
         ErrorHandle..
        break;
     }
}

 

 

P.S.

1.WSAWaitForMultipleEvents內(nèi)部調(diào)用的還是WaitForMulipleObjectsEx,MSDN中說使用WSAEventSelect模型等待時(shí)是不占cpu時(shí)間的,這也是效率比阻塞winsock高的原因。

2.WSAAsycSelect的用法和WSAEventSelect類似,不同的是網(wǎng)絡(luò)事件的通知是以windows消息的方式發(fā)送到指定的窗口。

 

posted on 2011-08-18 16:04 厚積薄發(fā) 閱讀(3432) 評(píng)論(0)  編輯 收藏 引用 所屬分類: 網(wǎng)絡(luò)編程

導(dǎo)航

<2025年11月>
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456

統(tǒng)計(jì)

常用鏈接

留言簿

隨筆分類

文章分類

文章檔案

搜索

最新評(píng)論

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            在线一区欧美| 国产精品一区亚洲| 久久久久久免费| 欧美—级在线免费片| 久久亚洲影院| 国产乱肥老妇国产一区二| 亚洲伦理在线观看| 亚洲国产精品久久久久婷婷老年| 午夜亚洲福利在线老司机| 亚洲天堂免费观看| 欧美美女视频| 亚洲高清免费在线| 国产综合视频| 欧美亚洲专区| 欧美一区亚洲二区| 国产精品一区免费在线观看| 99国产精品| 亚洲一级黄色| 欧美日韩妖精视频| 日韩视频中文字幕| 一本久久综合亚洲鲁鲁| 欧美成人中文字幕| 亚洲国产成人午夜在线一区| 亚洲高清在线播放| 美女久久网站| 亚洲国产精品久久人人爱蜜臀 | 国产精品永久在线| 一区二区高清在线| 亚洲一区二区精品在线| 国产精品久久久久久妇女6080 | 亚洲电影下载| av成人毛片| 国产精品国产三级国产aⅴ9色| 99精品视频免费全部在线| 亚洲一区二区在线播放| 欧美午夜精品久久久久久超碰| 一道本一区二区| 午夜在线视频观看日韩17c| 国产精品一区二区三区成人| 欧美一区二区在线免费播放| 美女视频网站黄色亚洲| 亚洲二区在线| 欧美人成在线视频| 亚洲影院高清在线| 美女视频黄 久久| 亚洲精品乱码久久久久| 国产精品成人va在线观看| 亚洲欧美日韩爽爽影院| 久久综合久久综合久久| 136国产福利精品导航网址| 欧美国产另类| 亚洲视频一区在线| 久久亚洲综合色一区二区三区| 亚洲国产另类久久精品| 欧美日韩福利| 欧美一进一出视频| 亚洲国产国产亚洲一二三| 亚洲一区二区视频| 一区视频在线播放| 欧美日韩大片一区二区三区| 亚洲欧美影院| 最新日韩av| 久久精品视频免费| 99亚洲一区二区| 国产在线视频欧美| 欧美日韩国产a| 久久精品一区二区国产| 99re6这里只有精品| 久久婷婷成人综合色| 亚洲天堂偷拍| 亚洲国产精选| 国产一区二区三区在线播放免费观看| 欧美成人免费在线| 久久精彩视频| 亚洲主播在线| 亚洲精品乱码久久久久久日本蜜臀| 久久九九有精品国产23| 宅男在线国产精品| 亚洲高清不卡在线| 国产一区二区成人久久免费影院| 欧美欧美在线| 毛片av中文字幕一区二区| 亚洲一区二区在线观看视频| 91久久精品www人人做人人爽| 久久久综合激的五月天| 亚洲欧美一区二区三区久久| 91久久久久久久久久久久久| 国产自产2019最新不卡| 国产精品看片资源| 欧美视频不卡| 欧美日韩国产123区| 欧美不卡在线视频| 久久精品一二三| 欧美一区午夜视频在线观看| 亚洲一区在线视频| 一区二区高清在线观看| 亚洲精品免费看| 91久久精品日日躁夜夜躁国产| 六月丁香综合| 免费欧美日韩| 免费成人小视频| 乱人伦精品视频在线观看| 久久精品亚洲一区二区| 性做久久久久久久免费看| 亚洲欧美国产制服动漫| 亚洲在线视频网站| 亚洲一区二区三区四区在线观看| 一区二区三区色| 亚洲性人人天天夜夜摸| 亚洲男人的天堂在线aⅴ视频| 亚洲午夜精品久久久久久app| 亚洲美女91| 亚洲视频第一页| 亚洲欧美日韩天堂| 欧美一区2区视频在线观看 | 最新国产拍偷乱拍精品| 亚洲欧洲日产国产网站| 亚洲免费精彩视频| 99国产欧美久久久精品| 亚洲色在线视频| 亚洲影院在线| 久久久久国产精品厨房| 久久影院亚洲| 欧美精品综合| 国产精品久久国产三级国电话系列| 国产精品每日更新在线播放网址| 国产女人精品视频| 精品成人在线观看| 亚洲日韩视频| 亚洲欧美日韩国产综合| 久久婷婷综合激情| 亚洲国产精品悠悠久久琪琪| 一本久道久久综合中文字幕| 亚洲欧美美女| 美女日韩在线中文字幕| 欧美调教视频| 一区二区三区在线观看欧美| 亚洲精品资源| 性欧美8khd高清极品| 久久综合图片| 亚洲精品久久嫩草网站秘色| 亚洲女同精品视频| 免费亚洲电影在线| 国产精品日韩欧美大师| 在线观看视频亚洲| 在线性视频日韩欧美| 久久久久久**毛片大全| 亚洲片在线观看| 欧美一区二区三区久久精品 | 久久综合九色欧美综合狠狠| 欧美日韩免费观看一区三区 | 亚洲天堂成人在线视频| 久久天天躁狠狠躁夜夜爽蜜月 | 一区二区视频欧美| 亚洲视频免费| 欧美成人精品三级在线观看| 一区二区三区高清| 久久亚洲精品欧美| 国产精品av一区二区| 亚洲第一免费播放区| 欧美亚洲免费| 日韩亚洲欧美成人一区| 久久成人精品无人区| 国产精品v欧美精品v日韩精品| 在线色欧美三级视频| 欧美中文字幕在线观看| 亚洲精品中文字幕在线| 久久久久久综合网天天| 国产精品色婷婷| 夜夜夜久久久| 欧美国产第一页| 久久aⅴ国产欧美74aaa| 国产精品欧美日韩| 在线视频精品一| 亚洲国产日韩一区| 久久影院午夜论| 狠狠色综合播放一区二区| 欧美有码在线观看视频| 日韩视频免费在线| 欧美精品电影在线| 亚洲国产综合在线| 欧美sm视频| 久久久国际精品| 加勒比av一区二区| 久久久精品999| 性欧美video另类hd性玩具| 国产精品私拍pans大尺度在线 | 中文一区字幕| 亚洲美女中出| 欧美无乱码久久久免费午夜一区| 一区二区三区高清视频在线观看| 亚洲二区免费| 欧美黄免费看| 日韩视频免费| 99在线精品视频在线观看| 欧美日韩一区二区视频在线观看 | 亚洲美女毛片| 欧美网站在线| 欧美一进一出视频| 欧美亚洲自偷自偷|