套接字I/O模型-WSAAsyncSelect

利用這個異步I/O模型,應用程序可在一個套接字上接收以Windows消息爲基礎的網絡事件通知。WSAAsyncSelect和WSAEventSelect提供讀寫數據能力的異步通知,但它們不提供異步數據傳輸,重疊及完成端口提供異步數據傳輸。

消息通知

要想使用WSAAsyncSelect模型,在應用程序中,首先必須用CreateWindow函數創建一個窗口,再爲該窗口提供一個窗口過程支持函數,亦可使用一個對話框,爲其提供一個對話框過程來代替窗口過程,這是因爲對話框本質也是窗口。

  1. int WSAAsyncSelect( 
  2.   SOCKET s,//我們感興趣的套接字 
  3.   HWND hwnd,//窗口句柄,標識的是網絡事件發生之後,想要收到通知消息的那個窗口或對話框 
  4.   unsigned int wMsg,//在發生網絡事件時,打算接收的消息,該消息將被投遞到由hwnd窗口句柄所標識的那個窗口 
  5.   long lEvent//網絡事件組合 
  6. ); 

大多數應用程序感興趣的網絡事件類型包括:FD_READ,FD_WRITE,FD_ACCEPT,FD_CONNECT,FD_CLOSE

WSAAsyncSelect(s, hwnd, WM_SOCKET, FD_CONNECT|FD_READ|FD_WRITE|FD_CLOSE);

這樣應用程序可在套接字s上接收到有關連接發送接收以及關閉套接字這一網絡事件的通知。特別注意的是對各網絡事件務必在套接字上一次完成註冊。一旦某個套接字上啓用了事件通知,那麼以後除非明確調用closesocket,或者由應用程序針對這個套接字調用WSAAsyncSelect,從而更改註冊的網絡事件類型,否則網絡事件總是有效。若將lEvent設爲0,則效果相當於停止在套接字上進行的所有網絡事件通知。

若應用程序針對一個套接字調用WSAsyncSelect,那麼套接字的模式會從阻塞模式自動轉換爲非阻塞模式。

演示WSAAsyncSelect程序基本流程:

  1. #include<windows.h> 
  2. #include<winsock2.h> 
  3. #pragma comment(lib, "ws2_32.lib") 
  4. #define WM_SOCKET WM_USER+1 
  5.  
  6. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
  7.                    LPSTR lpCmdLine, int nCmdShow) 
  8.   WSADATA wsaData; 
  9.   SOCKET Listen; 
  10.   SOCKADDR_IN addr; 
  11.   HWND window; 
  12.   //創建窗口 
  13.   window = CreateWindow(); 
  14.    
  15.   WSAStartup(MAKEWORD(2,2), &wsaData); 
  16.   Listen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
  17.   memset(addr, 0, sizeof(SOCKADDR_IN)); 
  18.   addr.sin_family = AF_INET; 
  19.   addr.sin_port = htons(5050); 
  20.   addr.sin_addr.s_addr = hotnl(INADDR_ANY); 
  21.    
  22.   bind(Listen, (SOCKADDR*)&addr, sizeof(SOCKADDR_IN)); 
  23.   //使用定義的WM_SOCKET 在新套接字上設置窗口消息通知 
  24.   WSAAsyncSelect(Listen, window, WM_SOCKET, FD_ACCEPT|FD_CLOSE); 
  25.   listen(Listen, 5); 
  26.   //轉換並分配消息 
  27.   while(1) 
  28.   { 
  29.     ... 
  30.   } 
  31.  
  32. BOOL CALLBACK ServerProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam) 
  33.   SOCKET Accept; 
  34.   switch(wMsg) 
  35.   { 
  36.   case WM_PAINT: 
  37.     break
  38.   case WM_SOCKET: 
  39.     //使用WSAGETSELECTERROR宏來判斷套接字上是否發生了錯誤 
  40.     if(WSAGETSELECTERROR(lParam)) 
  41.     { 
  42.       //顯示錯誤,關閉套接字 
  43.       closesocket((SOCKET)wParam); 
  44.       break
  45.     } 
  46.     //確定在套接字上發生什麼事件 
  47.     switch(WSAGETSELECTEVENT(lParam)) 
  48.     { 
  49.     case FD_ACCEPT: 
  50.       //接受一個傳入的連接 
  51.       Accept = accept(wParam, NULL, NULL); 
  52.       //讓接收套接字爲讀寫及關閉通知做好準備 
  53.       WSAAsyncSelect(Accept, hDlg, WM_SOCKET, FD_READ|FD_WRITE|FD_CLOSE); 
  54.       break
  55.     case FD_READ: 
  56.       //從wParam中的套接字中檢索數據 
  57.       break
  58.     case FD_WRITE: 
  59.       //wParam中的套接字已準備好發送數據 
  60.       break
  61.     case FD_CLOSE: 
  62.       closesocket((SOCKET)wParam); 
  63.       break
  64.     } 
  65.     break
  66.   } 
  67.   return true

最後一個特別有價值的問題是應用程序如何對FD_WRITE事件通知進行處理,只有3種條件下,FD_WRITE通知纔會發出:

1.使用connect或WSAConnect,一個套接字首次建立連接

2.使用accept或WSAAccept,套接字被接受以後

3.若send,WSASend,sendto或WSASendTo操作失敗,返回了WSAEWOULDBLOCK錯誤,而且緩衝區的空間變得可用時

優點是它可以在系統開銷不大的情況下同時處理許多連接,而select需要建立fd_set結構。

缺點是即使應用程序不需要窗口,它也不得不額外使用一個窗口。同時,用一個單窗口程序來處理成千上萬的套接字中的所有事件,很可能成爲性能瓶頸。

 

========================================================================

 

服務器端得主要流程: 

1.在WM_CREATE消息處理函數中,初始化Windows Socket library,創建監聽套接字,綁定,監聽,並且調用WSAAsyncSelect函數表示我們關心在監聽套接字上發生的FD_ACCEPT事件; 

2.自定義一個消息WM_SOCKET,一旦在我們所關心的套接字(監聽套接字和客戶端套接字)上發生了某個事件,系統就會調用WndProc並且message參數被設置爲WM_SOCKET; 

3.在WM_SOCKET的消息處理函數中,分別對FD_ACCEPT、FD_READ和FD_CLOSE事件進行處理; 

4.在窗口銷燬消息(WM_DESTROY)的處理函數中,我們關閉監聽套接字,清除Windows Socket library 

 

WSAAsyncSelect函數的網絡事件類型可以有以下一種: 

FD_READ 應用程序想要接收有關是否可讀的通知,以便讀入數據 

FD_WRITE 應用程序想要接收有關是否可寫的通知,以便寫入數據 

FD_OOB 應用程序想接收是否有帶外(OOB)數據抵達的通知 

FD_ACCEPT 應用程序想接收與進入連接有關的通知 

FD_CONNECT 應用程序想接收與一次連接或者多點join操作完成的通知 

FD_CLOSE 應用程序想接收與套接字關閉有關的通知 

FD_QOS 應用程序想接收套接字“服務質量”(QoS)發生更改的通知 

FD_GROUP_QOS  應用程序想接收套接字組“服務質量”發生更改的通知(現在沒什麼用處,爲未來套接字組的使用保留) 

FD_ROUTING_INTERFACE_CHANGE 應用程序想接收在指定的方向上,與路由接口發生變化的通知 

FD_ADDRESS_LIST_CHANGE  應用程序想接收針對套接字的協議家族,本地地址列表發生變化的通知

 

  1. #include<windows.h> 
  2. #include<winsock2.h> 
  3. #include<tchar.h> 
  4. #pragma comment(lib, "ws2_32.lib") 
  5.  
  6. #define WM_SOCKET WM_USER+1 
  7. #define PORT 5050 
  8. #define MSGSIZE 1024 
  9.  
  10. LRESULT CALLBACK WndProc(HWNDUINTWPARAMLPARAM); 
  11.  
  12. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCELPSTR lpCmdLine, int nCmdShow) 
  13.     static TCHAR szAppName[] = _T("WSAAsyncSelect Mode"); 
  14.     HWND hwnd; 
  15.     MSG msg; 
  16.     WNDCLASS wndclass; 
  17.     wndclass.style        = CS_HREDRAW | CS_VREDRAW; 
  18.     wndclass.lpfnWndProc  = WndProc; 
  19.     wndclass.cbClsExtra   = 0; 
  20.     wndclass.cbWndExtra   = 0; 
  21.     wndclass.hInstance    = hInstance; 
  22.     wndclass.hIcon        = LoadIcon(NULL, IDI_APPLICATION); 
  23.     wndclass.hCursor      = LoadCursor(NULL, IDC_ARROW); 
  24.     wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); 
  25.     wndclass.lpszMenuName = NULL; 
  26.     wndclass.lpszClassName = szAppName; 
  27.     if(!RegisterClass(&wndclass)) 
  28.     { 
  29.         MessageBox(NULL, _T("This program requires Windows NT!"), szAppName, MB_ICONERROR); 
  30.         return 0; 
  31.     } 
  32.     hwnd = CreateWindow(szAppName, 
  33.                         _T("WSAAsyncSelect Mode"), 
  34.                         WS_OVERLAPPEDWINDOW, 
  35.                         CW_USEDEFAULT, 
  36.                         CW_USEDEFAULT, 
  37.                         CW_USEDEFAULT, 
  38.                         CW_USEDEFAULT, 
  39.                         NULL, 
  40.                         NULL, 
  41.                         hInstance, 
  42.                         NULL); 
  43.     ShowWindow(hwnd, nCmdShow); 
  44.     UpdateWindow(hwnd); 
  45.     while(GetMessage(&msg, NULL, 0, 0)) 
  46.     { 
  47.         TranslateMessage(&msg); 
  48.         DispatchMessage(&msg); 
  49.     } 
  50.     return msg.wParam; 
  51.  
  52. LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 
  53.     WSADATA wsaData; 
  54.     static SOCKET sListen; 
  55.     SOCKET sClient; 
  56.     SOCKADDR_IN local, client; 
  57.     int ret; 
  58.     int iAddrSize = sizeof(SOCKADDR_IN); 
  59.     char szMessage[MSGSIZE]; 
  60.      
  61.     switch(message) 
  62.     { 
  63.     case WM_CREATE: 
  64.          WSAStartup(MAKEWORD(2,2), &wsaData); 
  65.          sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
  66.          memset(local, 0, sizeof(SOCKADDR_IN)); 
  67.          local.sin_family = AF_INET; 
  68.          local.sin_port = htons(PORT); 
  69.          local.sin_addr.s_addr = htonl(INADDR_ANY); 
  70.          bind(sListen, (SOCKADDR*)&local, sizeof(SOCKADDR_IN)); 
  71.          listen(sListen, 5); 
  72.          WSAAsyncSelect(sListen, hwnd, WM_SOCKET, FD_ACCEPT); 
  73.          return 0; 
  74.     case WM_DESTROY: 
  75.          closesocket(sListen); 
  76.          WSACleanup(); 
  77.          PostQuitMessage(0); 
  78.          return 0; 
  79.     case WM_SOCKET: 
  80.          if(GETSELECTERROR(lParam)) 
  81.          { 
  82.              closesocket(wParam); 
  83.              break
  84.          } 
  85.          switch(GETSELECTEVENT(lParam)) 
  86.          { 
  87.          case FD_ACCEPT: 
  88.               sClient = accept(wParam, (SOCKADDR*)&client, &iAddrSize); 
  89.               WSAAsyncSelect(sClient, hwnd, WM_SOCKET, FD_READ | FD_CLOSE); 
  90.               break
  91.          case FD_READ: 
  92.               ret = recv(wParam, szMessage, MSGSIZE, 0); 
  93.               if(ret==0||(ret==SOCKET_ERROR && WSAGetLastError()==WSAECONNRESET)) 
  94.               { 
  95.                   closesocket(wParam); 
  96.               } 
  97.               else 
  98.               { 
  99.                   szMessage[ret]='\0'
  100.                   send(wParam, szMesage, strlen(szMessage), 0); 
  101.               } 
  102.               break
  103.          case FD_CLOSE: 
  104.               closesocket(wParam); 
  105.               break
  106.          } 
  107.          return 0;     
  108.     } 
  109.     return DefWindowProc(hwnd, message, wParam, lParam); 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章