异步选择模型
逻辑
核心:消息队列,操作系统为每个窗口创建一个消息队列,并且维护,我们想要使用消息队列,那就要创建一个窗口。
第一步:将我们的socket,绑定在一个消息上,并且投递给操作系统。
WSAAsyncSelect
第二步:取消息分类处理,
该模型只能用于windows,windows处理用户操作的核心就是消息队列。但是思想是通用的。
窗口
第一步:创建窗口结构体——WNDCLASSEX
第二步:注册窗口结构体——RegisterClassEx
第三步:创建窗口——CreateWindowEx
第四步:显示窗口——ShowWindow
第五步:消息循环——GetMessage
——TranslateMessage
——DispatchMessage
第六步:回调函数
#include<windows.h>//窗口
LRESULT CALLBACK WinBackProc(HWND hwnd, UINT msgID, WPARAM wparaw, LPARAM lparam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nShowCmd)
{
//创建窗口结构体
WNDCLASSEX wc;
wc.cbClsExtra = 0;//窗口结构体额外的一块空间,一般用不到
wc.cbSize = sizeof(WNDCLASSEX);
wc.cbWndExtra = 0;
wc.hbrBackground = NULL;
wc.hCursor = NULL;
wc.hIcon = NULL;
wc.hIconSm = NULL;
wc.hInstance = hInstance;
wc.lpfnWndProc = WinBackProc;
wc.lpszClassName = L"mYwinDows";
wc.lpszMenuName = NULL;
wc.style = CS_HREDRAW | CS_VREDRAW;
//注册窗口结构体
RegisterClassEx(&wc);//窗口类变量地址
//创建窗口
//窗口句柄
HWND hwnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, L"mYwinDows", L"WuHu", WS_OVERLAPPEDWINDOW, 200, 200, 600, 400, NULL, NULL, hInstance, NULL);
if (NULL == hwnd)
{
return 0;
}
//显示窗口
ShowWindow(hwnd, nShowCmd);
//更新窗口
UpdateWindow(hwnd);
//消息循环!-窗口上的任何操作都会产生消息,然后被装进消息队列中。
//只要窗口在,就得不停的在窗口上取消息
//消息结构体——装消息
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))//点击关闭窗口,GetMessage会返回0,循环退出,两个范围填0——接收所有消息
{
//翻译消息
TranslateMessage(&msg);
//分发消息,到具体位置分类处理
DispatchMessageW(&msg);
}
return 0;
}
//回调函数
LRESULT CALLBACK WinBackProc(HWND hwnd, UINT msgID, WPARAM wparaw, LPARAM lparam)
{
switch (msgID)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msgID, wparaw, lparam);
}
服务端
网络库 头文件
打开网络库
校验版本
创建SOCKET
绑定地址与端口
开始监听
异步选择
异步选择
int WSAAsyncSelect(
SOCKET s,
HWND hWnd,
u_int wMsg,
long lEvent
);
例:
if (SOCKET_ERROR == WSAAsyncSelect(socketServer, hwnd, UM_ASYNCSELECTMSG, FD_ACCEPT))
{
int a = WSAGetLastError();
closesocket(socketServer);
WSACleanup();
return 0;
}
功能
绑定事件与socket并且投递出去。
参数1
服务器socket
参数2
窗口句柄,绑定到哪个窗口上。
本质:就是窗口的ID,编号。
参数3
消息编号,自定义消息。
本质:就是一个数。
参数4
消息类型。跟WSASelectEvent一模一样
FD_ACCEPT
有客户端连接,与服务器socket绑定
FD_WRITE
可以给客户端发信,与客户端socket绑定,会在accept后立即主动产生该信号,可以说明,客户端连接成功,即可随时send
FD_READ
有客户端发来消息,与客户端socket绑定,可多个属性并列 用 |
FD_CLOSE
客户端下线了,与客户端socket绑定,包含强制下线,正常下线。
FD_CONNECT
客户端一方,给服务器绑定这个
0
取消事件监视
WSAAsyncSelect(.....FD_ACCEPT | FD_READ);投递多个消息用按位或
WSAAsyncSelect(....0,0);取消消息托管
FD_OOB
带外数据,一般不使用
FD_QOS
套接字服务质量状态发生变化消息通知
WSAIoctl,得到服务质量信息
char strOut[2048] = { 0 };
DWORD nLen = 2048;
WSAIoctl(socketServer, SIO_QOS, 0, 0, strOut, nLen, & nLen, NULL, NULL);
FD_GROUP_QOS
windows保留
重叠I/O模型中
FD_ROUTING_ INTERFACE_CHANGE
想要接收指定目标的路由接口更改通知。
数据到达对方的所经过的线路改变了,由于是动态优化选择
要通过此函数WSAIoctl注册之后,才可以
SIO_ROUTING_ INTERFACE_CHANGE
FD_ADDRESS_ LIST_CHANGE
想要接收套接字地址族的本地地址列表更改通知。
要通过此函数WSAIoctl注册之后,才可以有效
服务器链接了很多客户端,服务器就记录着所有的客户端的地址信 息,就相当于一个列表,发生变化,会得到相关的信号。
SIO_ADDRESS_ LIST_CHANGE
返回值
成功——返回0
失败——返回SOCKET_ERROR
完整代码
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#define UM_ASYNCSELECTMSG WM_USER+1//这个数以上的数系统还没有使用,这样不会引起冲突
//#include<windows.h>//窗口
#include<WinSock2.h>
#include<stdlib.h>
#include<stdio.h>
#pragma comment(lib,"ws2_32.lib")
LRESULT CALLBACK WinBackProc(HWND hwnd, UINT msgID, WPARAM wparaw, LPARAM lparam);
//SOCKET数组——用于释放
#define MAX_SOCK_COUNT 1024
SOCKET g_sockall[MAX_SOCK_COUNT];
//记住socket个数
int g_count = 0;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nShowCmd)
{
//创建窗口结构体
WNDCLASSEX wc;
wc.cbClsExtra = 0;//窗口结构体额外的一块空间,一般用不到
wc.cbSize = sizeof(WNDCLASSEX);
wc.cbWndExtra = 0;
wc.hbrBackground = NULL;
wc.hCursor = NULL;
wc.hIcon = NULL;
wc.hIconSm = NULL;
wc.hInstance = hInstance;
wc.lpfnWndProc = WinBackProc;
wc.lpszClassName = "mYwinDows";
wc.lpszMenuName = NULL;
wc.style = CS_HREDRAW | CS_VREDRAW;
//注册窗口结构体
RegisterClassEx(&wc);//窗口类变量地址
//创建窗口
//窗口句柄
HWND hwnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, "mYwinDows", "WuHu", WS_OVERLAPPEDWINDOW, 200, 200, 600, 400, NULL, NULL, hInstance, NULL);
if (NULL == hwnd)
{
return 0;
}
//显示窗口
ShowWindow(hwnd, nShowCmd);
//更新窗口
UpdateWindow(hwnd);
//**********************************************************************
WORD wdVersion = MAKEWORD(2, 2);
WSADATA wdSockMsg;
int nRes = WSAStartup(wdVersion, &wdSockMsg);
if (nRes != 0)
{
switch (nRes)
{
case WSASYSNOTREADY:
printf("重启下电脑试试,或者检查网络库");
break;
case WSAVERNOTSUPPORTED:
printf("请更新网络库");
break;
case WSAEINPROGRESS:
printf("请重新启动");
break;
case WSAEPROCLIM:
printf("请尝试关掉不必要的软件,以为当前网络运行提供充足资源");
break;
}
return 0;
}
if (HIBYTE(wdSockMsg.wVersion) != 2 || LOBYTE(wdSockMsg.wVersion) != 2)
{
printf("版本错误");
int a = WSAGetLastError();
WSACleanup();
return 0;
}
SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socketServer == INVALID_SOCKET)
{
int a = WSAGetLastError();
WSACleanup();
return 0;
}
struct sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(12345);
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(si)))
{
int a = WSAGetLastError();
closesocket(socketServer);
WSACleanup();
return 0;
}
if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
{
int a = WSAGetLastError();
closesocket(socketServer);
WSACleanup();
return 0;
}
//**********************************************************************
if (SOCKET_ERROR == WSAAsyncSelect(socketServer, hwnd, UM_ASYNCSELECTMSG, FD_ACCEPT))
{
int a = WSAGetLastError();
closesocket(socketServer);
WSACleanup();
return 0;
}
g_sockall[g_count] = socketServer;
g_count++;
//消息循环!-窗口上的任何操作都会产生消息,然后被装进消息队列中。
//只要窗口在,就得不停的在窗口上取消息
//消息结构体——装消息
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))//点击关闭窗口,GetMessage会返回0,循环退出,两个范围填0——接收所有消息
{
//翻译消息
TranslateMessage(&msg);
//分发消息,到具体位置分类处理
DispatchMessageW(&msg);
}
//关闭socket
for (int i = 0; i < g_count; i++)
{
closesocket(g_sockall[i]);
}
WSACleanup();
return 0;
}
int x = 0;//x坐标是左侧竖着的
//回调函数
//一次取一个
LRESULT CALLBACK WinBackProc(HWND hwnd, UINT msgID, WPARAM wparaw, LPARAM lparam)
{
HDC hdc = GetDC(hwnd);
//分类处理
switch (msgID)
{
case UM_ASYNCSELECTMSG:
{
//MessageBox(NULL,L"有信号啦",L"提示",MB_OK);
//获取socket
SOCKET sock = (SOCKET)wparaw;
//获取消息
if (HIWORD(lparam) != 0)
{
if (WSAECONNABORTED == HIWORD(lparam))
{
TextOut(hdc, 0, x, "close", strlen("close"));
x += 15;
//关闭socket上的消息
WSAAsyncSelect(sock, hwnd, 0, 0);//后两个参数置零就是把这个socket上的消息取消了。
//关闭socket
closesocket(sock);
//记录数组中删除该socket
for (int i = 0; i < g_count; i++)
{
if (sock == g_sockall[i])
{
g_sockall[i] = g_sockall[g_count - 1];
g_count--;
break;
}
}
}
break;
}
//具体消息
switch (LOWORD(lparam))
{
case FD_ACCEPT:
{
TextOut(hdc,0, x, "accept", strlen("accept"));
x += 15;
SOCKET socketClient = accept(sock, NULL, NULL);
//如果是一个无效的SOCKET
if (socketClient == INVALID_SOCKET)
{
int a = WSAGetLastError();
break;
}
//将客户端投递给消息队列
if (SOCKET_ERROR == WSAAsyncSelect(socketClient, hwnd, UM_ASYNCSELECTMSG, FD_READ | FD_WRITE | FD_CLOSE))
{
int a = WSAGetLastError();
closesocket(socketClient);
break;
}
//记录
g_sockall[g_count] = socketClient;
g_count++;
}
break;
case FD_READ:
{
//走read,肯定传过来的是客户端的socket
TextOut(hdc, 0, x, "read", strlen("read"));
char str[1024] = { 0 };
if (SOCKET_ERROR == recv(sock, str, 1023, 0))
{
break;
}
TextOut(hdc, 60, x, str, strlen(str));
x += 15;
}
break;
case FD_WRITE:
//当客户端成功连接上服务器后,他会先后产生两个消息,
//accept和write,同事件选择模型
//与选择模型逻辑相同,事件选择模型基于事件,异步选择模型基于消息队列
//队列是有序的,理论起来操作更方便一些。
//send也可以写在accept中,以做提示
TextOut(hdc, 0, x, "write", strlen("write"));
x += 15;
break;
case FD_CLOSE:
TextOut(hdc, 0, x, "close", strlen("close"));
x += 15;
//关闭socket上的消息
WSAAsyncSelect(sock, hwnd, 0, 0);
//后两个参数置零就是把这个socket上的消息取消了。
//关闭socket
closesocket(sock);
//记录数组中删除该socket
for (int i = 0; i < g_count; i++)
{
if (sock == g_sockall[i])
{
g_sockall[i] = g_sockall[g_count - 1];
g_count--;
break;
}
}
break;
}
break;
}
case WM_CREATE://初始化-只执行一次
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
ReleaseDC(hwnd,hdc);
return DefWindowProc(hwnd, msgID, wparaw, lparam);
}
要点
客户端socket
(SOCKET)wParam
产生的错误码
HIWORD(lParam)
具体的消息种类
LOWORD(lParam)
窗口上打印数据
textout
优化
每个窗口维护一定的消息,然后创建多线程,每个线程一个窗口,每个窗口投递一定数量的客户端。
问题
在一次处理过程中,客户端产生多次send,服务器会产生多次接收消息,第一次接收消息会收完所有信息。
总结
事件选择模型和异步选择模型是解决select模型中select()同步阻塞的问题的。
重叠I/O模型和完成端口模型将recv(send)操作变成异步的 ,从而这个网络模型没有阻塞。全都顺利执行下来,且执行效率非常高。
评论区