前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【网络编程】异步选择模型

【网络编程】异步选择模型

作者头像
半生瓜的blog
发布2023-05-12 21:39:12
3230
发布2023-05-12 21:39:12
举报
文章被收录于专栏:半生瓜のblog半生瓜のblog

异步选择模型

逻辑

核心:消息队列,操作系统为每个窗口创建一个消息队列,并且维护,我们想要使用消息队列,那就要创建一个窗口。

第一步:将我们的socket,绑定在一个消息上,并且投递给操作系统。

WSAAsyncSelect

第二步:取消息分类处理,

该模型只能用于windows,windows处理用户操作的核心就是消息队列。但是思想是通用的。

窗口

第一步:创建窗口结构体——WNDCLASSEX

第二步:注册窗口结构体——RegisterClassEx

第三步:创建窗口——CreateWindowEx

第四步:显示窗口——ShowWindow

第五步:消息循环——GetMessage

​ ——TranslateMessage

​ ——DispatchMessage

第六步:回调函数

代码语言:javascript
复制
#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);
}

服务端

代码语言:javascript
复制
网络库 头文件 
打开网络库
校验版本
创建SOCKET
绑定地址与端口
开始监听

异步选择

异步选择

代码语言:javascript
复制
int WSAAsyncSelect(
  SOCKET s,
  HWND   hWnd,
  u_int  wMsg,
  long   lEvent
);

代码语言:javascript
复制
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一模一样

代码语言:javascript
复制
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保留 
代码语言:javascript
复制
重叠I/O模型中
FD_ROUTING_ INTERFACE_CHANGE
    想要接收指定目标的路由接口更改通知。
    数据到达对方的所经过的线路改变了,由于是动态优化选择
    要通过此函数WSAIoctl注册之后,才可以
    SIO_ROUTING_ INTERFACE_CHANGE
FD_ADDRESS_ LIST_CHANGE
    想要接收套接字地址族的本地地址列表更改通知。
    要通过此函数WSAIoctl注册之后,才可以有效
    服务器链接了很多客户端,服务器就记录着所有的客户端的地址信	 息,就相当于一个列表,发生变化,会得到相关的信号。
    SIO_ADDRESS_ LIST_CHANGE

返回值

代码语言:javascript
复制
成功——返回0
失败——返回SOCKET_ERROR

完整代码

代码语言:javascript
复制
#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);
}

要点

代码语言:javascript
复制
客户端socket
    (SOCKET)wParam
产生的错误码
    HIWORD(lParam)
具体的消息种类
    LOWORD(lParam)
窗口上打印数据
    textout

优化

每个窗口维护一定的消息,然后创建多线程,每个线程一个窗口,每个窗口投递一定数量的客户端。

问题

在一次处理过程中,客户端产生多次send,服务器会产生多次接收消息,第一次接收消息会收完所有信息。

总结

事件选择模型和异步选择模型是解决select模型中select()同步阻塞的问题的。

重叠I/O模型和完成端口模型将recv(send)操作变成异步的 ,从而这个网络模型没有阻塞。全都顺利执行下来,且执行效率非常高。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-09-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 异步选择模型
    • 逻辑
      • 窗口
        • 服务端
          • 异步选择
          • 功能
          • 参数1
          • 参数2
          • 参数3
          • 参数4
          • 返回值
          • 完整代码
          • 要点
          • 优化
          • 问题
          • 总结
      相关产品与服务
      消息队列
      腾讯云消息队列 TDMQ 是分布式架构中的重要组件,提供异步通信的基础能力,通过应用解耦降低系统复杂度,提升系统可用性和可扩展性。TDMQ 产品系列提供丰富的产品形态,包含 CKafka、RocketMQ、RabbitMQ、Pulsar、CMQ 五大产品,覆盖在线和离线场景,满足金融、互联网、教育、物流、能源等不同行业和场景的需求。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档