利用這個異步I/O模型,應用程序可在一個套接字上接收以Windows消息爲基礎的網絡事件通知。WSAAsyncSelect和WSAEventSelect提供讀寫數據能力的異步通知,但它們不提供異步數據傳輸,重疊及完成端口提供異步數據傳輸。
消息通知
要想使用WSAAsyncSelect模型,在應用程序中,首先必須用CreateWindow函數創建一個窗口,再爲該窗口提供一個窗口過程支持函數,亦可使用一個對話框,爲其提供一個對話框過程來代替窗口過程,這是因爲對話框本質也是窗口。
- int WSAAsyncSelect(
- SOCKET s,//我們感興趣的套接字
- HWND hwnd,//窗口句柄,標識的是網絡事件發生之後,想要收到通知消息的那個窗口或對話框
- unsigned int wMsg,//在發生網絡事件時,打算接收的消息,該消息將被投遞到由hwnd窗口句柄所標識的那個窗口
- long lEvent//網絡事件組合
- );
大多數應用程序感興趣的網絡事件類型包括: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程序基本流程:
- #include<windows.h>
- #include<winsock2.h>
- #pragma comment(lib, "ws2_32.lib")
- #define WM_SOCKET WM_USER+1
- int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
- LPSTR lpCmdLine, int nCmdShow)
- {
- WSADATA wsaData;
- SOCKET Listen;
- SOCKADDR_IN addr;
- HWND window;
- //創建窗口
- window = CreateWindow();
- WSAStartup(MAKEWORD(2,2), &wsaData);
- Listen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- memset(addr, 0, sizeof(SOCKADDR_IN));
- addr.sin_family = AF_INET;
- addr.sin_port = htons(5050);
- addr.sin_addr.s_addr = hotnl(INADDR_ANY);
- bind(Listen, (SOCKADDR*)&addr, sizeof(SOCKADDR_IN));
- //使用定義的WM_SOCKET 在新套接字上設置窗口消息通知
- WSAAsyncSelect(Listen, window, WM_SOCKET, FD_ACCEPT|FD_CLOSE);
- listen(Listen, 5);
- //轉換並分配消息
- while(1)
- {
- ...
- }
- }
- BOOL CALLBACK ServerProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
- {
- SOCKET Accept;
- switch(wMsg)
- {
- case WM_PAINT:
- break;
- case WM_SOCKET:
- //使用WSAGETSELECTERROR宏來判斷套接字上是否發生了錯誤
- if(WSAGETSELECTERROR(lParam))
- {
- //顯示錯誤,關閉套接字
- closesocket((SOCKET)wParam);
- break;
- }
- //確定在套接字上發生什麼事件
- switch(WSAGETSELECTEVENT(lParam))
- {
- case FD_ACCEPT:
- //接受一個傳入的連接
- Accept = accept(wParam, NULL, NULL);
- //讓接收套接字爲讀寫及關閉通知做好準備
- WSAAsyncSelect(Accept, hDlg, WM_SOCKET, FD_READ|FD_WRITE|FD_CLOSE);
- break;
- case FD_READ:
- //從wParam中的套接字中檢索數據
- break;
- case FD_WRITE:
- //wParam中的套接字已準備好發送數據
- break;
- case FD_CLOSE:
- closesocket((SOCKET)wParam);
- break;
- }
- break;
- }
- 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 應用程序想接收針對套接字的協議家族,本地地址列表發生變化的通知
- #include<windows.h>
- #include<winsock2.h>
- #include<tchar.h>
- #pragma comment(lib, "ws2_32.lib")
- #define WM_SOCKET WM_USER+1
- #define PORT 5050
- #define MSGSIZE 1024
- LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
- int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR lpCmdLine, int nCmdShow)
- {
- static TCHAR szAppName[] = _T("WSAAsyncSelect Mode");
- HWND hwnd;
- MSG msg;
- WNDCLASS wndclass;
- wndclass.style = CS_HREDRAW | CS_VREDRAW;
- wndclass.lpfnWndProc = WndProc;
- wndclass.cbClsExtra = 0;
- wndclass.cbWndExtra = 0;
- wndclass.hInstance = hInstance;
- wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
- wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
- wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
- wndclass.lpszMenuName = NULL;
- wndclass.lpszClassName = szAppName;
- if(!RegisterClass(&wndclass))
- {
- MessageBox(NULL, _T("This program requires Windows NT!"), szAppName, MB_ICONERROR);
- return 0;
- }
- hwnd = CreateWindow(szAppName,
- _T("WSAAsyncSelect Mode"),
- WS_OVERLAPPEDWINDOW,
- CW_USEDEFAULT,
- CW_USEDEFAULT,
- CW_USEDEFAULT,
- CW_USEDEFAULT,
- NULL,
- NULL,
- hInstance,
- NULL);
- ShowWindow(hwnd, nCmdShow);
- UpdateWindow(hwnd);
- while(GetMessage(&msg, NULL, 0, 0))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- return msg.wParam;
- }
- LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
- {
- WSADATA wsaData;
- static SOCKET sListen;
- SOCKET sClient;
- SOCKADDR_IN local, client;
- int ret;
- int iAddrSize = sizeof(SOCKADDR_IN);
- char szMessage[MSGSIZE];
- switch(message)
- {
- case WM_CREATE:
- WSAStartup(MAKEWORD(2,2), &wsaData);
- sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- memset(local, 0, sizeof(SOCKADDR_IN));
- local.sin_family = AF_INET;
- local.sin_port = htons(PORT);
- local.sin_addr.s_addr = htonl(INADDR_ANY);
- bind(sListen, (SOCKADDR*)&local, sizeof(SOCKADDR_IN));
- listen(sListen, 5);
- WSAAsyncSelect(sListen, hwnd, WM_SOCKET, FD_ACCEPT);
- return 0;
- case WM_DESTROY:
- closesocket(sListen);
- WSACleanup();
- PostQuitMessage(0);
- return 0;
- case WM_SOCKET:
- if(GETSELECTERROR(lParam))
- {
- closesocket(wParam);
- break;
- }
- switch(GETSELECTEVENT(lParam))
- {
- case FD_ACCEPT:
- sClient = accept(wParam, (SOCKADDR*)&client, &iAddrSize);
- WSAAsyncSelect(sClient, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);
- break;
- case FD_READ:
- ret = recv(wParam, szMessage, MSGSIZE, 0);
- if(ret==0||(ret==SOCKET_ERROR && WSAGetLastError()==WSAECONNRESET))
- {
- closesocket(wParam);
- }
- else
- {
- szMessage[ret]='\0';
- send(wParam, szMesage, strlen(szMessage), 0);
- }
- break;
- case FD_CLOSE:
- closesocket(wParam);
- break;
- }
- return 0;
- }
- return DefWindowProc(hwnd, message, wParam, lParam);
- }