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/IP 异步选择模型 -> 正文阅读

[网络协议]UDP/IP 异步选择模型

实现步骤:

异步选择模型是基于Windows消息机制的,也就是消息队列,Windows操作系统会为每个窗口创建一个消息队列并且维护

1. 创建窗口:

??????? 创建窗口步骤:

??????????????? 第一步:创建窗口结构体:

typedef struct tagWNDCLASSEXW {
  UINT      cbSize;
  UINT      style;
  WNDPROC   lpfnWndProc;
  int       cbClsExtra;
  int       cbWndExtra;
  HINSTANCE hInstance;
  HICON     hIcon;
  HCURSOR   hCursor;
  HBRUSH    hbrBackground;
  LPCWSTR   lpszMenuName;
  LPCWSTR   lpszClassName;
  HICON     hIconSm;
} WNDCLASSEXW, *PWNDCLASSEXW, *NPWNDCLASSEXW, *LPWNDCLASSEXW;

??????????????? 第二步:注册窗口结构体:

ATOM RegisterClassA(
  const WNDCLASSA *lpWndClass
);

??????????????? 第三步:创建窗口

HWND CreateWindowExA(
  DWORD     dwExStyle,
  LPCSTR    lpClassName,
  LPCSTR    lpWindowName,
  DWORD     dwStyle,
  int       X,
  int       Y,
  int       nWidth,
  int       nHeight,
  HWND      hWndParent,
  HMENU     hMenu,
  HINSTANCE hInstance,
  LPVOID    lpParam
);

??????????????? 第四步:显示窗口

BOOL ShowWindow(
  HWND hWnd,
  int  nCmdShow
);

??????????????? 第五步:更新窗口

BOOL UpdateWindow(
  HWND hWnd
);

??????????????? 第六步:消息循环

BOOL GetMessage(
  LPMSG lpMsg,
  HWND  hWnd,
  UINT  wMsgFilterMin,
  UINT  wMsgFilterMax
);

??????????????? 第七步:回调函数

LRESULT CALLBACK WindowProc(
  _In_ HWND   hwnd,
  _In_ UINT   uMsg,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
);

2. 异步选择模型核心:

??????? 第一步:将服务端socket与一个消息(自定义的消息)绑定在一起并投递给操作系统,投递完之后操作系统会帮我们监视着,如果有socket发来一些请求,操作系统会产生消息,并把消息放进消息队列里;

??????? 将socket与消息绑定在一起并投递给操作系统:

int WSAAsyncSelect(	// 函数功能:将socket与消息绑定在一起,并投递给操作系统
  SOCKET s,		// 要绑定的socket
  HWND   hWnd,	// 窗口句柄 绑定到那个窗口上
  u_int  wMsg,	// 消息编号 填自定义消息 注意要比 WM_USER大
  long   lEvent	// 消息类型 FD_READ FD_WRITE ...
);

??????? 第二步:循环从消息队列里往外取消息,之后进行分类处理;在窗口回调函数中,进行分类处理;

服务端代码:

/*
	工程:win32桌面应用程序
	项目:异步选择模型
	项目已改多字符集
	
	UDP/IP异步选择模型:
		异步选择模型是基于Windows消息机制的,也就是消息队列
		Windows操作系统会为每个窗口创建一个消息队列并且维护
		创建窗口步骤:
			第一步:创建窗口结构体
			第二步:注册窗口结构体
			第三步:创建窗口
			第四步:显示窗口
			第五步:更新窗口
			第六步:消息循环
			第七步:回调函数
		异步选择模型核心:
			第一步:将服务端socket,与一个消息绑定在一起并且投递给操作系统,
					投递完之后操作系统会帮我们监管着,如果有socket发来一些请求,操作系统会产生消息,并把信息放在消息队列里;
			第二步:循环从消息队列里往外取消息,进行分类处理,
*/
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <string.h>

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

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

// 声明回调函数
long CALLBACK fnWndProcBack(HWND hwnd, UINT msgID, WPARAM wParam, LPARAM lParam);

// 创建socket 并 绑定ip地址与端口号
SOCKET CreateSock_Bind();

// 窗口主函数
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hProInstance, LPSTR lpCmdLine, int nShowCmd)
{
	// 1.创建窗口结构体
	WNDCLASSEX wc;
	// 给窗口结构体成员赋值
	wc.cbSize = sizeof(wc);
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon = NULL;
	wc.hCursor = NULL;//loadcurser
	wc.hIconSm = NULL;		//任务栏图标
	wc.lpszClassName = "UDP/IP服务端";
	wc.lpszMenuName = NULL;	//菜单栏
	wc.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));
	wc.lpfnWndProc = fnWndProcBack;
	wc.style = CS_HREDRAW | CS_VREDRAW;
	//2.注册窗口结构体
	RegisterClassEx(&wc);
	//3.创建窗口
	HWND hwnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, "UDP/IP服务端", "异步选择模型", WS_OVERLAPPEDWINDOW,
		50, 50, 500, 900, NULL, NULL, hInstance, NULL);
	//判断返回值
	if (NULL == hwnd)
	{
		return 0;
	}
	//4.显示窗口
	ShowWindow(hwnd, nShowCmd);
	//5.更新窗口
	UpdateWindow(hwnd);

	// 创建socket 并 绑定ip地址与端口号
	SOCKET socketServer = CreateSock_Bind();
	
/*int WSAAsyncSelect(	// 函数功能:将socket与消息绑定在一起,并投递给操作系统
  SOCKET s,		// 要绑定的socket
  HWND   hWnd,	// 窗口句柄 绑定到那个窗口上
  u_int  wMsg,	// 消息编号 填自定义消息 注意要比 WM_USER大
  long   lEvent	// 消息类型 FD_READ FD_WRITE ...
);*/	
	// 将成功创建的 socket绑定事件并投递给操作系统
	int nAsyn = WSAAsyncSelect(socketServer, hwnd, UM_ASYNCSELECTMSG, FD_READ | FD_WRITE);
	// 判断是否成功绑定并投递给操作系统
	if (SOCKET_ERROR == nAsyn)
	{
		// 出错了
		int a = WSAGetLastError();
		// 获取可操作客户区 
		HDC hdc = GetDC(hwnd);	// 用完之后要记得释放
		// 定义字符串数组获取整型转成字符型的变量
		char str[20] = { 0 };
		// 打印错误码 itoa 
		TextOut(hdc, 0, 0, _itoa(a, str, 10), sizeof(str));

		//释放socket
		closesocket(socketServer);
		//清理网络库
		WSACleanup();
		// 释放 HDC
		ReleaseDC(hwnd, hdc);
		return 0;
	}

	//6.消息循环
	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{
		//翻译消息
		TranslateMessage(&msg);
		//分发消息
		DispatchMessage(&msg);
	}
	//释放socket
	closesocket(socketServer);
	//清理网络库
	WSACleanup();
	return 0;
}

// 定义 TextOut函数中参数3 y轴所需的变量
int y = 0;

// 声明回调函数
long CALLBACK fnWndProcBack(HWND hwnd, UINT msgID, WPARAM wParam, LPARAM lParam)
{
	// 通过参数2进行分类处理消息
	switch (msgID)
	{
		// 自定义消息
	case UM_ASYNCSELECTMSG:
	{
		// 获取可操作客户区 
		HDC hdc = GetDC(hwnd);	// 用完之后要记得释放

		// 通过回调函数参数3获取socket 服务端socket
		SOCKET sock = (SOCKET)wParam;
		// 将lParam 拆成两部分:HIWORD装高字节 LOWORD装低字节
		// 高字节产生的错误码 低字节处理具体的消息种类
		if (0 == HIWORD(lParam))
		{
			// 通过低字节分类处理消息
			// 处理 FD_WRITE  
			if (LOWORD(lParam) == FD_WRITE)
			{
				// 打印数据 提示成功处理 FD_WRITE 窗口显示日志 调用TextOut函数
				TextOut(hdc, 0, y, "FD_WRITE", strlen("FD_WRITE"));
				// 记录y轴位置+20
				y += 20;
			}
			// 处理 FD_READ
			if (LOWORD(lParam) == FD_READ)
			{
				// 1.接收来之 客户端的发来的消息
				// 定义recvfrom函数中参数2所需的 接收字节数的字符串数组
				char strBuf[548] = { 0 };
				// 声明recvfrom函数中参数5所需的 发送方所需的socket结构体
				struct sockaddr sockClient;
				// 获取客户端socket结构体的大小
				int len = sizeof(sockClient);
				int nRecv = recvfrom(sock, strBuf, 548, 0, &sockClient, &len);
				// 判断是否成功接收消息
				if (SOCKET_ERROR == nRecv)
				{
					// 出错了
					int a = WSAGetLastError();
					// 定义字符串数组获取整型转成字符型的变量
					char str[20] = { 0 };
					// 打印错误码 itoa 
					TextOut(hdc, 0, y, _itoa(a, str, 10), sizeof(str));
					// 记录的输出日志位置y轴 +=20
					y += 20;
				}
				// 打印成功接收的数据报
				TextOut(hdc, 0, y, strBuf, strlen(strBuf));
				// 记录的输出日志位置y轴 +=20
				y += 20;

				// 2.进行发送数据报操作
				int nSend = sendto(sock, "send ok", sizeof("send ok"), 0, &sockClient, sizeof(sockClient));
				// 判断是否成功发送消息
				if (SOCKET_ERROR == nSend)
				{
					// 出错了
					int a = WSAGetLastError();
					// 定义字符串数组获取整型转成字符型的变量
					char str[20] = { 0 };
					// 打印错误码 itoa 
					TextOut(hdc, 0, y, _itoa(a, str, 10), sizeof(str));
					// 记录的输出日志位置y轴 +=20
					y += 20;
				}
			}
		}
		// 释放 HDC
		ReleaseDC(hwnd, hdc);
	}
		break;
	case WM_CREATE:	// 只初始化一次
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	}

	return DefWindowProc(hwnd, msgID, wParam, lParam);
}
// 创建socket 并 绑定ip地址与端口号
SOCKET CreateSock_Bind()
{
	//1.:打开网络库
	WORD wVersionRequired = MAKEWORD(2, 2);
	WSADATA wsadata;
	int nStar = WSAStartup(wVersionRequired, &wsadata);
	//判断
	if (0 != nStar)
	{
		int a = WSAGetLastError();

		return 0;
	}
	//2.校验版本
	if (2 != HIBYTE(wsadata.wVersion) || 2 != LOBYTE(wsadata.wVersion))
	{
		int a = WSAGetLastError();
		//清理网络库
		WSACleanup();
		return 0;
	}
	//3.:创建socket
	SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	//判断
	if (INVALID_SOCKET == sock)
	{
		int a = WSAGetLastError();
		//清理网络库
		WSACleanup();
		return 0;
	}
	//4.:绑定地址与端口
	struct sockaddr_in si;
	si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	si.sin_port = htons(12345);
	si.sin_family = AF_INET;
	if (SOCKET_ERROR == bind(sock, (const struct sockaddr*)&si, sizeof(si)))
	{
		int a = WSAGetLastError();
		//释放socket
		closesocket(sock);
		//清理网络库
		WSACleanup();
		return 0;
	}
	return sock;
}

客户端代码:

#define _WINSOCK_DEPRECATED_NO_WARNINGS

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

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

/*
	UDP/IP 基本C/S模型 客户端:
		与 UDP/IP 基本C/S模型 服务端代码的区别:
		代码基本一致,只有bind 函数有区别
		服务端需要进行绑定地址与端口号,因为服务端是一对多
		客户端只需要创建服务端地址与端口结构体,与服务端发送数据报时,
		  直接传递的是创建的结构体就可以了,不用进行绑定,因为服务端的ip与端口号固定不变的	

*/
// 1.打开网络库 2.校验版本号
void OpenAndCheckWSAStartup();

// 3.创建服务端socket
SOCKET Sock_Server();

// 全局声明要创建 创建客户端socket
SOCKET socketClient;

// 4.与客户端进行发收数据
void SendToAndRecvFrom(SOCKET sock);

int main()
{
	// 1.打开网络库 2.校验版本号
	OpenAndCheckWSAStartup();

	// 3.创建服务端socket
	SOCKET socketClient = Sock_Server();

	// 4.与客户端进行发收数据
	SendToAndRecvFrom(socketClient);

	//释放socket 
	closesocket(socketClient);
	//清理网络库
	WSACleanup();
	system("pause");
	return 0;
}

// 1.打开网络库 2.校验版本号
void OpenAndCheckWSAStartup()
{
	// 定义WSAStartup函数中参数1 所需的版本号
	WORD wVersionRequestd = MAKEWORD(2, 2);	// 设置2.2版本号
	// 声明WSAStartup函数中参数1 所需的结构体
	WSADATA WSAData;
	// 打开网络库
	int nRet = WSAStartup(wVersionRequestd, &WSAData);
	if (0 != nRet)
	{
		//出错了
		int a = WSAGetLastError();
		printf("网络库打开失败 %d\n", a);
		return;
	}
	// 检验版本号
	if (2 != HIBYTE(WSAData.wVersion) || 2 != LOBYTE(WSAData.wVersion))
	{
		printf("校验版本号错误\n");
		// 关闭网络库
		WSACleanup();
		return;
	}
}
// 3.创建服务端socket
SOCKET Sock_Server()
{
	// 创建服务端 socket
	SOCKET sock_server = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	// 判断服务端socket是否成功创建
	if (INVALID_SOCKET == sock_server)
	{
		//出错了
		int a = WSAGetLastError();
		printf("创建服务端socket失败,获取的错误码为 %d\n", a);
		//关闭网络库
		WSACleanup();
		return 0;
	}
	return sock_server;
}
// 4.与客户端进行发收数据
void SendToAndRecvFrom(SOCKET sock)
{
	/*int recvfrom(
	  SOCKET   s,
	  char     *buf,
	  int      len,
	  int      flags,
	  sockaddr *from,
	  int      *fromlen
	);
	recvfrom函数:
		作用:得到当前服务端接收到的消息
		本质:复制	将协议缓存区的数据复制黏贴进自定义的buf
		与recv函数一样是进行接收消息,不同的是recv是傻等,recvfrom是死等
	参数1:客户端的socket
		特点:
			TCP是获取指定的,recv函数与客户端是1对1的关系 -- 傻等
			UDP是无差别获取,recvfrom函数与客户端是1对多的关系 -- 死等
	参数2:客户端消息的存储空间,也就是一个字符数组
		广域网:各级路由器上的MTU最小值是576字节
			tcp = 576 - 20(TCP包头) - 20(IP包头) = 536;
			udp = 576 - 8(UDP包头) - 20(IP包头) = 548;
	参数3:想要读取的字节个数, 参数2的字节数
	参数4:数据的读取方式
			0			从协议缓存区取一个数据报,然后就删除掉
			MSG_PEEK	取出数据报,但是协议缓存内不删除,一直残留,导致影响后面的数据读取
			MSG_OOB		带外数据
	参数5:对方的IP地址端口号
			此处结构体为相应的IP地址和端口号 struct sockaddr* sockClient; 填 &sockClient;
	参数6:参数5结构体的大小 int len = sizeof(&sockClient);	填 &len;
	*/
	/*int sendto(
	  SOCKET         s,
	  const char     *buf,
	  int            len,
	  int            flags,
	  const sockaddr *to,
	  int            tolen
	);
	sendto函数:
		作用:向目标发送数据报
		本质:send函数将我们的数据复制黏贴进行系统的协议发送缓冲区,计算机伺机发出去
	参数1:当前客户端的socket(自己的socket)
	参数2:给接收方发送的字节串  UDP统一字节数 548
	参数3:参数2的大小(字节个数 548)
	参数4:填0
	参数5:对方IP地址与端口号结构体
	参数6:参数5的大小

	*/
	// 声明sendto函数中参数5所需的 服务端地址与端口号结构体
	struct sockaddr_in sockServer;
	// 给服务端地址与端口号结构体成员赋值
	sockServer.sin_family = AF_INET;
	sockServer.sin_port = htons(12345);
	sockServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	// 循环与客户端进行收发消息
	while (1)
	{
		// 定义要发送的字节数组
		char strBuf[548] = { 0 };
		// 手动发送
		scanf_s("%s", strBuf, 548);
		// 设置输入0时,将关闭客户端
		if ('0' == strBuf[0])
		{
			break;
		}

		// 1.发消息
		int nSet = sendto(sock, strBuf, sizeof(strBuf), 0, (struct sockaddr*)&sockServer, sizeof(sockServer));
		// 判断是否成功发送消息
		if (SOCKET_ERROR == nSet)
		{
			// 出错了
			int a = WSAGetLastError();
			printf("发送消息失败,获取的错误码为:%d\n", a);
			continue;
		}

		// 2.收消息
		// 定义recvfrom 函数所需的接收字节数组
		char buf[548] = { 0 };
		// 声明recvfrom函数中参数5所需的 结构体相应的IP地址与端口号
		struct sockaddr sockClient;	// 对方的IP地址与端口号
		// 定义recvfrom函数中参数6所需的 参数5结构体的大小
		int len = sizeof(sockClient);
		int nRet = recvfrom(sock, buf, 548, 0, &sockClient, &len);
		// 判断是否成功接收消息
		if (SOCKET_ERROR == nRet)
		{
			// 出错了
			int a = WSAGetLastError();
			printf("接收消息失败,获取的错误码为:%d\n", a);
			continue;
		}
		// 进行接收消息
		printf("Server say: %s\n", buf);	
	}
}

程序运行结果:

?

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-08-27 12:13:42  更:2021-08-27 12:14:53 
 
开发: 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年11日历 -2024/11/25 21:46:41-

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