一、什么是異步選擇模型
異步選擇(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;
}
