一、什么是異步選擇模型
異步選擇(WSAAsyncSelect)模型是一個異步 I/O 模型。利用這個模型,應用程序可在一個套接字上,接收以 Windows 消息機制為基礎的網絡事件通知,開發者將socket注冊到消息機制,當socket有事件(新的連接,新的數據,連接斷開,可以寫入)來時候。具體的做法是在建好一個套接字后,調用WSAAsyncSelect函數。
該模型的核心即是WSAAsyncSelect函數,該函數是非阻塞的。
二、與select模型比較
相同點:
他們都可以對Windows套接字應用程序所使用的多個套接字進行有效的管理。
不同點:
1.WSAAsyncSelect模型是異步的。在應用程序中調用WSAAsyncSelect()函數,通知系統感興趣的網絡事件,該函數立即返回,應用程序繼續執行;
2.發生網絡事件時,應用程序得到的通知方式不同。Select()函數返回時,說明某個或者某些套接字滿足可讀可寫的條件,應用程序需要使用FD_ISSET宏,判斷套接字是否存在可讀可寫集合中。而對於WSAAsyncSelect模型來說,當網絡事件發生時,系統向應用程序發送消息。
3.WSAAsyncSelect模型應用在基於消息的Windos環境下,使用該模型時必須創建窗口。而Select模型廣泛應用在Unix系統和Windows系統,使用該模型不需要創建窗口。
4.應用程序調用WSAAsyncSelect()函數后,自動將套接字設置為非阻塞模式。而應用程序中調用select()函數后,並不能改變套接字的工作方式
三、異步選擇模型API函數
WSAAsyncSelect函數定義如下:
int WSAAsyncSelect( __in SOCKET s, //指定的是我們感興趣的那個套接字。 __in HWND hWnd, //指定一個窗口句柄,它對應於網絡事件發生之后,想要收到通知消息的那個窗口。 __in unsigned int wMsg, //指定在發生網絡事件時,打算接收的消息。該消息會投遞到由hWnd窗口句柄指定的那個窗口。 __in long lEvent //指定一個位掩碼,對應於一系列網絡事件的組合 );
WSAAsyncSelect模型是Select模型的異步版本,在調用select()函數時,會發生阻塞現象。可以通過select()函數timeout參數,設置函數調用的阻塞時間。在設定的時間內,線程保持等待,直到其中一個或多個套接字滿足可讀可寫的條件時,該函數返回。
四、異步選擇模型缺陷
不適合高並發網絡結構,因為是基於窗口消息機制,消息太多就處理速度較慢。
五、代碼示例
創建windwos窗口項目:
//1.添加頭文件 #include<WinSock2.h> #pragma comment(lib,"Ws2_32.lib") #define WM_MYSOCKMSG WM_USER+1 bool HandleData(SOCKET sockClient);//處理收發數據 //2.初始化WSAstartup WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 1, 1 ); err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { /* Tell the user that we could not find a usable */ /* WinSock DLL. */ return 0; } /* Confirm that the WinSock DLL supports 1.1.*/ /* Note that if the DLL supports versions greater */ /* than 1.1 in addition to 1.1, it will still return */ /* 1.1 in wVersion since that is the version we */ /* requested. */ if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { /* Tell the user that we could not find a usable */ /* WinSock DLL. */ WSACleanup( ); return 0; } //3.再處理消息處添加代碼 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: { //1) 創建socket SOCKET sockServer = socket( AF_INET, SOCK_STREAM, //流式 IPPROTO_TCP);//tcp協議 // 2) 綁定端口 sockaddr_in siServer; siServer.sin_family = AF_INET; siServer.sin_port = htons(9527); siServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); int nRet = bind(sockServer, (sockaddr*)&siServer, sizeof(siServer)); if (nRet == SOCKET_ERROR) { printf("綁定失敗 \r\n"); return 0; } // 3) 監聽 nRet = listen(sockServer, SOMAXCONN); if (nRet == SOCKET_ERROR) { printf("監聽失敗 \r\n"); return 0; } //4)接受連接 //這個socket只關系新連接和關閉時間 WSAAsyncSelect(sockServer, hWnd, WM_MYSOCKMSG, FD_ACCEPT | FD_CLOSE); break; } case WM_MYSOCKMSG: { SOCKET sock = (SOCKET)wParam; WORD wErrCode = WSAGETSELECTERROR(lParam); WORD wSelectEvent = WSAGETSELECTEVENT(lParam); switch (wSelectEvent) { case FD_ACCEPT: { sockaddr_in siClient; int nSize = sizeof(siClient); SOCKET sockClient = accept(sock, (sockaddr*)&siClient, &nSize); //為新的連接注冊對應的網絡事件 WSAAsyncSelect(sockClient, hWnd, WM_MYSOCKMSG, FD_READ | FD_CLOSE); break; } case FD_READ://數據來了 { HandleData(sock); //處理數據 break; } case FD_CLOSE://連接斷開 { //將sock從網絡事件中移除 closesocket(sock); break; } } break; } case WM_COMMAND: { int wmId = LOWORD(wParam); // 分析菜單選擇: switch (wmId) { case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } } break; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); // TODO: 在此處添加使用 hdc 的任何繪圖代碼... EndPaint(hWnd, &ps); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } bool HandleData(SOCKET sockClient) { // 5) 收發數據 char aryBuff[MAXWORD] = { 0 }; int nRet = recv(sockClient, aryBuff, sizeof(aryBuff), 0); if (nRet == 0 || nRet == SOCKET_ERROR) { printf("接受數據失敗 \r\n"); return false; } printf("收到數據: %s \r\n", aryBuff); char szBuff[] = { "recv OK " }; nRet = send(sockClient, szBuff, sizeof(szBuff), 0); if (nRet == SOCKET_ERROR) { printf("數據發送失敗 \r\n"); return false; } return true; }