TCP网络事件模型的封装2.0

TCP网络事件模型的封装2.0

最近学习了TCP网络模型的封装,其中运用的封装技术个人感觉有点绕

在反复读代码、做思维导图下初步理解了这套封装模型,不禁感叹原来代码还能这样写?神奇!

为此将源码分享出来并将流程图画出,方便理解和复习

PS:下列思维导图仅代表个人理解,如有误恳请指出纠正

2023/03/30:在原有事件模型基础上加入了发送模块,并将发送接受设置为了非阻塞状态,这样可避免出现客户端恶意拒收的情况,俗称黑客攻击

模型继承关系图

要理解该模型,我认为首先要画图理解类的继承关系,下面是我画的一个简单关系图

在这里插入图片描述

模型流程图

下面是我画的该模型的大致流程图,仅供参考,有误恳请指出

在这里插入图片描述

具体代码实现

EventLoop.hpp

#ifndef _EVENTLOOP_H_
#define _EVENTLOOP_H_

#include <list>
#include <WinSock2.h>


enum class E_Event_Type
{
	Recv,
	Send,
};

class IEventCallback
{
public:
	virtual void OnNetEvent(E_Event_Type e) = 0;
	virtual void OnClose() = 0;

	virtual bool NeedWrite() = 0;
};


struct sSelectEvent
{
	SOCKET sock;
	IEventCallback* event;
};


class EventLoop
{
private:
	std::list<sSelectEvent*> _events;
	std::list<sSelectEvent*> _delEventCaches;

public:
	void LoopOnce();

	void AddEvent(sSelectEvent* e)
	{
		_events.push_back(e);
	}

	void DelEvent(sSelectEvent* e);
};
#endif

EventLoop.cpp

#include "EventLoop.hpp"

void EventLoop::LoopOnce()
{

	fd_set reads;
	fd_set writes;
	FD_ZERO(&reads);
	FD_ZERO(&writes);

	do
	{
		auto begin = _events.begin();
		auto end = _events.end();
		for (; begin != end; ++begin)
		{
			FD_SET((*begin)->sock, &reads);
			if ((*begin)->event->NeedWrite())
			{
				FD_SET((*begin)->sock, &writes);

			}
		}
	} while (false);

	int nSeclect = select(0, &reads, &writes, nullptr, nullptr);

	if (0 == nSeclect)return;

	if (nSeclect < 0)
		return;

	do
	{
		auto begin = _events.begin();
		auto end = _events.end();
		for (; begin != end; ++begin)
		{
			if (FD_ISSET((*begin)->sock, &reads))
			{
				(*begin)->event->OnNetEvent(E_Event_Type::Recv);
			}
		}
	} while (false);
	do
	{
		auto begin = _delEventCaches.begin();
		auto end = _delEventCaches.end();
		for (; begin != end; ++begin)
		{
			sSelectEvent* p = *begin;
			_events.remove_if([p](sSelectEvent* a)
				{
					return a == p;
				});
			p->event->OnClose();
		}
		_delEventCaches.clear();
	} while (false);

	do
	{
		auto begin = _events.begin();
		auto end = _events.end();
		for (; begin != end; ++begin)
		{
			if (FD_ISSET((*begin)->sock, &writes))
			{
				(*begin)->event->OnNetEvent(E_Event_Type::Send);
			}
		}
	} while (false);
}

void EventLoop::DelEvent(sSelectEvent* e)
{
	_delEventCaches.push_back(e);
}

TcpListen.hpp

#ifndef _TCPLISTEN_H_
#define _TCPLISTEN_H_
#define _WINSOCK_DEPRECATED_NO_WARNINGS 

#include "EventLoop.hpp"
#include <WinSock2.h>
#include <iostream>
#include <functional>
class TcpSocket;
class TcpListen :public IEventCallback
{
protected:
	SOCKET _sock;
	sSelectEvent _event;
	EventLoop* _loop;
	std::function<TcpSocket* ()> _sockCB;
public:
	TcpListen();

	void Init(EventLoop* loop, std::function<TcpSocket* ()> sockCB)
	{
		_loop = loop; _sockCB = sockCB;
	}

	bool Listen(unsigned short port, const char* const ip = "0.0.0.0");

	// 通过 IEventCallback 继承
	virtual void OnNetEvent(E_Event_Type e) override;

	virtual void OnClose() override;

	virtual bool NeedWrite() override { return false; }
public:
	virtual void OnAccpet(TcpSocket* sock) = 0;
};
#endif

TcpListen.cpp

#include "TcpListen.h"
#include "TcpSocket.h"
TcpListen::TcpListen()
{
	_event.event = this;
	_sock = _event.sock = INVALID_SOCKET;
	_loop = nullptr;
}

bool TcpListen::Listen(unsigned short port, const char* const ip)
{

	_sock = socket(AF_INET, SOCK_STREAM, 0);

	if (INVALID_SOCKET == _sock)
	{
		std::cout << "create SOCKET fail!\n" << std::endl;
		return false;
	}
	std::cout << "1.create SOCKET OK!\n" << std::endl;

	// 2.绑定IP和端口
//  bind(SOCKET,绑定的IP端口结构体,结构体大小)
	SOCKADDR_IN serverAddr;
	serverAddr.sin_family = AF_INET;
	//SOCKADDR_IN6*

	serverAddr.sin_port = htons(port);
	// 127.0.0.1 本机回环地址
	// 0.0.0.0  绑定所有IP
	serverAddr.sin_addr.s_addr = inet_addr(ip);

	//SOCKADDR_IN6 serverAddr6;
	if (SOCKET_ERROR
		== bind(_sock, (SOCKADDR*)&serverAddr, sizeof(serverAddr)))
	{
		std::cout << "bind SOCKET fail!\n" << std::endl;
		return false;
	}
	std::cout << "2.bind SOCKET OK!\n" << std::endl;

	listen(_sock, 5);
	std::cout << "3.listen SOCKET OK!\n" << std::endl;

	_event.sock = _sock;
	_loop->AddEvent(&_event);

	return true;
}

void TcpListen::OnNetEvent(E_Event_Type e)
{
	SOCKADDR_IN clientAddr;
	int addrlen = sizeof(SOCKADDR_IN);
	SOCKET clientSock = accept(_sock, (SOCKADDR*)&clientAddr, &addrlen);

	if (INVALID_SOCKET == clientSock)
	{
		std::cout << "4.ACCEPT ERROR!!\n" << std::endl;
		return;
	}
	std::cout << "4.ACCEPT ip:" << inet_ntoa(clientAddr.sin_addr)
		<< "  Port:" << ntohs(clientAddr.sin_port) << "   " << clientSock
		<< std::endl;
	TcpSocket* sock = _sockCB();

	sock->OnAccept(clientSock, &clientAddr);

	OnAccpet(sock);

}

void TcpListen::OnClose()
{
}

TcpSocket.hpp

#ifndef _TCPSOCKET_H_
#define _TCPSOCKET_H_
#define _WINSOCK_DEPRECATED_NO_WARNINGS 

#include "EventLoop.hpp"

const unsigned int RECV_MAX_BUF = 4096 * 2;

class TcpSocket :public IEventCallback
{
private:
	SOCKET _sock;
	SOCKADDR_IN _addr;
	sSelectEvent _event;
	EventLoop* _loop;

	char _recvBuff[RECV_MAX_BUF];
	int _recvLen;

	bool _bClose;

	struct sPack
	{
		char* msg;
		int len;
		int size;
		sPack(const char* const msg, int len)
		{
			this->size = len;
			this->len = 0;
			this->msg = (char*)malloc(len);
			memcpy(this->msg, msg, len);
		}

		~sPack()
		{
			free(this->msg);
		}
	};
	std::list<sPack*> _sends;
public:
	TcpSocket();

	virtual ~TcpSocket();

	void Init(EventLoop* loop);

	void OnAccept(SOCKET sock, SOCKADDR_IN* addr);

	void Close();

	// TODO 连接服务端

public:
	// 通过 IEventCallback 继承
	virtual void OnNetEvent(E_Event_Type e) override;

	virtual void OnClose() override;

public:

	virtual int OnNetMsg(const char* const msg, int msgLen) = 0;

	virtual void Disconnect() = 0;

	virtual bool NeedWrite() override;

	void Send(const char* msg, int msgLen);


};

#endif

TcpSocket.cpp

#include "TcpSocket.h"

TcpSocket::TcpSocket()
{
	_sock = _event.sock = INVALID_SOCKET;
	_event.event = this;
	_recvLen = 0;
	_bClose = false;
}

TcpSocket::~TcpSocket()
{

}

void TcpSocket::Init(EventLoop* loop)
{
	unsigned long ul = 1;
	int ret = ioctlsocket(_sock, FIONBIO, (unsigned long*)&ul);
    //设置成非阻塞模式。 
	_loop = loop;
	_loop->AddEvent(&_event);

}

void TcpSocket::OnAccept(SOCKET sock, SOCKADDR_IN* addr)
{
	_sock = sock;
	_event.sock = sock;
	memcpy(&_addr, addr, sizeof(addr));


}

void TcpSocket::Close()
{
	if (_bClose) return;

	_bClose = true;
	if (_loop != NULL)
		_loop->DelEvent(&_event);
	else
		OnClose();
}

void TcpSocket::OnNetEvent(E_Event_Type e)
{
	if (E_Event_Type::Recv == e)
	{
		int nRecv = recv(_sock, _recvBuff + _recvLen, RECV_MAX_BUF - _recvLen, 0);

		if (nRecv <= 0)
		{
			Close();
			return;
		}
		_recvLen += nRecv;


		while (_recvLen != 0)
		{
			int nRet = OnNetMsg(_recvBuff, _recvLen);
			if (nRet <= 0)break;
			_recvLen -= nRet;
			for (int i = 0; i < _recvLen; ++i)
			{
				_recvBuff[i] = _recvBuff[i + nRet];
			}
		}
		if (_recvLen == RECV_MAX_BUF) Close();

	}


	if (E_Event_Type::Send == e)
	{
		sPack* p = _sends.front();

		int nRet = send(_sock, p->msg + p->len, p->size - p->len, 0);
		if (nRet <= 0)
		{
			Close();
			return;
		}
		p->len += nRet;
		if (p->len == p->size)
		{
			_sends.pop_front();
			delete p;
		}
	}
}

void TcpSocket::OnClose()
{
	do
	{
		auto begin = _sends.begin();
		auto end = _sends.end();
		for (; begin != end; ++begin)
		{
			delete (*begin);
		}
		_sends.clear();
	} while (false);

	Disconnect();
	closesocket(_sock);
	delete this;
}

bool TcpSocket::NeedWrite()
{
	return !_sends.empty();
}

void TcpSocket::Send(const char* msg, int msgLen)
{
	//send(_sock, msg, msgLen, 0);

	sPack* p = new sPack(msg, msgLen);
	_sends.push_back(p);
}

EasyTcpServer.cpp

#define  _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>

#include <WinSock2.h>
#pragma comment(lib,"ws2_32")
#include <vector>

#include "TcpListen.h"
#include "TcpSocket.h"

class EasyTcpClient :public TcpSocket
{
public:
	// 通过 TcpSocket 继承
	virtual int OnNetMsg(const char* const msg, int msgLen) override
	{
		//std::cout << msgLen << "接受客户端数据:" << msg << std::endl;
		Send(msg, msgLen);

		return msgLen;
	}

	// 通过 TcpSocket 继承
	virtual void Disconnect() override
	{
		std::cout << "断开连接" << std::endl;
	}
};


class EasyTcpServer :public TcpListen
{
public:

	void OnAccpet(TcpSocket* sock) override
	{
		//sock->Init(_loop)
		sock->Init(_loop);
		std::cout << "客户端连接" << std::endl;
	}

};


int main()
{
	// 加载网络环境
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	EventLoop loop;

	EasyTcpServer listen;

	listen.Init(&loop, []()->TcpSocket*
		{
			return new EasyTcpClient;
		});

	listen.Listen(7890);

	while (true)
	{
		loop.LoopOnce();
	}

	return 0;
}

最后

{
	std::cout << "断开连接" << std::endl;
}

};

class EasyTcpServer :public TcpListen
{
public:

void OnAccpet(TcpSocket* sock) override
{
	//sock->Init(_loop)
	sock->Init(_loop);
	std::cout << "客户端连接" << std::endl;
}

};

int main()
{
// 加载网络环境
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);

EventLoop loop;

EasyTcpServer listen;

listen.Init(&loop, []()->TcpSocket*
	{
		return new EasyTcpClient;
	});

listen.Listen(7890);

while (true)
{
	loop.LoopOnce();
}

return 0;

}




# 最后

代码经供参考,如有疑问或者代码有问题欢迎提出

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/4537.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FITC-PEG-SH,荧光素-聚乙二醇-巯基的用途:用于修饰氨基酸,蛋白质等

FITC-PEG-SH 荧光素聚乙二醇巯基 英文名称&#xff1a;Fluorescein (polyethylene glycol)Thiol 中文名称&#xff1a;荧光素聚乙二醇巯基 外观: 黄色液体、半固体或固体&#xff0c;取决于分子量。 溶剂&#xff1a;溶于水等其他常规性有机溶剂 激光/发射波长&#xff1a…

ChatGPT使用案例之自然语言处理

ChatGPT使用案例之自然语言处理 自然语言处理被誉为“人工智能皇冠上的明珠”&#xff0c;这句话就已经说明了自然语言处理在整个人工智能体系中的重要性&#xff0c;自然语言处理&#xff08;Natural Language Processing&#xff0c;NLP&#xff09;是一种涉及计算机和人类自…

联想小新 青春版-14笔记本电脑重装系统教程

在使用笔记本电脑的过程中&#xff0c;我们难免会遇到一些问题&#xff0c;比如系统崩溃、病毒感染等等。这时候&#xff0c;我们就需要重装系统来解决这些问题。而联想小新 青春版-14笔记本电脑的系统重装方法&#xff0c;就是我们需要掌握的技能之一。本文将详细介绍如何重装…

python怎么自学

其实0基础选择python学习入行的不在少数&#xff0c;Python近段时间一直涨势迅猛&#xff0c;在各大编程排行榜中崭露头角&#xff0c;得益于它多功能性和简单易上手的特性&#xff0c;让它可以在很多不同的工作中发挥重大作用。 正因如此&#xff0c;目前几乎所有大中型互联网…

element-plus官网访问太慢 下载文档到本地部署 实现快速查阅

我只是吐槽下 element基于githup pages这个部署文档地址 本来访问就慢,然后吧这个文档看的人还很多,导致更慢了 经常卡半天才出来文档地址 文档地址: https://github.com/element-plus/element-plus/tree/gh-pages 文档的地址(你直接下载下来 想跑起来的话可能需要更改文档的路…

【C++】IO流 + 空间配置器(了解)

文章目录&#x1f4d6; 前言1. IO流1.1 C语言的输入和输出&#xff1a;1.2 流的概念及特性&#xff1a;1.3 自定义类型隐式类型转换&#xff1a;1.4 在线OJ中的输入和输出&#xff1a;1.5 CIO流对文件的操作&#xff1a;1.6 stringstream介绍&#xff1a;2. 空间配置器2.1 什么…

安科瑞智能照明控制系统在工厂的应用

安科瑞 安科瑞 李亚娜 1&#xff5c;概述 安科瑞智能照明控制解决方案ALIBUS&#xff08;Acrel Lighting intelligent Bus&#xff09;基于成熟的RS485通讯控制技术&#xff0c;同时创新地引入了载波侦听和冲突碰撞检测机制&#xff0c;多机间实现了实时双向通讯&#xff0c;线…

Java设计模式-9 、策略模式

策略模式 策略模式&#xff08;Strategy Pattern&#xff09;属于对象的⾏为模式。其⽤意是针对⼀组算 法&#xff0c;将每⼀个算法封装到具有共同接⼝的独⽴的类中&#xff0c;从⽽使得它们可以 相互替换。策略模式使得算法可以在不影响到客户端的情况下发⽣变化。 其主要⽬的…

基于Python长时间序列遥感数据处理及在全球变化、物候提取、植被变绿与固碳分析、生物量估算与趋势分析等领域中的应用

植被是陆地生态系统中最重要的组分之一&#xff0c;也是对气候变化最敏感的组分&#xff0c;其在全球变化过程中起着重要作用&#xff0c;能够指示自然环境中的大气、水、土壤等成分的变化&#xff0c;其年际和季节性变化可以作为地球气候变化的重要指标。此外&#xff0c;由于…

手敲Mybatis(八)-参数处理器

前言在之前的章节里边&#xff0c;类PreparedStatementHandler我们还没有处理在执行Sql时的参数&#xff0c;目前是硬编码写死存储的&#xff0c;如&#xff1a;ps.setLong()&#xff0c;这里就只能处理long类型的数据因为写死了&#xff0c;我们需要处理下让它支持设置不同的数…

【Linux 网络编程4】网络层--UDP/TCP协议,3次握手4次挥手、粘包问题等

netstat命令-n.拒绝显示别名&#xff0c;能显示数字的全部转化成数字(IPPORT)-l 仅列出有在 Listen (监听) 的服务的状态-p 显示建立相关链接的程序名&#xff08;pid&#xff09;-t 仅显示tcp相关选项-u 仅显示udp相关选项 2.UDP协议2.1.全双工和半双工的区别全双工&#xff1…

了解Session、LocatStorage、Cache-Control、ETag区别

一、cookie与session有什么区别&#xff1f; 1. 由于HTTP协议是无状态的协议&#xff0c;所以服务端需要记录用户的状态时&#xff0c;就需要用某种机制来识具体的用户&#xff0c;这个机制就是Session.典型的场景比如购物车&#xff0c;当你点击下单按钮时&#xff0c;由于HT…

SpringBoot学习笔记(4)-分析 SpringBoot 底层机制

文章目录4. 分析 SpringBoot 底层机制4.1 Tomcat启动分析4.2 创建Spring 容器4.3 将Tomcat 和 Spring 容器关联&#xff0c;并启动 Spring 容器4.4 扩展-debug查看 ac.refresh()4. 分析 SpringBoot 底层机制 【Tomcat 启动分析 Spring 容器初始化Tomcat 如何关联 Spring 容器…

微软分享修复WinRE BitLocker绕过漏洞的脚本

微软发布了一个脚本&#xff0c;可以更轻松地修补 Windows 恢复环境 (WinRE) 中的 BitLocker 绕过安全漏洞。 此 PowerShell 脚本 (KB5025175) 简化了保护 WinRE 映像以防止试图利用CVE-2022-41099漏洞的过程&#xff0c;该漏洞使攻击者能够绕过 BitLocker 设备加密功能系统存…

jvm03垃圾回收篇

p134 垃圾回收相关章节的说明 p135 什么是GC 为什么需要GC P136 了解早起垃圾回收行为 p137 java自动内存管理介绍 p138垃圾回收相关算法概述 p139引用计数算法的原理及优缺点 p140 python引用计数实施方案 p141 可达性分析算法与GC ROOTS p142 对象的finalization机制 p143 代…

【MyBatis】字段名和属性名不同时,如何处理

目录 前言 1、返回类型&#xff1a;resultType 2、返回字典映射&#xff1a;resultMap 2.1、字段名和属性名不同怎么处理 解决方案一&#xff1a;使用resultMap 解决方案二&#xff1a;使用as起别名 3、多表查询 总结&#xff1a; 前言 在之前的文章中&#xff0c;我们可…

TXT 和 SEV技术小知识

1.Intel TXT 可信执行技术(Trusted Execute Technology&#xff0c;TXT)是Intel公司的可信计算技术&#xff0c;主要通过改造芯片组和CPU&#xff0c;增加安全特性&#xff0c;通过结合一个基于硬件的安全设备—可信平台模块(Trusted Platform Module&#xff0c;TPM)&#xf…

蓝桥杯C/C++VIP试题每日一练之Sine之舞

💛作者主页:静Yu 🧡简介:CSDN全栈优质创作者、华为云享专家、阿里云社区博客专家,前端知识交流社区创建者 💛社区地址:前端知识交流社区 🧡博主的个人博客:静Yu的个人博客 🧡博主的个人笔记本:前端面试题 个人笔记本只记录前端领域的面试题目,项目总结,面试技…

Python 之列表推导式

文章目录参考描述列表推导式举个栗子基本形式一般式基本形式&#xff08;高阶&#xff09;判断使用逻辑运算符笛卡尔积拆解变量污染列表推导式参考 项目描述流畅的 PythonLuciano Ramalho 著 / 安道 吴珂 译搜索引擎Bing 描述 项目描述Python3.10.6 列表推导式 列表推导式是…

python---函数的进阶

函数的进阶 1.8函数的进阶 1.8.1函数作为参数进行传入 1.简介&#xff1a;函数作为范围进行传递到函数中进行操作 2.函数作为参数传入到函数中 3.函数调用和逻辑传入之间的区别 一个是作为数据进行传入&#xff0c;但是调用的函数时一定的一个作为逻辑进行调用&#xff0c;但是…