WSAAsyncSelect 消息模型

select 模型雖然可以管理多個(gè)socket,但是它涉及到一個(gè)時(shí)機(jī)的問題,select模型會(huì)針對(duì)所管理的數(shù)組中的每一個(gè)socket循環(huán)檢測(cè)它管理是否在對(duì)應(yīng)的數(shù)組中,從時(shí)間復(fù)雜度上來說它是O(n^2)的,而且還有可能發(fā)生數(shù)組中沒有socket處于待決狀態(tài)而導(dǎo)致本輪循環(huán)做無用功的情況,針對(duì)這些問題,winsock中有了新的模型——WSAAsyncSelect 消息模型

消息模型的核心是基于Windows窗口消息獲得網(wǎng)絡(luò)事件的通知,Windows窗口是用來與用戶交互的,而它并不知道用戶什么時(shí)候會(huì)操作窗口,所以Windows窗口本身就是基于消息的異步通知,網(wǎng)絡(luò)事件本身也是一個(gè)通知消息,將二者結(jié)合起來可以很好的使socket通知像消息那樣當(dāng)觸發(fā)通知時(shí)調(diào)用窗口過程。這樣就解決了select中的時(shí)機(jī)問題和里面兩層循環(huán)的問題
WSAAsyncSelect函數(shù)原型如下:

int WSAAsyncSelect(
    __in  SOCKET s,
    __in  HWND hWnd,
    __in  unsigned int wMsg,
    __in  long lEvent
);

第一個(gè)參數(shù)是綁定的socket,第二個(gè)參數(shù)是消息所對(duì)應(yīng)的窗口句柄,第三個(gè)參數(shù)是對(duì)應(yīng)的消息,這個(gè)消息需要自己定義,第4個(gè)參數(shù)是我們所關(guān)心的事件,當(dāng)在s這個(gè)socket發(fā)生lEvent這個(gè)事件發(fā)生時(shí)會(huì)向hWnd對(duì)應(yīng)的窗口發(fā)送wMsg消息。
在消息附帶的兩個(gè)參數(shù)wParam和lParam中,lParam的高位16位表示當(dāng)前的錯(cuò)誤碼,低16位表示當(dāng)前socket上發(fā)生的事件。其中事件的取值如下:

  1. FD_WRITE : 當(dāng)socket上可寫時(shí)觸發(fā)該事件,F(xiàn)D_WRITE的觸發(fā)與調(diào)用send沒有必然的聯(lián)系,F(xiàn)D_WRITE只是表示socket已經(jīng)為發(fā)送準(zhǔn)備好了必要的條件,其實(shí)調(diào)用時(shí)可以不必理會(huì)這個(gè)事件,只需要在想發(fā)送數(shù)據(jù)的場(chǎng)合調(diào)用send,一般來說FD_WRITE只在這些條件下觸發(fā):a) 調(diào)用connect函數(shù)成功連接到服務(wù)器 b) 調(diào)用accept接受連接成功后(該條件是綁定在accept返回的那個(gè)與客戶端通訊的socket上) c)調(diào)用send,sendto 失敗并返回WSAWOULDBLOCK(由于是異步操作,可能同時(shí)客戶端也在發(fā)數(shù)據(jù), 此時(shí)可能導(dǎo)致send失敗)
    為了方便我們處理這些參數(shù),WinSock 提供了兩個(gè)宏來解析它的高16位和低16位,分別是WSAGETSELECTERROR和WSAGETSELECTEVENT
    而lParam則保存了當(dāng)前觸發(fā)事件的socket句柄

如果對(duì)一個(gè)句柄調(diào)用了WSAAsyncSelect 并成功后,對(duì)應(yīng)的socket會(huì)自動(dòng)編程非阻塞模式。它就不像前面的select模型那樣需要顯示調(diào)用ioctrlsocket將socekt設(shè)置為非阻塞。
另外不需要每個(gè)socket都定義一個(gè)消息ID,通常一個(gè)ID已經(jīng)足夠處理所有的socket事件。
下面是一個(gè)具體的例子

int _tmain(int argc, TCHAR *argv[])
{
    WSADATA wd = {0};
    WSAStartup(MAKEWORD(2, 2), &wd);
    SOCKADDR_IN SrvAddr = {AF_INET};
    SrvAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    SrvAddr.sin_port = htons(SERVER_PORT);

    SOCKET skServer = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    if (INVALID_SOCKET == skServer)
    {
         printf("初始化socket失敗,錯(cuò)誤碼為:%08x\n", WSAGetLastError());
         goto __CLEAR_UP;
    }

    if (0 != bind(skServer, (SOCKADDR*)&SrvAddr, sizeof(SOCKADDR)))
    {
         printf("綁定失敗,錯(cuò)誤碼為:%08x\n", WSAGetLastError());
         goto __CLEAR_UP;
    }

    if (0 != listen(skServer, 5))
    {
        printf("監(jiān)聽失敗,錯(cuò)誤碼為:%08x\n", WSAGetLastError());
        goto __CLEAR_UP;
    }

    RegisterWindow();
    CreateAndShowWnd();

    g_uSockMsgID = RegisterWindowMessage(SOCKNOTIFY_MESSAGE);
    WSAAsyncSelect(skServer, g_hMainWnd, g_uSockMsgID, FD_ACCEPT | FD_CLOSE);

    MessageLoop();
__CLEAR_UP:
    if (INVALID_SOCKET != skServer)
    {
        closesocket(skServer);
    }
    WSACleanup();
    return 0;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    LRESULT lRes = 0;
    switch (uMsg)
    {
    case WM_CLOSE:
       {
             CloseWindow(hwnd);
               DestroyWindow(hwnd);
         }   
        break;
    case WM_PAINT:
        {
                PAINTSTRUCT ps = {0};
                BeginPaint(hwnd, &ps);
                EndPaint(hwnd, &ps);
        }
        break;
    case WM_DESTROY:
          PostQuitMessage(0);
          break;
    default:
            if (uMsg == g_uSockMsgID)
            {
                   lRes = ParseNotifyMessage(wParam, lParam);
            }
            lRes = DefWindowProc(hwnd, uMsg, wParam, lParam);
    }

    return lRes;
}

LRESULT ParseNotifyMessage(WPARAM wParam, LPARAM lParam)
{
    WORD wNotify = WSAGETSELECTEVENT(lParam);
    WORD wError = WSAGETSELECTERROR(lParam);

    if (wNotify == FD_ACCEPT)
    {
          return OnAcceptMsg((SOCKET)wParam, lParam);
    }else if (wNotify == FD_READ)
    {
          return OnReadMsg((SOCKET)wParam, lParam);
    }

    return 1;
}

LRESULT OnAcceptMsg(SOCKET s, LPARAM lParam)
{
    SOCKADDR_IN AddrClient = {0};
    int nAddrSize = sizeof(SOCKADDR);
    SOCKET sClient = accept(s, (SOCKADDR*)&AddrClient, &nAddrSize);
    printf("有客戶端連接進(jìn)來[%s:%u]\n", inet_ntoa(AddrClient.sin_addr), ntohs(AddrClient.sin_port));
    return WSAAsyncSelect(sClient, g_hMainWnd, g_uSockMsgID, FD_WRITE | FD_READ | FD_CLOSE);
}

LRESULT OnReadMsg(SOCKET s, LPARAM lParam)
{
    char *pszBuf = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 1024);
    ZeroMemory(pszBuf, 1024);
    int nTotalSize = 0;
    int i = 1;

    while (TRUE)
    {
          i++;
            int nReadSize = recv(s, pszBuf + nTotalSize, 1024, 0);
            if (nReadSize < 1024)
            {
                nTotalSize += nReadSize;
                break;
            }

            nTotalSize += nReadSize;
            HeapReAlloc(GetProcessHeap(), 0, pszBuf, 1024 * i);
    }

    if (strcmp(pszBuf, "exit") == 0)
    {
            shutdown(s, SD_BOTH);
            closesocket(s);
    }

    send(s, pszBuf, nTotalSize, 0);
    HeapFree(GetProcessHeap(), 0, pszBuf);

    return 0;
}

<hr />
在上面的代碼中我們?cè)趍ain函數(shù)中創(chuàng)建了窗口程序,而常規(guī)的都是在WinMain中創(chuàng)建,其實(shí)從本質(zhì)上講控制臺(tái)程序和窗口程序都是一個(gè)進(jìn)程,至于以main作為入口還是以WinMain作為入口只是習(xí)慣上這樣,但是并沒有硬性規(guī)定。
在創(chuàng)建窗口之后我們將監(jiān)聽socket也綁定到窗口消息中,然后在對(duì)應(yīng)的消息中判斷FD_ACCEPT事件,如果是則調(diào)用accept進(jìn)行連接。并將對(duì)生成的socket進(jìn)行綁定。
在接下來的socket消息中主要處理FD_READ事件,當(dāng)發(fā)生READ事件時(shí)調(diào)用read接收數(shù)據(jù),然后調(diào)用send將數(shù)據(jù)原封不動(dòng)的發(fā)送出去。

從上面的代碼上看,該模型相對(duì)于select來說省去了查看socket是否在對(duì)應(yīng)數(shù)組中的操作,減少了循環(huán)。而且可以很好的把握什么調(diào)用時(shí)機(jī)問題。
主要的缺點(diǎn)是它需要一個(gè)窗口,這樣在服務(wù)程序中基本就排除掉了這個(gè)模型,它基本上只會(huì)出現(xiàn)在客戶端程序中。
另外如果在一個(gè)窗口中需要管理成千上萬個(gè)句柄時(shí),它的性能會(huì)急劇下降,因此它的伸縮性較差。但是在客戶端中基本不存在這個(gè)問題,所以如果要在客戶端中想要減少編程難度,它是一個(gè)不二的選擇

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 大綱 一.Socket簡(jiǎn)介 二.BSD Socket編程準(zhǔn)備 1.地址 2.端口 3.網(wǎng)絡(luò)字節(jié)序 4.半相關(guān)與全相...
    VD2012閱讀 2,714評(píng)論 0 5
  • 必備的理論基礎(chǔ) 1.操作系統(tǒng)作用: 隱藏丑陋復(fù)雜的硬件接口,提供良好的抽象接口。 管理調(diào)度進(jìn)程,并將多個(gè)進(jìn)程對(duì)硬件...
    drfung閱讀 3,780評(píng)論 0 5
  • NIO(Non-blocking I/O,在Java領(lǐng)域,也稱為New I/O),是一種同步非阻塞的I/O模型,也...
    閃電是只貓閱讀 3,292評(píng)論 0 7
  • 依然是第二遍,現(xiàn)在的讀書筆記有了點(diǎn)學(xué)習(xí)的味道。我跳出了金妮的角色,更客觀地去尋找我和她身上的共同點(diǎn)。 我依然沒有經(jīng)...
    櫻雪花開閱讀 136評(píng)論 1 1
  • 初春是最迷人的時(shí)刻。原野上那一抹嫩綠醉飲甘醇的春雨,追逐春的情懷;把生命的血液注入小草,旖旎了春色,燦爛了春光...
    山野雅竹閱讀 125評(píng)論 0 0

友情鏈接更多精彩內(nèi)容