• <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>

            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
            }

            上述代碼簡(jiǎn)單易用,但是缺點(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收到連接請(qǐng)求,讀取收到的數(shù)據(jù)請(qǐng)求等等)時(shí)通過監(jiān)視集合(select),事件對(duì)象(WSAEventSelect,重疊I/O),窗口消息(WSAAsyncSelect),回調(diào)函數(shù)(重疊I/O),完成端口的方式通知程序,告訴我們可以“干活了”,這樣的話大大的提高了執(zhí)行效率,程序只需枕戈待旦,兵來將擋水來土掩,通知我們來什么網(wǎng)絡(luò)事件,就做相應(yīng)的處理即可。

            2.WSAEventSelect模型的使用

            WSAEventSelect模型其實(shí)很簡(jiǎn)單,就是將一個(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)起來
                // 并注冊(cè)程序感興趣的網(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又看了一些文章并測(cè)試了下,終于搞懂了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連接請(qǐng)求時(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ā) 閱讀(3360) 評(píng)論(0)  編輯 收藏 引用 所屬分類: 網(wǎng)絡(luò)編程

            導(dǎo)航

            <2025年5月>
            27282930123
            45678910
            11121314151617
            18192021222324
            25262728293031
            1234567

            統(tǒng)計(jì)

            常用鏈接

            留言簿

            隨筆分類

            文章分類

            文章檔案

            搜索

            最新評(píng)論

            岛国搬运www久久| 久久久国产精品亚洲一区| 亚洲国产欧美国产综合久久 | 久久综合亚洲色HEZYO社区 | 久久精品九九亚洲精品| 久久99精品久久久大学生| 久久久久人妻精品一区| 久久精品国产亚洲AV影院 | 国产精品久久久久久五月尺| 久久精品18| 超级碰碰碰碰97久久久久| 亚洲国产精品狼友中文久久久| 综合久久国产九一剧情麻豆| 国产精品久久久久影院色| 久久久久免费精品国产| 亚洲成av人片不卡无码久久| 欧美熟妇另类久久久久久不卡 | 日本久久中文字幕| 久久夜色精品国产噜噜亚洲a| 亚洲中文久久精品无码ww16| 亚洲精品无码成人片久久| 99久久婷婷国产综合亚洲| 午夜精品久久影院蜜桃| 久久久国产精华液| 9久久9久久精品| 亚洲欧美日韩久久精品| 精品无码久久久久久午夜| 国产免费福利体检区久久| 亚洲国产天堂久久综合| 亚洲精品乱码久久久久久按摩| 久久99热精品| 精品免费tv久久久久久久| 欧美日韩中文字幕久久久不卡| 久久99热这里只有精品国产| 久久91精品国产91久久户| 久久只有这精品99| 亚洲一区中文字幕久久| 久久精品国产91久久综合麻豆自制| 精品无码人妻久久久久久| 国产精品伦理久久久久久| 亚洲精品美女久久久久99|