WSAAsyncSelect模型是Select模型的异步版本,在调用Select()函数的时候会发生阻塞现象,而WSAAsyncSelect()则不会。
不同点
1.WSAAsyncSelect模型是异步的。在应用程序中调用WSAAsyncSelect()函数,通知系统感兴趣的网络事件,该函数立即返回,应用程序继续执行;
2.发生网络事件时,应用程序得到的通知方式不同。Select()函数返回时,说明某个或者某些套接字满足可读可写的条件,应用程序需要使用FD_ISSET宏,判断套接字是否存在可读可写集合中。而对于WSAAsyncSelect模型来说,当网络事件发生时,系统向应用程序发送消息。
3.WSAAsyncSelect模型应用在基于消息的Windos环境下,使用该模型时必须创建窗口。而Select模型广泛应用在Unix系统和Windows系统,使用该模型不需要创建窗口。
4.应用程序调用WSAAsyncSelect()函数后,自动将套接字设置为非阻塞模式。而应用程序中调用select()函数后,并不能改变套接字的工作方式。
函数原型
int WSAAsyncSelect(
__in SOCKET s,
__in HWND hWnd,
__in unsigned int wMsg,
__in long lEvent
);
● s 参数指定的是我们感兴趣的那个套接字。
● hWnd 参数指定一个窗口句柄,它对应于网络事件发生之后,想要收到通知消息的那个窗口。
● wMsg 参数指定在发生网络事件时,打算接收的消息。该消息会投递到由hWnd窗口句柄指定的那个窗口。(通常,应用程序需要将这个消息设为比Windows的WM_USER大的一个值,避免网络窗口消息与系统预定义的标准窗口消息发生混淆与冲突)
● lEvent 参数指定一个位掩码,对应于一系列网络事件的组合,大多数应用程序通常感兴趣的网络事件类型包括: FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT、FD_CLOSE。当然,到底使用FD_ACCEPT,还是使用FD_CONNECT类型,要取决于应用程序的身份是客户端,还是服务器。如应用程序同时对多个网络事件有兴趣,只需对各种类型执行一次简单的按位OR(或)运算,然后将它们分配给lEvent就可以了,例如:
WSAAsyncSeltct(s, hwnd, WM_SOCKET, FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE);
解释说明:我们的应用程序以后便可在套接字s上,接收到有关连接、发送、接收以及套接字关闭这一系列网络事件的通知。
创建窗口
异步选择模型基于Windows的消息机制,必须创建窗口。在Windows编程里,有个WinMain()入口函数,和C++中的main()类,其原型为:
int WINAPI WinMain(
In HINSTANCE hInstance,
In HINSTANCE hPrevInstance,
In LPSTR lpCmdLine,
In int nCmdShow
)
参数:hInstance 是第一次运行的实例句柄,
hPrevInstance是前一个实例句柄,例如:把同一个界面程序(a.exe)运行两次,进程里面就会有2个a.exe,第一次假设为01,就会传给hInstance ,第二次为02,就会传给hPrevInstance
lpCmdLine 是指命令行
nCmdShow 窗口的显示方式
利用WNDClASSEX 创建窗口
主要流程为:
示例:
char szClassName[] = "MyWNDClass";
char szTitleName[] = "MyWindow";
WNDCLASSEX ws;
ws.cbSize = sizeof(WNDCLASSEX);
ws.style = CS_HREDRAW | CS_VREDRAW; //Heigth and width 宽度和高度发生变化重绘
ws.cbClsExtra = 0;
ws.cbWndExtra = 0;
ws.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
ws.hInstance = hinstance;
ws.lpfnWndProc = WndProc;
ws.hCursor = LoadCursor(NULL,IDC_ARROW);
ws.hIcon = LoadIcon(hinstance, MAKEINTRESOURCE(IDI_APPLICATION));
ws.hIconSm=LoadIcon(ws.hInstance,MAKEINTRESOURCE(IDI_WINLOGO));
ws.lpszMenuName = NULL;
ws.lpszClassName = szClassName;
RegisterClassEx(&ws);
HWND hWnd;
hWnd=CreateWindowEx(NULL, szClassName, szTitleName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinstance, NULL);
ShowWindow(hWnd, nShowCmd);
UpdateWindow(hWnd);
//消息循环处理
MSG msg;
while (GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
注意事项:注册窗口类之前必须填充WNDCLASSEX的结构体,也就是要为所有的成员赋值,就算你不需要,也要赋值为零或者Null,没有初始化就无法正确的分配内存,导致注册失败。解决方法:
WNDCLASS ws={};
回调函数
LRESULT CALLBACK WndPro(
In HWND hwnd,
In UINT uMsg,
In WPARAM wParam,
In LPARAM lParam
)
参数:hwnd 接收消息的窗口句柄
message 网络事件发生时要接收的消息
wParam 标识网络事件发生的套接字
lParam 低字节指明了发生的网络事件,高字节含有一个错误代码
示例:
#include <windows.h>
#include <iostream>
#include <tchar.h>
#pragma comment(lib,"ws2_32.lib")
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define WM_SOCKET WM_USER+1
HINSTANCE hInst;
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
sockaddr_in recvSa;
static SOCKET sListen;
int len = sizeof(sockaddr_in);
char recvBuf[1024];
int ret = 0;
switch (message)
{
case WM_CREATE:
{
WSADATA wsData;
sockaddr_in sa;
WSAStartup(MAKEWORD(2, 2), &wsData);
sListen = socket(AF_INET, SOCK_STREAM, 0);
sa.sin_family = AF_INET;
sa.sin_port = htons(8888);
sa.sin_addr.S_un.S_addr = INADDR_ANY;
bind(sListen, (sockaddr*)&sa, len);
listen(sListen, 5);
WSAAsyncSelect(sListen, hwnd, WM_SOCKET, FD_ACCEPT);
}
break;
case WM_PAINT:
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_SOCKET:
{
if (WSAGETSELECTERROR(lParam))
{
closesocket(wParam);
break;
}
switch (WSAGETSELECTEVENT(lParam))
{
case FD_ACCEPT:
{
SOCKET sClient = accept(wParam, (sockaddr*)&recvSa, &len);
WSAAsyncSelect(sClient, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);
}
break;
case FD_READ:
ZeroMemory(recvBuf, 1024);
ret = recv(wParam, recvBuf, 1024, 0);
if (ret>0)
{
WCHAR sBuf[256] = { 0 };
MultiByteToWideChar(CP_ACP, 0, recvBuf, strlen(recvBuf) + 1, sBuf, sizeof(sBuf) / sizeof(sBuf[0]));
//OutputDebugString(sBuf);
//MessageBox(nullptr, sBuf,nullptr,NULL);
}
else if ( ret == 0)
{
closesocket(wParam);
}
//MessageBox(hwnd, recvBuf, "提示", MB_OK);
break;
case FD_CLOSE:
//MessageBox(hwnd, "break", "提示", MB_OK);
closesocket(wParam);
break;
default:
break;
}
}
default:
return DefWindowProc(hwnd, message, wParam, lParam);
break;
}
return 0;
}
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
WCHAR szClassName[] = _T("MyWNDClass");
WCHAR szTitleName[] = _T("MyWindow");
WNDCLASSEX ws;
ws.cbSize = sizeof(WNDCLASSEX);
ws.style = CS_HREDRAW | CS_VREDRAW; //Heigth and width 宽度和高度发生变化重绘
ws.cbClsExtra = 0;
ws.cbWndExtra = 0;
ws.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
ws.hInstance = hinstance;
ws.lpfnWndProc = WndProc;
ws.hCursor = LoadCursor(NULL, IDC_ARROW);
/*
* IDI_APPLICATION 缺省引用程序图标
*IDI_ASTERISK 星号
* IDI_WINLOGO 语言图标
* IDI_ERROR 手形图标
*/
ws.hIcon = LoadIcon(hinstance, MAKEINTRESOURCE(IDI_APPLICATION));
ws.hIconSm = LoadIcon(ws.hInstance, MAKEINTRESOURCE(IDI_WINLOGO));
ws.lpszMenuName = NULL;
ws.lpszClassName = szClassName;
//Register 失败返回NULL
RegisterClassEx(&ws);
//CreateWinow and show
//重叠窗口
HWND hWnd;
hWnd = CreateWindowEx(NULL, szClassName, szTitleName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinstance, NULL);
hInst = hinstance;
ShowWindow(hWnd, nShowCmd);
//ReDraw
UpdateWindow(hWnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}