Winsock I/O模型之WSAAsyncSelect

來源:互聯網
上載者:User

Winsock提供了一個很有用的非同步I/O模型,利用這個模型,應用程式可以在一個通訊端上接
收以Windows訊息為基礎的網路事件通知。這個模型最開始出現在Winsock
1.1版本中,是為
了協助開發人員面向一些早期的16位Windows平台而設計的。但是現在的應用程式仍然可以從
這種模型中得到好處,就連MFC中的CSocket類也採納了這種模型。

由於該模型是基於Windows訊息機制的,所以要想使用這種模型必須要Create一個視窗,這
個視窗將會被用來接收訊息。接下來建立通訊端,然後調用WSAAsyncSelect函數,開啟視窗
訊息通知,函數原型如下:

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

其中s就是我們想要的那個通訊端;hWnd是接收訊息通知那個視窗控制代碼;wMsg參數指定在
發生網路事件時要接受的訊息,通常設成比WM_USER大的一個值,以避免訊息衝突;
lEvent指定了一個位元遮罩,對應一系列網路事件的組合,見下表:

Event

含義

FD_READ

程式想要接收有關是否可讀的通知,以便讀入資料

FD_WRITE

程式想要接收有關是否可寫的通知,以便寫入資料

FD_OOB

程式想要接收是否有OOB資料到達的通知

FD_ACCEPT

程式想要接收與進入串連有關的通知

FD_CONNECT

程式想要接收與一次串連或多點接入有關的通知

FD_CLOSE

程式想要接收與通訊端關閉有關的通知

FD_QOS

程式想要接收通訊端“服務品質(QoS)”發生變化的通知

FD_GROUP_QOS

暫時沒用,屬於保留事件

FD_ROUTING_INTERFACE_CHANGE

程式想要接收有關到指定地址的路由介面發生變化的通知

FD_ADDRESS_LIST_CHANGE

程式想要接收本地地址變化的通知

當程式在一個通訊端上調用WSAAsyncSelect成功後,這個程式就會在與hWnd視窗控制代碼對
應的視窗常式中以Windows訊息的形式接收網路事件通知。視窗常式通常定義成這個樣子:

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,

         LPARAM
lParam)
其中wParam參數指定在其上面發生了一個網路事件的通訊端,如果定義了多個通訊端,這
個參數就顯得很重要了。lParam參數則包含了兩方面的重要訊息,它的低位字指定了已經發
生的網路事件,而高位字包含了可能出現的錯誤碼。

簡單的來說,這個模型的具體使用流程就是:
當網路訊息抵達一個視窗常式後,程式要先檢測lParam的高位位元組,從而判斷是否在通訊端
上面發生了網路錯誤。現成的宏已經有在這裡了
-->
WSAGETSELECTERROR,可以用它返
回高位元組包含的錯誤資訊,如果沒有發現任何的錯誤,接下來就是確定究竟是什麼類型的網
絡事件觸發了這條Windows訊息,這個操作也有現成的宏
--> WSAGETSELECTEVENT

下面就是原始碼,其中部分很基本的代碼我就省略掉了,編譯平台為
Win2000 Server with SP2 + VC6.0 with
SP5

#include <windows.h>
#include <winsock2.h>

#define PORT 5150
#define DATA_BUFSIZE 8192

typedef struct _SOCKET_INFORMATION {
 BOOL RecvPosted;
 CHAR
Buffer[DATA_BUFSIZE];
 WSABUF DataBuf;
 SOCKET Socket;
 DWORD
BytesSEND;
 DWORD BytesRECV;
 _SOCKET_INFORMATION *Next;
}
SOCKET_INFORMATION, * LPSOCKET_INFORMATION;

#define WM_SOCKET (WM_USER + 1)

void CreateSocketInformation(SOCKET s, HWND);
LPSOCKET_INFORMATION
GetSocketInformation(SOCKET s);
void FreeSocketInformation(SOCKET s);

LPSOCKET_INFORMATION SocketInfoList;

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE
hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
 DWORD
Ret;
 SOCKET Listen;
 SOCKADDR_IN InternetAddr;
 WSADATA
wsaData;
 static TCHAR szAppName[] = TEXT ("HelloWin") ;
 HWND hwnd
;
 MSG msg ;
 WNDCLASS wndclass ;

 // Prepare echo server
 wndclass.style = CS_HREDRAW | CS_VREDRAW
;
 ...
 ...
 RegisterClass (&wndclass);
 hwnd = CreateWindow
(...) ; // creation parameters
 ShowWindow (hwnd, nCmdShow)
;
 UpdateWindow (hwnd) ;

 if ((Ret = WSAStartup(0x0202, &wsaData))
!= 0)
 {
  MessageBox(hwnd, TEXT("Start socket failed"), TEXT("error"),
MB_OK);
  ExitProcess(1);
 }
 if ((Listen = socket (PF_INET,
SOCK_STREAM, 0)) == INVALID_SOCKET)
 {
  MessageBox(hwnd, TEXT("socket()
failed"), TEXT("error"),
MB_OK);
  ExitProcess(1);
 }
 WSAAsyncSelect(Listen, hwnd, WM_SOCKET,
FD_ACCEPT|FD_CLOSE);
 InternetAddr.sin_family =
AF_INET;
 InternetAddr.sin_addr.s_addr =
htonl(INADDR_ANY);
 InternetAddr.sin_port = htons(PORT);

 if (bind(Listen, (PSOCKADDR) &InternetAddr,
sizeof(InternetAddr))
    == SOCKET_ERROR)
 {
  MessageBox(hwnd,
TEXT("bind() failed"), TEXT("error"), MB_OK);
  ExitProcess(1);
 }
 if
(listen(Listen, 5))
 {
  MessageBox(hwnd, TEXT("listen() failed"),
TEXT("error"), MB_OK);
  ExitProcess(1);
 }
 // Translate and dispatch
window messages for the application thread
 while (GetMessage (&msg,
NULL, 0, 0))
 {
  TranslateMessage (&msg) ;
  DispatchMessage
(&msg) ;
 }
 return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message,
        WPARAM wParam,
LPARAM lParam)
{
 HDC hdc ;
 PAINTSTRUCT ps ;
 RECT rect
;
 SOCKET Accept;
 LPSOCKET_INFORMATION SocketInfo;
 DWORD RecvBytes,
SendBytes;
 DWORD Flags;

 switch (message)
 {
  case
WM_CREATE:
   return 0 ;

  case WM_PAINT:
   hdc = BeginPaint
(hwnd, &ps) ;
   GetClientRect (hwnd, &rect) ;
   DrawText (hdc,
TEXT ("Server Started!"), -1, &rect,
   DT_SINGLELINE | DT_CENTER |
DT_VCENTER) ;
   EndPaint (hwnd, &ps) ;
   return 0 ;

  case
WM_DESTROY:
   PostQuitMessage (0) ;
   return 0 ;

  case WM_SOCKET:
   if
(WSAGETSELECTERROR(lParam))
   {
    MessageBox(...);
    FreeSocketInformation(wParam);
   }

   else
   {
    switch(WSAGETSELECTEVENT(lParam))
    {
     case
FD_ACCEPT:
      if ((Accept = accept(wParam, NULL, NULL)) ==
INVALID_SOCKET)
      {

       MessageBox(...);
       break;
      }
      // Create a
socket information structure to associate with the
      // socket for
processing I/O.
      CreateSocketInformation(Accept,
hwnd);
      WSAAsyncSelect(Accept, hwnd, WM_SOCKET,

         FD_READ|FD_WRITE|FD_CLOSE);
      break;

     case FD_READ:
      SocketInfo =
GetSocketInformation(wParam);
      // Read data only if the receive buffer
is empty.
      if (SocketInfo->BytesRECV !=
0)
      {
       SocketInfo->RecvPosted = TRUE;
       return
0;
      }
      else
      {
       SocketInfo->DataBuf.buf =
SocketInfo->Buffer;
       SocketInfo->DataBuf.len =
DATA_BUFSIZE;
       Flags = 0;
       if (WSARecv(SocketInfo->Socket,
&(SocketInfo->DataBuf),
           1, &RecvBytes, &Flags,
NULL, NULL) == SOCKET_ERROR)
       {
        if (WSAGetLastError() !=
WSAEWOULDBLOCK)
        {
         MessageBox(...);
         FreeSocketInformation(wParam);
         return
0;
        }
       }
       else // No error so update the byte
count
        SocketInfo->BytesRECV = RecvBytes;
      }
      // DO
NOT BREAK HERE SINCE WE GOT A SUCCESSFUL RECV.
      // Go ahead and begin
writing data to the client.

      case FD_WRITE:
       SocketInfo =
GetSocketInformation(wParam);
       if (SocketInfo->BytesRECV >
SocketInfo->BytesSEND)
       {
        SocketInfo->DataBuf.buf =

         SocketInfo->Buffer +
SocketInfo->BytesSEND;
        SocketInfo->DataBuf.len =

         SocketInfo->BytesRECV - SocketInfo->BytesSEND;
        if
(WSASend(SocketInfo->Socket, &(SocketInfo->DataBuf),

            1, &SendBytes, 0,NULL, NULL) ==
SOCKET_ERROR)
        {
         if (WSAGetLastError() !=
WSAEWOULDBLOCK)
         {
          MessageBox(...);
          FreeSocketInformation(wParam);
          return
0;
         }
        }
        else // No error so update the byte
count
         SocketInfo->BytesSEND += SendBytes;
       }

       if (SocketInfo->BytesSEND ==
SocketInfo->BytesRECV)
       {
        SocketInfo->BytesSEND =
0;
        SocketInfo->BytesRECV = 0;
        // If a RECV occurred
during our SENDs then we need to post
        // an FD_READ notification on
the socket.

        if (SocketInfo->RecvPosted ==
TRUE)
        {
         SocketInfo->RecvPosted =
FALSE;
         PostMessage(hwnd, WM_SOCKET, wParam,
FD_READ);
        }
       }
       if(SocketInfo->DataBuf.buf !=
NULL)
        MessageBox(hwnd, SocketInfo->DataBuf.buf,
          
TEXT("Received"), MB_OK);
       break;

      case
FD_CLOSE:
       FreeSocketInformation(wParam);
       break;
    }
   }
   return
0;
 }
 return DefWindowProc(hwnd, message, wParam, lParam);
}

void CreateSocketInformation(SOCKET s, HWND
hwnd)
{
 LPSOCKET_INFORMATION SI;
 if ((SI = (LPSOCKET_INFORMATION)
GlobalAlloc(GPTR,
 sizeof(SOCKET_INFORMATION))) ==
NULL)
 {
  MessageBox(...);
  return;
 }
 // Prepare SocketInfo
structure for use.
 SI->Socket = s;
 SI->RecvPosted =
FALSE;
 SI->BytesSEND = 0;
 SI->BytesRECV = 0;
 SI->Next =
SocketInfoList;
 SocketInfoList = SI;
}

LPSOCKET_INFORMATION GetSocketInformation(SOCKET
s)
{
 SOCKET_INFORMATION *SI = SocketInfoList;
 while(SI)
 {
  if
(SI->Socket == s)
  return SI;
  SI = SI->Next;
 }
 return
NULL;
}

void FreeSocketInformation(SOCKET s)
{
 SOCKET_INFORMATION *SI =
SocketInfoList;
 SOCKET_INFORMATION *PrevSI =
NULL;
 while(SI)
 {
  if (SI->Socket == s)
  {
   if
(PrevSI)
    PrevSI->Next = SI->Next;
   else
    SocketInfoList
=
SI->Next;
   closesocket(SI->Socket);
   GlobalFree(SI);
   return;
  }
  PrevSI
= SI;
  SI = SI->Next;
 }
}

伺服器就這樣建好了,只需要一個客戶機就可以通訊了,具體的代碼我就不再貼了,各位可
以自己設計客戶機,或者去下載區下載來源程式。最後向大家推薦《Windows網路編程技術》,
真的不錯。

本文中部分內容翻譯自MSDN,客戶機程式使用的是《Windows網路編程技術》中的例子

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.