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

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
}

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

2.WSAEventSelect模型的使用

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

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

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

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

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

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

    // 創(chuàng)建事件對象,同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)
     {
        // 由于第三個參數(shù)是 FALSE,所以 g_eventArray 數(shù)組中有一個元素受信 WSAWaitForMultipleEvents 就返回
        // 注意 返回值 nIndex 減去 WSA_WAIT_EVENT_0 的值才是受信事件在數(shù)組中的索引。
        // 如果有多個事件同時受信,函數(shù)返回索引值最小的那個。
        // 由于第四個參數(shù)指定 WSA_INFINITE ,所以沒有對象受信時會無限等待。
        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中說如果事件對象不是NULL, WSAEnumNetworkEvents 會幫咱重置該事件對象為非受信,方便等待新的網(wǎng)絡(luò)事件
        // 也就是說這里的 g_eventArray[nIndex] 變?yōu)榉鞘苄帕耍猿绦蛑胁挥迷僬{(diào)用 WSAResetEvent了
        // WSANETWORKEVENTS 這個結(jié)構(gòu)中 記錄了關(guān)于g_sockArray[nIndex] 的網(wǎng)絡(luò)事件和錯誤碼
         WSANETWORKEVENTS event;
         WSAEnumNetworkEvents(g_sockArray[nIndex], g_eventArray[nIndex], &event);

        // 這里處理 FD_ACCEPT 這個網(wǎng)絡(luò)事件
        // event.lNetWorkEvents中記錄的是網(wǎng)絡(luò)事件類型
        if(event.lNetworkEvents & FD_ACCEPT)
         {
            // event.iErrorCode是錯誤代碼數(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);

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

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

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


        // 這里處理FD_READ通知消息,當(dāng)會話套接字上有數(shù)據(jù)到來時,ws2_32.dll會記錄該事件
         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這個錯誤
                    // MSDN中說在異步模式下有時recv(WSARecv)讀取時winsock的緩沖區(qū)中沒有數(shù)據(jù),導(dǎo)致recv立即返回
                    // 錯誤碼就是 WSAEWOULDBLOCK,但這時程序并沒有出問題,在有新的數(shù)據(jù)到來時recv還是可以讀到數(shù)據(jù)的
                    // 所以不能僅僅根據(jù)recv返回值是SOCKET_ERROR就認(rèn)為出錯從而執(zhí)行退出操作。
                    //如果錯誤碼不是WSAEWOULDBLOCK 則表示真的出錯了
                    if(WSAGetLastError() != WSAEWOULDBLOCK)
                     {    
                         ErrorHandle...
                        break;
                     }
                 }
                // 沒出任何錯誤
                 else
                     DoSomeThing...
             }

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


        // 這里處理FD_CLOSE通知消息
        // 當(dāng)連接被關(guān)閉時,ws2_32.dll會記錄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事件其實就是ws2_32.dll告訴我們winsock的緩沖區(qū)已經(jīng)ok,可以發(fā)送數(shù)據(jù)了
        // 同recv一樣,send(WSASend)的返回值也要對SOCKET_ERROR特殊判斷一下 WSAEWOULDBLOCK
         else if(event.lNetworkEvents & FD_WRITE)        
         {
            //關(guān)于FD_WRITE的討論在下面。
         }
     }

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

    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,搞不清楚到底什么時候才會觸發(fā)這個網(wǎng)絡(luò)事件,后來仔細(xì)查了MSDN又看了一些文章并測試了下,終于搞懂了FD_WRITE的觸發(fā)機(jī)制。

下面是MSDN中對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事件只有在以下三種情況下才會觸發(fā)

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

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

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

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

主要是③這種情況:send出去的數(shù)據(jù)其實都先存在winsock的發(fā)送緩沖區(qū)中,然后才發(fā)送出去,如果緩沖區(qū)滿了,那么再調(diào)用send(WSASend,sendto,WSASendTo)的話,就會返回一個 WSAEWOULDBLOCK的錯誤碼,接下來隨著發(fā)送緩沖區(qū)中的數(shù)據(jù)被發(fā)送出去,緩沖區(qū)中出現(xiàn)可用空間時,一個 FD_WRITE 事件才會被觸發(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 不會再被觸發(fā),程序永遠(yuǎn)也等不到可以發(fā)送的網(wǎng)絡(luò)事件。

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

上面代碼片段中省略的對 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模型等待時是不占cpu時間的,這也是效率比阻塞winsock高的原因。

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

 

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

導(dǎo)航

<2025年9月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

統(tǒ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>
            久久久午夜电影| 国产精品家庭影院| 中文无字幕一区二区三区| 亚洲精品一区久久久久久| 一本大道av伊人久久综合| 亚洲无人区一区| 欧美一区中文字幕| 日韩视频免费观看| 一区二区三区精品视频| 一区二区三区高清视频在线观看| 日韩视频一区二区三区在线播放免费观看 | 免费观看国产成人| 亚洲丶国产丶欧美一区二区三区 | 先锋亚洲精品| 玖玖综合伊人| 亚洲人成在线观看网站高清| 亚洲免费中文字幕| 亚洲视频大全| 欧美亚洲综合久久| 欧美成人亚洲| 亚洲影音先锋| 欧美成人国产一区二区 | 伊人激情综合| 一区二区免费在线视频| 久久久久国产精品麻豆ai换脸| 亚洲第一页中文字幕| 亚洲欧美日韩电影| 欧美国产视频一区二区| 国产色综合网| 亚洲一区二区av电影| 欧美成人黑人xx视频免费观看| 亚洲视频久久| 欧美区亚洲区| 亚洲福利视频三区| 久久精品国产第一区二区三区| 亚洲精品综合精品自拍| 久久女同精品一区二区| 国产精品久久久久久亚洲调教| 亚洲人成人一区二区在线观看| 欧美尤物一区| 亚洲天堂激情| 欧美日韩日本网| 亚洲精品中文字幕在线观看| 美女免费视频一区| 欧美一区二区视频免费观看| 国产精品久久久久久久久久直播 | 久久久久久久久久久久久久一区| 欧美性一二三区| 一区二区免费在线观看| 亚洲第一偷拍| 蜜臀av国产精品久久久久| 一区二区三区在线不卡| 久久久久国产精品午夜一区| 亚洲综合首页| 国产日韩欧美在线播放| 欧美在线免费| 欧美在线首页| 精品91在线| 欧美黄色成人网| 欧美不卡一区| 在线一区视频| 亚洲一区二区三区三| 国产精品综合视频| 久久久999成人| 久久久久国内| 亚洲精品在线免费观看视频| 日韩视频免费在线| 国产精品嫩草影院av蜜臀| 午夜精品国产| 久久岛国电影| 亚洲精品欧美日韩| 亚洲免费观看高清完整版在线观看| 久久久99久久精品女同性| 韩国成人精品a∨在线观看| 欧美一区=区| 久久国产精品久久国产精品| 1000部精品久久久久久久久| 亚洲国产精品一区二区尤物区| 欧美区二区三区| 香蕉成人久久| 久久久www成人免费毛片麻豆| 亚洲人成网站在线播| 一本久久综合| 国语对白精品一区二区| 亚洲黄色免费电影| 国产精品视频观看| 蘑菇福利视频一区播放| 欧美日韩国产色站一区二区三区| 午夜精品视频在线| 男人的天堂亚洲| 性欧美在线看片a免费观看| 久久视频免费观看| 亚洲视频日本| 久久天堂精品| 午夜精品久久久久久久久久久久久 | 亚洲精品中文字幕女同| 9l国产精品久久久久麻豆| 国产精品伦一区| 久久综合图片| 欧美视频中文一区二区三区在线观看| 性亚洲最疯狂xxxx高清| 久久男人资源视频| 亚洲影视在线| 老司机精品导航| 先锋影院在线亚洲| 欧美国产日韩一二三区| 欧美一站二站| 欧美视频在线视频| 亚洲国产欧美在线人成| 精品51国产黑色丝袜高跟鞋| 亚洲无人区一区| 一区二区三区产品免费精品久久75| 久久精品国产综合| 欧美在线播放一区| 欧美视频在线观看视频极品| 亚洲国产欧美一区| 亚洲国产综合视频在线观看| 亚洲女ⅴideoshd黑人| 一本色道久久精品| 欧美成人久久| 欧美成人精品不卡视频在线观看 | 亚洲日本中文字幕区| 在线观看日韩专区| 欧美一级免费视频| 欧美在线视频全部完| 欧美午夜免费电影| 中日韩午夜理伦电影免费| 国产欧美视频一区二区| 一本久久a久久精品亚洲| 午夜欧美精品| 欧美一区2区三区4区公司二百| 欧美日韩视频在线一区二区| 亚洲国产精品视频| 亚洲国产成人porn| 久久精品青青大伊人av| 久久免费一区| 精品成人在线视频| 久久裸体艺术| 欧美国产激情| 亚洲精品视频在线播放| 欧美激情中文字幕一区二区| 亚洲国产精品高清久久久| 亚洲精品久久久久久久久久久久 | 亚洲免费av片| 日韩午夜av| 欧美午夜不卡视频| 亚洲一区二区三区在线观看视频 | 欧美成人精品高清在线播放| 亚洲国产精品一区制服丝袜 | 亚洲欧美日韩精品综合在线观看| 销魂美女一区二区三区视频在线| 国产麻豆精品在线观看| 久久精品一区蜜桃臀影院 | 国产精品久久久久久久第一福利| 一区二区动漫| 久久精品欧美日韩精品| 亚洲高清三级视频| 欧美日韩精品一区二区天天拍小说 | 久久精品一区二区三区四区| 国产在线观看一区| 久热精品视频在线观看一区| 91久久精品一区二区别| 亚洲欧美日韩综合| 亚洲第一在线视频| 欧美午夜在线| 久久久久久网| 在线亚洲电影| 欧美成人免费小视频| 亚洲无线视频| 在线观看欧美激情| 国产精品美女| 欧美成人伊人久久综合网| 亚洲一区在线免费| 亚洲韩国一区二区三区| 久久久www| 亚洲四色影视在线观看| 在线观看欧美| 亚洲麻豆国产自偷在线| 国产精品ⅴa在线观看h| 久久精品视频免费观看| 日韩午夜在线视频| 久久一区中文字幕| 一本久道久久综合狠狠爱| 国产一区二区三区在线播放免费观看| 能在线观看的日韩av| 午夜老司机精品| 亚洲精品中文字幕女同| 老牛嫩草一区二区三区日本 | 欧美大色视频| 久久精品天堂| 亚洲欧美国产毛片在线| 日韩视频永久免费观看| 极品av少妇一区二区| 国产精品任我爽爆在线播放| 欧美精品三级日韩久久| 免费成年人欧美视频| 欧美亚洲一区在线| 亚洲欧美激情一区| 在线一区二区三区四区| 亚洲三级性片|