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();
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oldmao_2000

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值