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 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> TCP/IP 事件选择模型 -> 正文阅读

[网络协议]TCP/IP 事件选择模型

服务端:

1.打开网络库

int WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData);

2.校验版本号

2 != HIBYTE(wsaData.wVersion) || 2 != LOBYTE(wsaData.wVersion)  // (检验版本号是 2.2)

3.创建socket 套接字

SOCKET socket(int af, int type, int protocol);

?4.绑定地址与端口号

int bind(SOCKET s, const sockaddr* addr, int namelen);

?5.进行监听

int WSAAPI listen(SOCKET s, int backlog);

6.事件选择模型开始

第一步:创建事件对象:

WSAEVENT WSAAPI WSACreateEvent();

第二步:绑定并投递

int WSAAPI WSAEventSelect
(
  SOCKET   s,
  WSAEVENT hEventObject,
  long     lNetworkEvents
);

第三步:创建结构体用于装所有的socket和对应的事件对象,并记录对应的个数

struct fd_es_set
{
	SOCKET allsocks[64];	// 装所有的socket
	WSAEVENT allevents[64];	// 装所有的事件
	int count;				// 记录个数
};

第四步:等待事件

DWORD WSAAPI WSAWaitForMultipleEvents(
  DWORD          cEvents,             //事件,socket的个数
  const WSAEVENT *lphEvents,  //事件数组的首地址
  BOOL           fWaitAll,               //直接填FALSE
  DWORD          dwTimeout,      //可以填WSA_INFINITE,表示一直等待事件的发生
  BOOL           fAlertable             //直接填FALSE
);

??????? 如果WSAWaitForMultipleEvents()函数中的参数4 填具体的等待时间的话,要进行判断是否超时操作:

判断是否超时

if (WSA_WAIT_TIMEOUT == retWaitEv)
{
	// 在规定的时间每没有等待到事件发生 直接继续进行等待
	continue;
}

第五步:列举事件

int WSAAPI WSAEnumNetworkEvents(
  SOCKET             s,
  WSAEVENT           hEventObject,
  LPWSANETWORKEVENTS lpNetworkEvents
);

第六步:分类处理 FD_ACCEPT FD_READ FD_WRITE FD_CLOSE

// 处理 FD_ACCEPT FD_READ FD_WRITE FD_CLOSE
if (lpNetworkEvents->lNetworkEvents & FD_ACCEPT)
{
    if (lpNetworkEvents->iErrorCode[FD_ACCEPT_BIT] == 0)
    {
       
    }
}

代码如下:

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")

/*
	事件选择模型步骤:
	第一步:创建一个事件对象	
				WSACreateEvent();
	第二步:将socket 和 对应的事件绑定在一起 并投递给系统,由系统帮我们监视着
				WSAEventSelect();
	第三步:创建一个结构体,用来装所有的socket和对应的事件,每创建一个socket和对应的事件都装进结构体对应的数组中
				struct fd_eventsocket_set{SOCKET allsock[64]; WSAEVENT allevent[64]; int count;};
				注意每装一次 对应的结构体的变量 count就要+1
	第四步:查看事件是否有信号  
				WSAWaitForMultipleEvents();
	第五步:有信号的话进行分类处理
				WSAEnumNetworkEvents();
	第六步:分类处理 FD_ACCEPT FD_WRITE FD_READ FD_CLOSE
*/

// 6.3、创建结构体用来装所有的socket和对应的事件
struct fd_es_set
{
	SOCKET allsocks[64];	// 装所有的socket
	WSAEVENT allevents[64];	// 装所有的事件
	int count;				// 记录个数
};
// 定义结构体对象
struct fd_es_set esSet = { {0}, {NULL}, 0 };

// 声明WSAStartup 函数中参数2所需的结构体
WSADATA wsadata;	

// 监视窗口的回调函数
BOOL WINAPI fun(DWORD dwCtrlType)
{
	switch (dwCtrlType)
	{
	case CTRL_CLOSE_EVENT:
	{
		// 关闭套接字 和对应的事件
		for (int i = 0; i < esSet.count; i++)
		{
			closesocket(esSet.allsocks[i]);
			WSACloseEvent(esSet.allevents[i]);
		}
		// 清理网络库
		WSACleanup();
	}
	break;
	}
	return TRUE;
}

// 1.打开网络库
void OpenWSAStarup()
{
	WORD version = MAKEWORD(2, 2);	// 设置版本号为 2.2	
	int retStartup = WSAStartup(version, &wsadata);
	// 检验是否成功打开网络库
	if (0 != retStartup)
	{
		// 出错了
		int error = WSAGetLastError();	// 通过WSAGetLastError 获取错误码
		printf("打开网络库失败,错误码为: %d\n", error);
		return;	// 退出
	}
}

// 2.检验版本号
void CheckVersion()
{
	if (2 != HIBYTE(wsadata.wVersion) || 2 != LOBYTE(wsadata.wVersion))
	{
		// 出错了
		int error = WSAGetLastError();	// 通过WSAGetLastError 获取错误码
		printf("检验版本号出错,错误码为: %d\n", error);
		// 清理网络库
		WSACleanup();
		return;	// 退出
	}
}

// 3.创建socket 服务端的socket
SOCKET createSocket()
{
	SOCKET socket_Server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	// 检验是否成功创建socket
	if (INVALID_SOCKET == socket_Server)
	{
		// 出错了
		int error = WSAGetLastError();	// 通过WSAGetLastError 获取错误码
		printf("创建socket错误,错误码为: %d\n", error);
		// 清理网络库
		WSACleanup();
		return 0;	// 退出
	}
	return socket_Server;
}

// 4.绑定地址与端口号
void Bind(SOCKET socket_Server)
{
	// 声明bind() 函数所需的参数2 的结构体
	struct sockaddr_in serverMsg;
	// 给结构体成员初始化
	serverMsg.sin_family = AF_INET;								// ip 地址的类型
	serverMsg.sin_port = htons(12345);							// 端口号
	serverMsg.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");	// 本地ip
	int retBind = bind(socket_Server, (struct sockaddr*)&serverMsg, sizeof(serverMsg));
	// 检验是否成功绑定ip和端口号
	if (SOCKET_ERROR == retBind)
	{
		// 出错了
		int error = WSAGetLastError();	// 通过WSAGetLastError 获取错误码
		printf("绑定ip和端口号失败,错误码为: %d\n", error);
		// 关闭套接字
		closesocket(socket_Server);
		// 清理网络库
		WSACleanup();
		return;	// 退出
	}
}

// 5.进行监听
void Listen(SOCKET socket_Server)
{
	if (SOCKET_ERROR == listen(socket_Server, SOMAXCONN))
	{
		// 出错了
		int error = WSAGetLastError();	// 通过WSAGetLastError 获取错误码
		printf("进行监听失败,错误码为: %d\n", error);
		// 关闭套接字
		closesocket(socket_Server);
		// 清理网络库
		WSACleanup();
		return;	// 退出
	}
}

// 6.事件选择模型核心代码
void EventSelect(SOCKET socket_Server)
{
	// 6.1、创建一个事件对象
	WSAEVENT eventServer = WSACreateEvent();
	// 检验是否成功创建事件对象
	if (WSA_INVALID_EVENT == eventServer)
	{
		// 创建事件对象失败
		// 出错了
		int error = WSAGetLastError();	// 通过WSAGetLastError 获取错误码
		printf("WSACreateEvent() 创建事件对象失败,错误码为: %d\n", error);
		// 关闭套接字
		closesocket(socket_Server);
		// 清理网络库
		WSACleanup();
		return;	// 退出
	}
	// 6.2、绑定并投递
	int retEtSel = WSAEventSelect(socket_Server, eventServer, FD_ACCEPT);
	// 检验是否成功绑定并投递
	if (SOCKET_ERROR == retEtSel)
	{
		// 绑定并投递失败
		int error = WSAGetLastError();	// 通过WSAGetLastError 获取错误码
		printf("WSAEventSelect() 绑定并投递失败,错误码为: %d\n", error);
		// 关闭套接字
		closesocket(socket_Server);
		// 关闭事件对象
		WSACloseEvent(eventServer);
		// 清理网络库
		WSACleanup();
		return;	// 退出
	}
	 6.3、创建结构体用来装所有的socket和对应的事件
	//struct fd_es_set
	//{
	//	SOCKET allsocks[64];	// 装所有的socket
	//	WSAEVENT allevents[64];	// 装所有的事件
	//	int count;				// 记录个数
	//};
	 定义结构体对象
	//struct fd_es_set esSet = { {0}, {NULL}, 0 };
	// 将socket装进数组中
	esSet.allsocks[esSet.count] = socket_Server;
	// 将对应的事件装进数组中
	esSet.allevents[esSet.count] = eventServer;
	// 记录个数++
	esSet.count++;
	// 循环等待事件 一直进行等待
	while (1)
	{
		// 6.4、等待事件 参数1:事件、socket 的个数 参数2:事件数组的首地址 参数3:直接填FALSE 参数4:等待时间(WSA_INFINITE 等待直到事件发生)  参数5:直接填FALSE
		DWORD retWaitEvn = WSAWaitForMultipleEvents(esSet.count, esSet.allevents, FALSE, WSA_INFINITE, FALSE);
		// 检验是否等到事件发生
		if (WSA_WAIT_FAILED == retWaitEvn)
		{
			// 没有等到事件的发生 进行继续等待
			continue;
		}
		// 获取发生事件所在的数组下标
		DWORD iIndex = retWaitEvn - WSA_WAIT_EVENT_0;
		// 获取下标之后的操作
		// 6.5、列举事件 获取事件类型,并将事件上的信号重置
		// 声明 WSAEnumNetworkEvents()函数 中参数3所需的结构体 
		/*
		typedef struct _WSANETWORKEVENTS {
			long lNetworkEvents;
			int  iErrorCode[FD_MAX_EVENTS];
		} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;
		*/
		WSANETWORKEVENTS NetworkEvents;
		int retEumEv = WSAEnumNetworkEvents(esSet.allsocks[iIndex], esSet.allevents[iIndex], &NetworkEvents);
		// 判断列举事件是否出错
		if (SOCKET_ERROR == retEumEv)
		{
			// 列举事件出错
			int error = WSAGetLastError();	// 通过WSAGetLastError 获取错误码
			printf("WSAEnumNetworkEvents() 列举事件出错,错误码为: %d\n", error);
			continue;
		}
		// 6.6、分类处理 FD_ACCEPT FD_WRITE FD_READ FD_CLOSE
		// 处理FD_ACCEPT
		if (NetworkEvents.lNetworkEvents & FD_ACCEPT)
		{
			if (0 == NetworkEvents.iErrorCode[FD_ACCEPT_BIT])
			{
				// accept进行链接 本质创建客户端socket
				SOCKET socketClient = accept(esSet.allsocks[iIndex], NULL, NULL);
				// 判断是否成功创建客户端socket
				if (INVALID_SOCKET == socketClient)
				{
					// 创建客户端socket失败
					int error = WSAGetLastError();	// 通过WSAGetLastError 获取错误码
					printf("处理FD_ACCEPT 创建客户端socket失败,错误码为: %d\n", error);
					continue;
				}
				// 创建对应的事件对象
				WSAEVENT eventClient = WSACreateEvent();
				// 检验是否成功创建事件对象
				if (WSA_INVALID_EVENT == eventClient)
				{
					// 创建对应的事件对象失败 				
					int error = WSAGetLastError();	// 通过WSAGetLastError 获取错误码
					printf("创建对应的(client)事件对象失败,错误码为: %d\n", error);
					// 关闭客户端socket
					closesocket(socketClient);
					continue;
				}
				// 将客户端socket和对应的事件对象绑定在一起并投递给系统
				int retEventSel = WSAEventSelect(socketClient, eventClient, FD_ACCEPT | FD_READ | FD_WRITE | FD_CLOSE);
				// 判断是否绑定并投递成功
				if (SOCKET_ERROR == retEventSel)
				{
					// 绑定并投递失败 				
					int error = WSAGetLastError();	// 通过WSAGetLastError 获取错误码
					printf("绑定并投递失败(client),错误码为: %d\n", error);
					// 关闭客户端socket
					closesocket(socketClient);
					// 关闭事件对象
					WSACloseEvent(eventClient);
					continue;
				}
				// 将客户端socket和对应的事件对象装进到数组中
				esSet.allsocks[esSet.count] = socketClient;
				esSet.allevents[esSet.count] = eventClient;
				esSet.count++;	// 注意个数要++
				// 可以打印提示一下成功处理 FD_ACCEPT
				printf("accept success\n");
			}
			else
			{
				// 处理FD_ACCEPT 出错 
				printf("处理FD_ACCEPT出错 错误码为%d\n", NetworkEvents.iErrorCode[FD_ACCEPT_BIT]);
				continue;
			}
		}
		// 处理FD_WRITE
		if (NetworkEvents.lNetworkEvents & FD_WRITE)
		{
			if (0 == NetworkEvents.iErrorCode[FD_WRITE_BIT])
			{
				// 进行 send
				if (SOCKET_ERROR == send(esSet.allsocks[iIndex], "connection ok", sizeof("connection ok"), 0))
				{
					// 发送数据失败
					int error = WSAGetLastError();	// 通过WSAGetLastError 获取错误码
					printf("处理FD_WRITE send 发送数据失败,错误码为: %d\n", error);
					continue;
				}
				printf("write ok\n");
			}
			else
			{
				// 处理FD_WRITE 出错
				printf("处理FD_WRITE出错 错误码为%d\n", NetworkEvents.iErrorCode[FD_WRITE_BIT]);
				continue;
			}
		}
		// 处理FD_READ
		if (NetworkEvents.lNetworkEvents & FD_READ)
		{
			if (0 == NetworkEvents.iErrorCode[FD_READ_BIT])
			{
				// recv 接收信息
				// 定义接收数据的缓存区
				char recvBuf[1024] = { 0 };
				int retRecv = recv(esSet.allsocks[iIndex], recvBuf, sizeof(recvBuf), 0);
				// 判断是否成功接收到信息
				if (SOCKET_ERROR == retRecv)
				{
					// 接收到信息
					int error = WSAGetLastError();	// 通过WSAGetLastError 获取错误码
					printf("处理FD_READ recv 接收到信息,错误码为: %d\n", error);
					continue;
				}
				printf("客户端say: %s\n", recvBuf);
			}
			else
			{
				// 处理FD_READ_BIT 出错 
				printf("处理FD_READ出错 错误码为%d\n", NetworkEvents.iErrorCode[FD_READ_BIT]);
				continue;
			}
		}
		// 处理FD_CLOSE
		if (NetworkEvents.lNetworkEvents & FD_CLOSE)
		{
			// 打印客户端下线了
			printf("客户端下线了!!!\n");
			// 出错了
			// 先关闭对应的socket 在 删除,删除的时候可以将数组最后的一个元素覆盖掉要删除的元素
			closesocket(esSet.allsocks[iIndex]);
			esSet.allsocks[iIndex] = esSet.allsocks[esSet.count - 1];
			// 先关闭对应的事件 在删除 同上
			WSACloseEvent(esSet.allevents[iIndex]);
			esSet.allevents[iIndex] = esSet.allevents[esSet.count - 1];
			// 注意socket、对应的事件个数要--
			esSet.count--;
		}
	}
}


int main()
{
	// 监视关闭窗口按钮
	SetConsoleCtrlHandler(fun, TRUE);

	// 1.打开网络库
	OpenWSAStarup();

	// 2.检验版本号
	CheckVersion();

	// 3.创建socket 服务端的socket
	SOCKET socketServer = createSocket();

	// 4.绑定地址与端口号
	Bind(socketServer);

	// 5.进行监听
	Listen(socketServer);

	// 6.事件选择模型核心代码
	EventSelect(socketServer);

	// 关闭套接字 和对应的事件
	for (int i = 0; i < esSet.count; i++)
	{
		closesocket(esSet.allsocks[i]);
		WSACloseEvent(esSet.allevents[i]);
	}
	
	// 清理网络库
	WSACleanup();
	system("pause");
	return 0;
}

客户端代码如下:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <Winsock.h>
#pragma comment(lib,"ws2_32.lib")

WSADATA WSAData;
// 1,打开网络库
void OpenWSAStarup()
{
	// 1,打开网络库
	WORD version = MAKEWORD(2, 2);	//设置要使用的库的版本 MAKEWORD(主版本,副版本); 
	int n = WSAStartup(version, &WSAData);
	// 检验是否成功打开网络库
	if (0 != n)
	{
		// 打开网络库失败,通过n 返回的错误码,给出提示
		switch (n)
		{
		case WSASYSNOTREADY:
			printf("重启电脑试试,或者检查ws_2_32库是否存在\n");
			break;
		case WSAVERNOTSUPPORTED:
			printf("当前库版本号不支持,尝试更换版本试试\n");
			break;
		case WSAEPROCLIM:
			printf("已达到对Windows套接字实现支持的任务数量的限制\n");
			break;
		case WSAEINPROGRESS:
			printf("正在阻止Windows Sockets 1.1操作,当前函数运行期间,因为某些原因造成阻塞\n");
			break;
		case WSAEFAULT:
			printf("lpWASData参数不是有效的指针,参数写错了\n");
			break;
		default:
			break;
		}
	}
}
// 2.检验版本
void CheckVersion()
{
	if (2 != HIBYTE(WSAData.wVersion) || 2 != LOBYTE(WSAData.wVersion))
	{
		// 打开版本号 2.2 失败
		printf("打开版本号 2.2 失败, 正在退出程序...\n");
		// 关闭网络库
		WSACleanup();
		// 退出程序
		return;
	}
}
// 3.创建socket
SOCKET CreateSocket()
{
	// 3.创建套接字 socket 创建服务端的socket
	SOCKET socketCreate = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	// 检验是否成功创建套接字
	if (INVALID_SOCKET == socketCreate)
	{
		// 创建套接字失败
		// 获取错误码
		int error = WSAGetLastError();
		printf("创建套接字失败,返回错误号为:%d\n", error);
		// 关闭网络库
		WSACleanup();
		// 退出程序
		return 0;
	}
	return socketCreate;
}
// 4.连接服务器
void Connection(SOCKET socket_)
{
	// 4.连接到服务器
	// 创建connect() 函数中参数2所需的结构体 用来装IP地址和端口号
	sockaddr_in client_s;
	//给结构体成员赋值
	client_s.sin_family = AF_INET;		//IP地址类型
	client_s.sin_port = htons(12345);	//端口号
	client_s.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	int nCon = connect(socket_, (struct sockaddr*)&client_s, sizeof(client_s));
	// 检验是否成功连接到服务端
	if (SOCKET_ERROR == nCon)
	{
		// 连接服务端失败
		// 获取错误码
		int error = WSAGetLastError();
		printf("连接服务端失败,返回错误号为:%d\n", error);
		// 关闭套接字
		closesocket(socket_);
		// 关闭网络库
		WSACleanup();
		// 退出程序
		return;
	}
}

// 5.与服务端发送信息
void RecvAndSend(SOCKET socket_)
{
	// 只收一次 信息
	// 定义接收服务端信息的缓存区
	char recvBuf[1024] = { 0 };
	int res = recv(socket_, recvBuf, 1024, 0);
	// 判断是否成功接收到信息
	if (0 == res)
	{
		printf("服务端下线了\n");
		return;
	}
	else if (SOCKET_ERROR == res)
	{
		// 获取错误码
		int error = WSAGetLastError();
		printf("连接服务端失败,返回错误号为:%d", error);
		printf("\n");
		return;
	}
	else
	{
		// 打印信息
		printf("服务端say: %s\n", recvBuf);
	}
	printf("我是客户端,正在发送信息,请稍等...\n");

	// 5.与服务端进行收发信息
	while (1)
	{
		// 发信息
		char sendBuf[1024] = { 0 };
		scanf_s("%s", sendBuf, 1024);
		// 设置输入 0 时自动退出
		if ('0' == sendBuf[0])
		{
			break;	//退出循环
		}
		send(socket_, sendBuf, 1024, 0);
	}
}

int main()
{
	// 1,打开网络库
	OpenWSAStarup();
	// 2.校验版本
	CheckVersion();
	// 3.创建socket
	SOCKET socketServer = CreateSocket();
	// 4.连接服务器
	Connection(socketServer);
	// 5.与服务端发送信息
	RecvAndSend(socketServer);

	// 关闭套接字
	closesocket(socketServer);
	// 关闭网络库
	WSACleanup();
	system("pause");
	return 0;
}

程序运行结果:

  网络协议 最新文章
使用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:25 
 
开发: 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 2:33:57-

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