这是我参与11月更文挑战的第二十五天,活动详情查看:2021最后一次更文挑战
异步选择模型
目的和要求
1. 掌握创建基本窗口的代码,以及回调函数的概念;
2. 掌握异步选择模型的通讯过程;
3. 掌握异步选择模型的代码实现。
内容和步骤
服务器端:
实现基本窗口功能:
1、创建窗口结构体:WNDCLASSEX(这一步不能少设置属性,否则会在第三步失败)
WNDCLASSEX wndc;
wndc.cbClsExtra = 0;//窗口类额外数据,不用就写0
wndc.cbSize = sizeof(WNDCLASSEX);//窗口类大小
wndc.cbWndExtra = 0;//窗口额外数据,不用就写0
wndc.hbrBackground = NULL;//用默认白色
wndc.hCursor = NULL;//默认光标
wndc.hIcon = NULL;//默认窗口图标
wndc.hIconSm = NULL;//默认任务栏图标
wndc.hInstance = hInstance;//少这个就不能创建成功
wndc.lpfnWndProc = callBackProc;//回调函数名称,要和定义的回调函数名字一样,由系统调用
wndc.lpszClassName = "emptywnd";//窗口类名
wndc.lpszMenuName = NULL;//窗口菜单名称
wndc.style = CS_HREDRAW | CS_VREDRAW;//https://docs.microsoft.com/zh-cn/windows/win32/winmsg/window-class-styles
复制代码
2、注册窗口结构体:RegisterClassEx
int regid = RegisterClassEx(&wndc);
if (regid == 0)//如果注册失败
{
int RegisterClassExerr = GetLastError();
}
复制代码
3、创建窗口:CreateWindowEx
HWND hWnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, "emptywnd", "窗口标题", WS_OVERLAPPEDWINDOW, 100, 100, 640, 480, NULL, NULL, hInstance, NULL);
if (hWnd == NULL)//如果创建失败
{
int CreateWindowExerr = GetLastError();
//MessageBox(0,"注册窗口失败", "提示", MB_OK);
return 0;
}
复制代码
4、显示窗口:ShowWindow
howWindow(hWnd, SW_NORMAL);
复制代码
5、网络通信功能, SOCKET初始化操作
WORD wdVersion = MAKEWORD(2, 2);
int a = *((char*)&wdVersion);
int b = *((char*)&wdVersion + 1);
复制代码
5.1.打开网络库
if (0 != nRes)
{
switch (nRes)
{
case WSASYSNOTREADY:
printf("解决方案:重启。。。\n");
break;
case WSAVERNOTSUPPORTED:
break;
case WSAEINPROGRESS:
break;
case WSAEPROCLIM:
break;
case WSAEFAULT:
break;
}
return 0;
}
复制代码
5.2.校验版本
if (2 != HIBYTE(wdScokMsg.wVersion) || 2 != LOBYTE(wdScokMsg.wVersion))
{
printf("版本有问题!\n");
WSACleanup();
return 0;
}
复制代码
5.3.创建SOCKET
SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
复制代码
5.4.绑定地址与端口
if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(si)))
{
int err = WSAGetLastError();//取错误码
printf("服务器bind失败错误码为:%d\n", err);
closesocket(socketServer);//释放
WSACleanup();//清理网络库
return 0;
}
printf("服务器端bind成功!\n");5.5.开始监听
if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
{
int err = WSAGetLastError();//取错误码
printf("服务器监听失败错误码为:%d\n", err);
closesocket(socketServer);//释放
WSACleanup();//清理网络库
return 0;
}
printf("服务器端监听成功!\n");
复制代码
5.6.绑定消息和服务器SOCKET,并投递给操作系统
if (WSAAsyncSelect(socketServer, hWnd, UM_ASYNCSELECTMSG, FD_ACCEPT) == SOCKET_ERROR)//失败处理
{
int WSAAsyncSelecterr = WSAGetLastError();
closesocket(socketServer);
WSACleanup();
return 0;
}
复制代码
6、消息循环:GetMessage、TranslateMessage、DispatchMessage
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);//将消息转换为可识别的代号
DispatchMessage(&msg);//分发消息,让回调函数来处理
}
复制代码
7、创建回调函数
LRESULT CALLBACK callBackProc(HWND hWnd, UINT msgID, WPARAM wparam, LPARAM lparam)
7.1从wparam中获取socket句柄
SOCKET sock = (SOCKET)wparam;
7.2获取操作码,使用分支语句进行判断分别进行处理
switch (LOWORD(lparam))
{
case FD_ACCEPT:
{
//参数1:当前窗口的上下文句柄
//参数2,3:要显示的位置坐标,左上角是0,0
//参数4:要显示的字符串
//参数5:参数4的长度,这里不用加后面的/0,因此可以sizeof要减一,或者直接用strlen
TextOut(hdc, 10, y, "accept执行", sizeof("accept执行") - 1);
y += 15;
SOCKET socketClient = accept(sock, NULL, NULL);//获取客户端SOCKET句柄
if (socketClient == INVALID_SOCKET)//出错拿错误码
{
int accepterr = WSAGetLastError();
break;
}
//没错则将事件和客户端SOCKET句柄装消息队列并投递到操作系统
if (WSAAsyncSelect(socketClient, hWnd, UM_ASYNCSELECTMSG, FD_READ | FD_WRITE | FD_CLOSE) == SOCKET_ERROR)
{
int WSAAsyncSelecterr = WSAGetLastError();
closesocket(socketClient);
}
//成功则装进SOCKET数组,便于最后释放
garr_sockAll[gi_sockCout] = socketClient;
gi_sockCout++;
break;
}
case FD_READ:
{
TextOut(hdc, 10, y, "read执行", sizeof("read执行") - 1);
y += 15;
char str[1000] = { 0 };
//接收消息
if (recv(sock, str, 999, 0) == SOCKET_ERROR)
{
int recverr = WSAGetLastError();
break;
}
//显示消息
TextOut(hdc, 10, y, str, strlen(str));
y += 15;
break;
}
case FD_WRITE:
{
TextOut(hdc, 10, y, "wirte执行", sizeof("wirte执行") - 1);
y += 15;
//窗口还没地方写消息,写个死的先
if (send(sock, "异步选择模型连接成功~", sizeof("异步选择模型连接成功~"), 0) == SOCKET_ERROR)
{
int FD_WRITEsenderr = WSAGetLastError();
}
break;
}
case FD_CLOSE:
{
TextOut(hdc, 10, y, "close执行", sizeof("close执行") - 1);
y += 15;
//通过将后面两个参数设置为0,即可关闭该socket上的消息
WSAAsyncSelect(sock, hWnd, 0, 0);
//关闭socket
closesocket(sock);
//记录数组中删除该socket
for (int i = 0; i < gi_sockCout; i++)
{
if (garr_sockAll[i] == sock)
{
garr_sockAll[i] = garr_sockAll[gi_sockCout - 1];//没有顺序要求,直接用最后一个元素补位
gi_sockCout--;
break;
}
}
WSACleanup();//清理网络库
//最后一个分支不用break
}
}
复制代码
8、关闭SOCKET句柄
ReleaseDC(hWnd, hdc);//释放hdc
//对未处理的消息进行默认处理
return DefWindowProc(hWnd, msgID, wparam, lparam);
复制代码
运行结果
如何理解异步、同步
同步:提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事
异步: 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕****