IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> UDP.4.异步选择模型 -> 正文阅读

[网络协议]UDP.4.异步选择模型


https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-socket
基于UDP的网络编程还有5种模型:
SELECT模型
事件选择模型
异步选择模型
重叠IO模型
完成端口模型
这节讲基于UDP的异步选择模型。

异步选择模型简介

操作系统为每个窗口创建一个消息队列并且维护,因此异步选择模型是基于窗口的异步模型(只能Windows上玩,别的系统可能不是基于消息机制的)。该模型的思路是:
1.将SOCKET句柄绑定在消息上,并投递给系统,(事件选择模型是绑定在事件上投递给系统)由系统来维护消息队列;
2.查询消息队列,取出队头消息进行处理。与TCP不一样的是,UDP只用处理2种消息。

裸窗口的创建

创建窗口要使用Win32项目,这里如果不适用空项目窗口创建后会带有菜单什么的,很多乱七八糟用不上的代码,这里根据步骤自己创建一个空白窗口。
先加载头文件和主函数:

//#include <windows.h>//窗口头文件与WinSock2.h有重复定义,使用一个即可

#include <WinSock2.h>
#include <stdio.h>


#pragma comment(lib, "Ws2_32.lib")
//hInstance is something called a "handle to an instance" or "handle to a module." The operating system uses this value to identify the executable (EXE) when it is loaded in memory. The instance handle is needed for certain Windows functions—for example, to load icons or bitmaps.可理解为应用程序的ID,是一个整型
//hPrevInstance has no meaning. It was used in 16-bit Windows, but is now always zero.已废弃
//pCmdLine contains the command-line arguments as a Unicode string.命令行传入的参数
//nCmdShow is a flag that says whether the main application window will be minimized, maximized, or shown normally.窗口显示方式:最大化、最小化、正常等
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nShowCmd)
{
	return 0;
}

第一步:创建窗口结构体:WNDCLASSEX(这一步不能少设置属性,否则会在第三步失败)

//w1.创建窗口结构体https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexa
	WNDCLASSEX wndc;//WNDCLASSEX比WNDCLASS多两个属性
	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

C95标准化了两种表示大型字符集的方法:宽字符(wide character,Unicode字符集,该字符集内每个字符使用相同的位长,2个字符)以及多字节字符(multibyte character,每个字符可以是一到多个字节不等,例如中文占2字符,英文占1字符)。
这里如果是VS2019这里在窗口类名这里可能会报字符集转换出错,有三种解决方案将宽字符集转多字符集:
1.在项目属性里面吧Unicode字符集换成多字符集;
2.将"emptywnd"前面加L,变成L"emptywnd";
3.加头文件#include <tchar.h>,然用:_T(“emptywnd”)或者TEXT(“emptywnd”)
第二步:注册窗口结构体:RegisterClassEx

	//w2.注册窗口结构体
	int regid = RegisterClassEx(&wndc);//上面是WNDCLASSEX,这里就不能用RegisterClass
	if (regid ==0)//如果注册失败
	{
		int RegisterClassExerr = GetLastError();

	}

第三步:创建窗口:CreateWindowEx

//w3.创建窗口https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexa
	//参数1是EX窗口的显示属性,例如总在最前等,可用【|】设置多个https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
	//参数2要和上面创建窗口结构处的窗口类名一模一样
	//参数3是窗口显示的标题名称,就是左上角的名称
	//参数4是窗口的风格,例如是否有最大化最小化按钮,是否有滚动条等,可以设置多个https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles
	//参数56是窗口位置坐标,以像素为单位
	//参数78是窗口尺寸,以像素为单位
	//参数9是指定父窗口句柄,不指定就用NULL
	//参数10是指定窗口的菜单句柄,要和上面的一样,没有就用NULL
	//参数11是当前应用的句柄,用winmain的参数hInstance就可以
	//参数12是给回调函数传的参数,这里没有就用NULL
	//返回:成功,窗口的句柄;失败,NULL
	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;
	}

第四步:显示窗口:ShowWindow

//w4.显示窗口
	//参数1:要显示的窗口句柄
	//参数2:显示窗口的形式,例如:最大化、最小化
	ShowWindow(hWnd,SW_NORMAL);

	//optional.更新窗口,showwindow后没变化可以不用update
	UpdateWindow(hWnd);

第五步:消息循环:GetMessage、TranslateMessage、DispatchMessage

//w5.消息循环,窗口创建成功后,窗口的消息队列也将同步创建,此时对窗口的所有操作都会产生相应的消息。
	//GetMessage()除非捕获退出消息会返回0,其他消息都不为0
	//参数1:消息结构体地址,传址调用
	//参数2:获取指定窗口的消息,如果填NULL就是获取应用程序(多个窗口)的消息
	//参数34:处理消息的范围,都填0表示不限制消息ID的范围
	MSG msg;
	while (GetMessage(&msg,NULL,0,0))
	{
		TranslateMessage(&msg);//将消息转换为可识别的代号
		DispatchMessage(&msg);//分发消息,让回调函数来处理
		
	}

第六步:创建回调函数

//w6.定义回调函数
//参数1:窗口句柄
//参数2:消息代号
//参数3:包含SOCKET句柄的参数
//参数4:SOCKET要进行的事件操作和错误码
LRESULT CALLBACK callBackProc(HWND hWnd, UINT msgID, WPARAM wparam, LPARAM lparam)
{
	switch (msgID)
	{
	case WM_DESTROY:
		{
			PostQuitMessage(0);//传递退出消息,跳出w5.消息循环
			break;
		}
//	default:
//		break;
	}

	//对未处理的消息进行默认处理
	return DefWindowProc(hWnd,msgID,wparam,lparam);
}

裸窗口的异步选择模型

SOCKET初始化

有了最基本的窗口之后,我们就可以在这个基础上增加相应的SOCKET代码。和之前一样,异步选择模型服务器端的SOCKET代码套路的前面部分是一样的:
1、包含网络头文件网络库
2、打开网络库
3、校验版本
4、创建SOCKET
5、绑定地址与端口
以上代码属于网络的初始化操作,因此不能放到窗口的第五步消息循环里面,而是放在第五步消息循环之前执行即可,当然也可以弄成一个函数的形式然后调用。
另外一个要注意的是,把SOCKET库文件拷贝过来的时候,需要把windows.h注释掉,因为二者有重复定义。
准备工作好了以后,下面就是异步选择模型的重点代码了

6、异步选择

6.1绑定消息和SOCKET

https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsaasyncselect

int WSAAPI WSAAsyncSelect(
  SOCKET s,
  HWND   hWnd,
  u_int  wMsg,
  long   lEvent
);

其作用是将消息和SOCKET绑定后,投递给操作系统。
参数1:SOCKET句柄
参数2:窗口句柄,指消息和SOCKET要投递到的指定窗口句柄中(每个窗口都有自己的消息队列)
参数3:SOCKET绑定的消息ID,不可与已有的系统消息(WM_开头)重复,可以用WM_USER+X来定义该消息ID,使用UM_开头。

#define UM_ASYNCSELECTMSG WM_USER+1//用户自定义消息

参数4:要绑定的操作,UDP这里就两个:FD_READ|FD_WRITE
返回:
成功:0
失败:SOCKET_ERROR
具体代码:

	//6、异步选择模型
	//6.1.绑定消息和服务器SOCKET,并投递给操作系统
	if (WSAAsyncSelect(socketServer,hWnd,UM_ASYNCSELECTMSG,FD_READ|FD_WRITE)==SOCKET_ERROR)//失败处理
	{
		int WSAAsyncSelecterr = WSAGetLastError();
		printf("WSAAsyncSelect失败错误码为:%d\n",WSAAsyncSelecterr);
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}

6.2根据操作码进行处理

这里要进入回调函数中处理,如果消息队列里面传送过来我们自定义的UM_ASYNCSELECTMSG消息,那么这个时候就要根据我们设置的FD_READ|FD_WRITE两个操作进行处理。
这里先看下UDP协议下传进来的参数,由于UDP只有服务器SOCKET句柄,客户端没有连接操作,因此,回调函数中的参数3就是服务器SOCKET句柄,获取代码如下:

	//获取回调函数带来的SOCKET,注意转定义
	SOCKET sockServer = (SOCKET)wparam;

服务器SOCKET中操作码的信息可以从回调函数中的lparam里面读取。lparam的低位保存的是操作码(LOWORD(lparam)),高位保存的是错误码(HIWORD(lparam))。
由于win32的窗口项目不能使用printf,这里我们可以创建HDC,然后用TextOut来显示信息。每次移动y轴坐标后显示信息。

//异步选择模型6.2.根据操作码进行处理
	case UM_ASYNCSELECTMSG:
		{
			//https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox
			//这里仅做测试用
			//MessageBox(NULL,"捕获SOCKET绑定消息","提示",MB_OK);
			
		
			//获取回调函数带来的SOCKET,注意转定义
			SOCKET socketServer = (SOCKET)wparam;
			
			//获取操作码,注意lparam的低位是操作码,高位是错误码
			if (HIWORD(lparam) == 0)
			{
				//获取当前窗口的上下文句柄(就是除了菜单和工具栏的其他区域)
				//获取它可用TextOut显示一些信息,因为这里printf没法用
				//要记得释放
				HDC hdc = GetDC(hWnd);
				if (FD_READ == LOWORD(lparam))
				{
					TextOut(hdc,10,y,"FD_READ执行ING!",sizeof("FD_READ执行ING!")-1);
					y+=16;//往下挪16个像素再输出

					//这里是基本模型里面的内容
					//收
					struct sockaddr sa;
					int iSaLen = sizeof(sa);
					char strRecvBuff[548]={0};

					if(recvfrom(socketServer,strRecvBuff,548,0,&sa,&iSaLen)==SOCKET_ERROR)
					{
						int err = WSAGetLastError();//取错误码
						char strerr = {0};
						//_itoa是整型转字符,最后一个参数代表进制
						TextOut(hdc,10,y,_itoa(err,&strerr,10),sizeof(_itoa(err,&strerr,10))-1);
						y+=16;
					}
					
					TextOut(hdc,10,y,strRecvBuff,strlen(strRecvBuff));
					y+=16;

					//发
					if(sendto(socketServer,"This is a asyncmessage from server~!",sizeof("This is a asyncmessage from server~!"),0,&sa,sizeof(sa))==SOCKET_ERROR)
					{
						int err = WSAGetLastError();//取错误码
						char strerr = {0};
						TextOut(hdc,10,y,_itoa(err,&strerr,10),sizeof(_itoa(err,&strerr,10))-1);
						y+=16;
					}

				}
				
				if (FD_WRITE == LOWORD(lparam))//FD_WRITE操作
				{
					TextOut(hdc,10,y,"FD_WRITE执行ING!",sizeof("FD_WRITE执行ING!")-1);
					y+=16;
				}
				ReleaseDC(hWnd,hdc);//释放hdc

			}			

			break;
		}

非裸窗口的创建

第一步:创建窗口结构体:WNDCLASSEX(这一步不能少设置属性,否则会在第三步失败)
第二步:注册窗口结构体:RegisterClassEx

//w1.创建窗口结构体:WNDCLASSEX
	WNDCLASSEX wcex;
	
	wcex.cbSize = sizeof(WNDCLASSEX); 
	
	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= (WNDPROC)WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= LoadIcon(hInstance, (LPCTSTR)IDI_UDPNONEMPTYASYNSELECT);
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= (LPCSTR)IDC_UDPNONEMPTYASYNSELECT;
	wcex.lpszClassName	= szWindowClass;
	wcex.hIconSm		= LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);
	
	//w2.注册窗口结构体:RegisterClassEx
	return RegisterClassEx(&wcex);

第三步:创建窗口:CreateWindowEx
第四步:显示窗口:ShowWindow

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
	HWND hWnd;
	
	hInst = hInstance; // Store instance handle in our global variable
	
	//w3.创建窗口:CreateWindowEx
	hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
	
	if (!hWnd)
	{
		return FALSE;
	}
	
	//w4.显示窗口:ShowWindow
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
	
	return TRUE;
}

第五步:消息循环:GetMessage、TranslateMessage、DispatchMessage

// w5.消息循环:GetMessage、TranslateMessage、DispatchMessage
	// Main message loop:
	while (GetMessage(&msg, NULL, 0, 0)) 
	{
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) 
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}
	

第六步:创建回调函数

//w6.创建回调函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

非裸窗口的异步选择模型

1-5

SOCKET代码加在消息循环之前:

while (GetMessage(&msg, NULL, 0, 0)) 

和之前一样,异步选择模型服务器端的SOCKET代码套路的前面部分是一样的:
1、包含网络头文件网络库

//1、包含网络头文件网络库
#include <WinSock2.h>
#include <stdio.h>


#pragma comment(lib, "Ws2_32.lib")

#define UM_ASYNCSELECTMSG WM_USER+1//用户自定义消息

2、打开网络库

int iret = WSAStartup(wVersionRequested,&wsaDATA);
	if (iret!=0)
	{
		//有错
		switch(iret)
		{
			case WSASYSNOTREADY: 
				printf("解决方案:重启。。。");
				break; 
			case WSAVERNOTSUPPORTED: 
				printf("解决方案:更新网络库");
				break; 
			case WSAEINPROGRESS: 
				printf("解决方案:重启。。。");
				break; 
			case WSAEPROCLIM: 
				printf("解决方案:网络连接达到上限或阻塞,关闭不必要软件");
				break; 
			case WSAEFAULT:
				printf("解决方案:程序有误");
				break;
		}
		getchar();
		return 0;
	}

3、校验版本

//3、校验版本,只要有一个不是2,说明系统不支持我们要的2.2版本	
	if (2!=HIBYTE(wsaDATA.wVersion)|| 2!=LOBYTE(wsaDATA.wVersion))
	{
			printf("版本有问题!");
			WSACleanup();//关闭网络库
			return 0;
	}

4、创建SOCKET

// 4、创建SOCKET
	SOCKET socketServer = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
	if(INVALID_SOCKET == socketServer)
	{
		//清理网络库,不关闭句柄
		WSACleanup();
		return 0;
	}

5、绑定地址与端口

//5、绑定地址与端口
	struct sockaddr_in si;
	si.sin_family = AF_INET;//这里要和创建SOCKET句柄的参数1类型一样
	si.sin_port = htons(9527);//用htons宏将整型转为端口号的无符号整型
	si.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");

	if(bind(socketServer,(struct sockaddr*)&si,sizeof(si))==SOCKET_ERROR)
	{
		int err = WSAGetLastError();//取错误码
		printf("服务器bind失败错误码为:%d\n",err);
		closesocket(socketServer);//释放
		WSACleanup();//清理网络库
	}

6、异步选择

6.1绑定消息和SOCKET

	//6、异步选择模型
	//6.1.绑定消息和服务器SOCKET,并投递给操作系统
	if (WSAAsyncSelect(socketServer,hWnd,UM_ASYNCSELECTMSG,FD_READ|FD_WRITE)==SOCKET_ERROR)//失败处理
	{
		int WSAAsyncSelecterr = WSAGetLastError();
		printf("WSAAsyncSelect失败错误码为:%d\n",WSAAsyncSelecterr);
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}

6.2 根据操作码进行处理

在主窗口回调函数的switch下面加一个case,坐标变量在函数外面定义一下

int y =0;//显示消息的y坐标,每次显示消息后y坐标+16,避免消息重复在同一个区域显示
//异步选择模型6.2.根据操作码进行处理
	case UM_ASYNCSELECTMSG:
		{
			//https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox
			//这里仅做测试用
			//MessageBox(NULL,"捕获SOCKET绑定消息","提示",MB_OK);
			
		
			//获取回调函数带来的SOCKET,注意转定义
			SOCKET socketServer = (SOCKET)wparam;
			
			//获取操作码,注意lparam的低位是操作码,高位是错误码
			if (HIWORD(lparam) == 0)
			{
				//获取当前窗口的上下文句柄(就是除了菜单和工具栏的其他区域)
				//获取它可用TextOut显示一些信息,因为这里printf没法用
				//要记得释放
				HDC hdc = GetDC(hWnd);
				if (FD_READ == LOWORD(lparam))
				{
					TextOut(hdc,10,y,"FD_READ执行ING!",sizeof("FD_READ执行ING!")-1);
					y+=16;//往下挪16个像素再输出

					//收
					struct sockaddr sa;
					int iSaLen = sizeof(sa);
					char strRecvBuff[548]={0};

					if(recvfrom(socketServer,strRecvBuff,548,0,&sa,&iSaLen)==SOCKET_ERROR)
					{
						int err = WSAGetLastError();//取错误码
						char strerr = {0};
						//_itoa是整型转字符,最后一个参数代表进制
						TextOut(hdc,10,y,_itoa(err,&strerr,10),sizeof(_itoa(err,&strerr,10))-1);
						y+=16;
					}
					
					TextOut(hdc,10,y,strRecvBuff,strlen(strRecvBuff));
					y+=16;

					//发
					if(sendto(socketServer,"This is a asyncmessage from server~!",sizeof("This is a asyncmessage from server~!"),0,&sa,sizeof(sa))==SOCKET_ERROR)
					{
						int err = WSAGetLastError();//取错误码
						char strerr = {0};
						TextOut(hdc,10,y,_itoa(err,&strerr,10),sizeof(_itoa(err,&strerr,10))-1);
						y+=16;
					}

				}
				
				if (FD_WRITE == LOWORD(lparam))
				{
					TextOut(hdc,10,y,"FD_WRITE执行ING!",sizeof("FD_WRITE执行ING!")-1);
					y+=16;
				}
				ReleaseDC(hWnd,hdc);//释放hdc

			}

			

			break;
		}

非空项目附录

把VS2019自动生成非裸窗口项目的readme放上来:
项目名.vcxproj
这是使用应用程序向导生成的 VC++ 项目的主项目文件,其中包含生成该文件的 Visual C++ 的版本信息,以及有关使用应用程序向导选择的平台、配置和项目功能的信息。

项目名.vcxproj.filters
这是使用“应用程序向导”生成的 VC++ 项目筛选器文件。它包含有关项目文件与筛选器之间的关联信息。在 IDE 中,通过这种关联,在特定节点下以分组形式显示具有相似扩展名的文件。例如,“.cpp”文件与“源文件”筛选器关联。

主文件名.cpp
这是主应用程序源文件。

/
应用程序向导创建了下列资源:

项目名.rc
这是程序使用的所有 Microsoft Windows 资源的列表。它包括 RES 子目录中存储的图标、位图和光标。此文件可以直接在 Microsoft Visual C++ 中进行编辑。

Resource.h
这是标准头文件,可用于定义新的资源 ID。Microsoft Visual C++ 将读取并更新此文件。

非空项目异步选择.ico
这是用作应用程序图标 (32x32) 的图标文件。此图标包括在主资源文件 非空项目异步选择.rc 中。

small.ico
这是一个图标文件,其中包含应用程序的图标的较小版本 (16x16)。此图标包括在主资源文件 非空项目异步选择.rc 中。

/
其他标准文件:

StdAfx.h, StdAfx.cpp
这些文件用于生成名为 非空项目异步选择.pch 的预编译头 (PCH) 文件和名为 StdAfx.obj 的预编译类型文件。

/

MFC窗口的创建

在这里插入图片描述
选择哪个都行,不过第三个的代码会少一些,后面都用默认即可
在这里插入图片描述
运行效果:
在这里插入图片描述
代码很多,找到UDPMFCAsynSelectDlg.cpp
找到消息映射代码(消息映射有很多段)


//消息映射
BEGIN_MESSAGE_MAP(CUDPMFCAsynSelectDlg, CDialog)
	//{{AFX_MSG_MAP(CUDPMFCAsynSelectDlg)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_UM_SELECTMSG(UM_ASYNCSELECTMSG,&CUDPMFCAsynSelectDlg::OnMyMsg);
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

在这里加上自定义消息映射,里面内含两个参数,一个是消息ID,一个操作函数(这里是OnMyMsg)

#define UM_ASYNCSELECTMSG WM_USER+1//用户自定义消息ID

操作函数需要在头文件中添加操作函数声明:

public:
	LRESULT OnMyMsg(WPARAM wParam, LPARAM lParam);

可以看到这个操作函数和回调函数差不多,就少了两个参数。

LRESULT CUDPMFCAsynSelectDlg::OnMyMsg(WPARAM wParam, LPARAM lParam)
{

}

然后往里面放异步选择模型

MFC的异步选择模型

1-5

将网络的初始化步骤放在OnInitDialog()中,当然也可以放构造函数里面
放提示语下面就好

// TODO: Add extra initialization here

1、包含网络头文件网络库
2、打开网络库
3、校验版本
4、创建SOCKET
5、绑定地址与端口

6、异步选择

6.1绑定消息和SOCKET

这块代码接着上面的代码放就好

	//6、异步选择模型
	//6.1.绑定消息和服务器SOCKET,并投递给操作系统,MFC里面hWnd封装为m_hWnd
	if (WSAAsyncSelect(socketServer,m_hWnd,UM_ASYNCSELECTMSG,FD_READ|FD_WRITE)==SOCKET_ERROR)//失败处理
	{
		int WSAAsyncSelecterr = WSAGetLastError();
		printf("WSAAsyncSelect失败错误码为:%d\n",WSAAsyncSelecterr);
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}

6.2 根据操作码进行处理

这块代码放在操作函数里面:

LRESULT CUDPMFCAsynSelectDlg::OnMyMsg(WPARAM wParam, LPARAM lParam)
{
	//获取回调函数带来的SOCKET,这里可以不获取,因为已经是全局变量
	//SOCKET socketServer = (SOCKET)wparam;
	
	//获取操作码,注意lparam的低位是操作码,高位是错误码
	if (HIWORD(lParam) == 0)
	{
		//获取当前窗口的上下文句柄(就是除了菜单和工具栏的其他区域)
		//获取它可用TextOut显示一些信息,因为这里printf没法用
		//要记得释放
		HDC hdc = ::GetDC(m_hWnd);
		if (FD_READ == LOWORD(lParam))
		{
			TRACE("%s\n","FD_READ");
			TextOut(hdc,10,y,"FD_READ执行ING!",sizeof("FD_READ执行ING!")-1);
			y+=16;//往下挪16个像素再输出
			
			//收
			struct sockaddr sa;
			int iSaLen = sizeof(sa);
			char strRecvBuff[548]={0};
			
			if(recvfrom(socketServer,strRecvBuff,548,0,&sa,&iSaLen)==SOCKET_ERROR)
			{
				int err = WSAGetLastError();//取错误码
				char strerr = {0};
				TRACE("%d\n",err);
				//_itoa是整型转字符,最后一个参数代表进制
				TextOut(hdc,10,y,_itoa(err,&strerr,10),sizeof(_itoa(err,&strerr,10))-1);
				y+=16;
			}
			TRACE("%s\n",strRecvBuff);
			TextOut(hdc,10,y,strRecvBuff,strlen(strRecvBuff));
			y+=16;
			
			//发
			if(sendto(socketServer,"This is a asyncmessage from server~!",sizeof("This is a asyncmessage from server~!"),0,&sa,sizeof(sa))==SOCKET_ERROR)
			{
				int err = WSAGetLastError();//取错误码
				char strerr = {0};
				TRACE("%d\n",err);
				TextOut(hdc,10,y,_itoa(err,&strerr,10),sizeof(_itoa(err,&strerr,10))-1);
				y+=16;
			}			
		}
		
		if (FD_WRITE == LOWORD(lParam))
		{
			TRACE("%s\n", "FD_WRITE");
			TextOut(hdc,10,y,"FD_WRITE执行ING!",sizeof("FD_WRITE执行ING!")-1);
			y+=16;
		}
		::ReleaseDC(m_hWnd,hdc);//释放hdc
		
	}
	return 0;
}

这里有几个小地方要注意:
1、可以用TRACE来打印东西,用法和printf一样,但是只能在调试模式下看到;
2、TextOut没有去掉,因为更加直观一些,他的y坐标变量在头文件加了声明,在OnInitDialog()赋初始值;
3、服务器SOCKET句柄可以从参数wParam里面取也可以直接用全局变量里面的句柄。

释放句柄

这个最好在析构函数里面做:
头文件加析构函数定义后,主文件加:


//析构函数
CUDPMFCAsynSelectDlg::~CUDPMFCAsynSelectDlg()
{
	closesocket(socketServer);
	WSACleanup();
}
  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-08-02 11:07:19  更:2021-08-02 11:08:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/4 10:59:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码