Ainx的消息封装

在这里插入图片描述

📕作者简介: 过去日记,致力于Java、GoLang,Rust等多种编程语言,热爱技术,喜欢游戏的博主。
📗本文收录于Ainx系列,大家有兴趣的可以看一看
📘相关专栏Rust初阶教程、go语言基础系列、spring教程等,大家有兴趣的可以看一看
📙Java并发编程系列,设计模式系列、go web开发框架 系列正在发展中,喜欢Java,GoLang,Rust,的朋友们可以关注一下哦!


📙 本文大部分都是借鉴刘丹冰大佬的zinx框架和文章,更推荐大家去读大佬的原文,本文只是个人学习的记录

文章目录

  • Ainx的消息封装
    • 创建消息封装类型
    • 消息的封包与拆包
      • 创建拆包封包抽象类
      • 实现拆包封包类
    • Ainx-V0.5代码实现
      • Request字段修改
      • 集成拆包过程
      • 提供封包方法
    • 使用Ainx-V0.5完成应用程序

Ainx的消息封装

接下来我们再对Ainx做一个简单的升级,现在我们把服务器的全部数据都放在一个Request里,当前的Request结构如下:

type Request struct {conn ziface.IConnection //已经和客户端建立好的链接data []byte             //客户端请求的数据
}

很明显,现在是用一个[]byte来接受全部数据,又没有长度,又没有消息类型,这不科学。怎么办呢?我们现在就要自定义一种消息类型,把全部的消息都放在这种消息类型里。

创建消息封装类型

在ainx/ainterface/下创建imessage.go文件

ainx/ainterface/imessage.go

package ainterface/*
将请求的一个消息封装到message中,定义抽象层接口
*/
type IMessage interface {GetDataLen() uint32 //获取消息数据段长度GetMsgId() uint32   //获取消息IDGetData() []byte    //获取消息内容SetMsgId(uint32)   //设计消息IDSetData([]byte)    //设计消息内容SetDataLen(uint32) //设置消息数据段长度
}

同时创建实例message类,在ainx/anet/下,创建message.go文件

ainx/anet/message.go

package anettype Message struct {Id      uint32 //消息的IDDataLen uint32 //消息的长度Data    []byte //消息的内容
}// 创建一个Message消息包
func NewMsgPackage(id uint32, data []byte) *Message {return &Message{Id:      id,DataLen: uint32(len(data)),Data:    data,}
}// 获取消息数据段长度
func (msg *Message) GetDataLen() uint32 {return msg.DataLen
}// 获取消息ID
func (msg *Message) GetMsgId() uint32 {return msg.Id
}// 获取消息内容
func (msg *Message) GetData() []byte {return msg.Data
}// 设置消息数据段长度
func (msg *Message) SetDataLen(len uint32) {msg.DataLen = len
}// 设计消息ID
func (msg *Message) SetMsgId(msgId uint32) {msg.Id = msgId
}// 设计消息内容
func (msg *Message) SetData(data []byte) {msg.Data = data
}

整理一个基本的message包,会包含消息ID,数据,数据长度三个成员,提供基本的setter和getter方法,目的是为了以后做封装优化的作用。同时也提供了一个创建一个message包的初始化方法NewMegPackage。

消息的封包与拆包

我们这里就是采用经典的TLV(Type-Len-Value)封包格式来解决TCP粘包问题吧。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
由于Ainx也是TCP流的形式传播数据,难免会出现消息1和消息2一同发送,那么zinx就需要有能力区分两个消息的边界,所以Ainx此时应该提供一个统一的拆包和封包的方法。在发包之前打包成如上图这种格式的有head和body的两部分的包,在收到数据的时候分两次进行读取,先读取固定长度的head部分,得到后续Data的长度,再根据DataLen读取之后的body。这样就能够解决粘包的问题了。

创建拆包封包抽象类

在ainx/ainterface下,创建idatapack.go文件
ainterface

ainx/ainterface/idatapack.go

package ainterface/*
封包数据和拆包数据
直接面向TCP链接中的数据流,为传输数据添加头部信息,用于处理TCP粘包问题。
*/
type IDataPack interface {GetHeadLen() uint32                //获取包头长度方法Pack(msg IMessage) ([]byte, error) //封包方法Unpack([]byte) (IMessage, error)   //拆包方法
}

实现拆包封包类

在ainx/anet/下,创建datapack.go文件.

ainx/anet/datapack.go

package anetimport ("ainx/ainterface""ainx/utils""bytes""encoding/binary""errors"
)// 封包拆包实例,暂时不需要成员
type DataPack struct {
}// 封包拆包实例初始化方法
func NewDataPack() *DataPack {return &DataPack{}
}// 获取包头长度方法
func (dp *DataPack) GetHeadLen() uint32 {//Id uint32(4字节) +  DataLen uint32(4字节)return 8
}// 封包方法(压缩)
func (dp *DataPack) Pack(msg ainterface.IMessage) ([]byte, error) {// 创建一个存放bytes字节的缓冲dataBuff := bytes.NewBuffer([]byte{})写dataLen//字节序 就是多字节数据类型 (int, float 等)在内存中的存储顺序。可分为大端序,低地址端存放高位字节;小端序与之相反,低地址端存放低位字节。if err := binary.Write(dataBuff, binary.LittleEndian, msg.GetDataLen()); err != nil {return nil, err}//写msgIDif err := binary.Write(dataBuff, binary.LittleEndian, msg.GetMsgId()); err != nil {return nil, err}//写data数据if err := binary.Write(dataBuff, binary.LittleEndian, msg.GetData()); err != nil {return nil, err}return dataBuff.Bytes(), nil
}// 拆包方法(解压数据)
func (dp *DataPack) Unpack(binaryData []byte) (ainterface.IMessage, error) {//创建一个从输入二进制数据的ioReaderdataBuff := bytes.NewReader(binaryData)//只解压head的信息,得到dataLen和msgIDmsg := &Message{}//读dataLenif err := binary.Read(dataBuff, binary.LittleEndian, &msg.DataLen); err != nil {return nil, err}//读msgIDif err := binary.Read(dataBuff, binary.LittleEndian, &msg.Id); err != nil {return nil, err}//判断dataLen的长度是否超出我们允许的最大包长度if utils.GlobalSetting.MaxPacketSize > 0 && msg.DataLen > utils.GlobalSetting.MaxPacketSize {return nil, errors.New("Too large msg data recieved")}//这里只需要把head的数据拆包出来就可以了,然后再通过head的长度,再从conn读取一次数据return msg, nil
}

Ainx-V0.5代码实现

现在我们需要把封包和拆包的功能集成到Zinx中,并且测试Zinx该功能是否生效。

Request字段修改

首先我们要将我们之前的Request中的[]byte类型的data字段改成Message类型.

ainx/anet/request.go

package anetimport "ainx/ainterface"type Request struct {conn ainterface.IConnection //已经和客户端建立好的链接msg  ainterface.IMessage    //客户端请求数据
}// 获取请求链接信息
func (r *Request) GetConnection() ainterface.IConnection {return r.conn
}// 获取请求消息的数据
func (r *Request) GetData() []byte {return r.msg.GetData()
}// 获取请求的消息的ID
func (r *Request) GetMsgID() uint32 {return r.msg.GetMsgId()
}

集成拆包过程

接下来我们需要在Connection的StartReader()方法中,修改之前的读取客户端的这段代码:

func (c *Connection) StartReader() {//...for  {//读取我们最大的数据到buf中buf := make([]byte, utils.GlobalSetting.MaxPacketSize)_, err := c.Conn.Read(buf)if err != nil {fmt.Println("recv buf err ", err)c.ExitBuffChan <- truecontinue}//...}
}

改成如下:

ainx/anet/connection.go

StartReader()方法

// 处理conn读数据的Goroutine
func (c *Connection) StartReader() {fmt.Println("Reader Goroutine is  running")defer fmt.Println(c.RemoteAddr().String(), " conn reader exit!")defer c.Stop()for {// 创建拆包解包的对象dp := NewDataPack()//读取客户端的Msg headheadData := make([]byte, dp.GetHeadLen())if _, err := io.ReadFull(c.GetTCPConnection(), headData); err != nil {fmt.Println("read msg head error ", err)c.ExitBuffChan <- truecontinue}//拆包,得到msgid 和 datalen 放在msg中msg, err := dp.Unpack(headData)if err != nil {fmt.Println("unpack error ", err)c.ExitBuffChan <- truecontinue}//根据 dataLen 读取 data,放在msg.Data中var data []byteif msg.GetDataLen() > 0 {data = make([]byte, msg.GetDataLen())if _, err := io.ReadFull(c.GetTCPConnection(), data); err != nil {fmt.Println("read msg data error ", err)c.ExitBuffChan <- truecontinue}}msg.SetData(data)//得到当前客户端请求的Request数据req := Request{conn: c,msg:  msg, //将之前的buf 改成 msg}//从路由Routers 中找到注册绑定Conn的对应Handlego func(request ainterface.IRequest) {//执行注册的路由方法c.Router.PreHandle(request)c.Router.Handle(request)c.Router.PostHandle(request)}(&req)}
}

提供封包方法

现在我们已经将拆包的功能集成到Ainx中了,但是使用Ainx的时候,如果我们希望给用户返回一个TLV格式的数据,总不能每次都经过这么繁琐的过程,所以我们应该给Ainx提供一个封包的接口,供Ainx发包使用。

ainx/ainterface/iconnection.go

新增SendMsg()方法


type IConnection interface {// 启动连接,让当前连接开始工作Start()// 停止链接,结束当前连接状态Stop()//从当前连接获取原始的socket TCPConn GetTCPConnection() *net.TCPConn //获取当前连接IDGetConnID() uint32       //获取远程客户端地址信息 RemoteAddr() net.AddrGetConnection() net.Conn //  (从当前连接获取原始的socket TCPConn)//直接将Message数据发送数据给远程的TCP客户端SendMsg(msgId uint32, data []byte) error
}// 定义⼀一个统⼀一处理理链接业务的接⼝口
type HandFunc func(*net.TCPConn, []byte, int) error

ainx/anet/connection.go

SendMsg()方法实现:

// 直接将Message数据发送数据给远程的TCP客户端
func (c *Connection) SendMsg(msgId uint32, data []byte) error {if c.isClosed == true {return errors.New("Connection closed when send msg")}//将data封包,并且发送dp := NewDataPack()msg, err := dp.Pack(NewMsgPackage(msgId, data))if err != nil {fmt.Println("Pack error msg id = ", msgId)return errors.New("Pack error msg ")}//写回客户端if _, err := c.Conn.Write(msg); err != nil {fmt.Println("Write msg id ", msgId, " error ")c.ExitBuffChan <- truereturn errors.New("conn Write error")}return nil
}

使用Ainx-V0.5完成应用程序

现在我们可以基于Ainx框架完成发送msg功能的测试用例了。

Server.go

package mainimport ("ainx/ainterface""ainx/anet""fmt"
)// ping test 自定义路由
type PingRouter struct {anet.BaseRouter
}// Test Handle
func (this *PingRouter) Handle(request ainterface.IRequest) {fmt.Println("Call PingRouter Handle")//先读取客户端的数据,再回写ping...ping...pingfmt.Println("recv from client : msgId=", request.GetMsgID, ", data=", string(request.GetData()))//回写数据err := request.GetConnection().SendMsg(1, []byte("ping...ping...ping"))if err != nil {fmt.Println(err)}
}func main() {//创建一个server句柄s := anet.NewServer("Ainx V0.5")//配置路由s.AddRouter(&PingRouter{})//开启服务s.Serve()
}

Client.go

package mainimport ("ainx/anet""fmt""io""net""time"
)/*
模拟客户端
*/
func main() {fmt.Println("Client Test ... start")//3秒之后发起测试请求,给服务端开启服务的机会time.Sleep(3 * time.Second)conn, err := net.Dial("tcp", "127.0.0.1:8080")if err != nil {fmt.Println("client start err, exit!")return}for {//发封包message消息dp := anet.NewDataPack()msg, _ := dp.Pack(anet.NewMsgPackage(0, []byte("Ainx V0.5 Client Test Message")))_, err := conn.Write(msg)if err != nil {fmt.Println("write error err ", err)return}//先读出流中的head部分headData := make([]byte, dp.GetHeadLen())_, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止if err != nil {fmt.Println("read head error")break}//将headData字节流 拆包到msg中msgHead, err := dp.Unpack(headData)if err != nil {fmt.Println("server unpack err:", err)return}if msgHead.GetDataLen() > 0 {//msg 是有data数据的,需要再次读取data数据msg := msgHead.(*anet.Message)msg.Data = make([]byte, msg.GetDataLen())//根据dataLen从io中读取字节流_, err := io.ReadFull(conn, msg.Data)if err != nil {fmt.Println("server unpack data err:", err)return}fmt.Println("==> Recv Msg: ID=", msg.Id, ", len=", msg.DataLen, ", data=", string(msg.Data))}time.Sleep(1 * time.Second)}
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

186基于matlab的信号盲源分离算法

基于matlab的信号盲源分离算法&#xff0c;包括变步长盲源分离&#xff08;EASI&#xff09;,RLS(自然梯度和普通梯度)&#xff0c;并将三种方法分离结果进行对比。程序已调通&#xff0c;可直接运行。 186 信号盲源分离算法 变步长盲源分离 (xiaohongshu.com)

智能革新:2024年AI辅助研发的挑战、机遇与未来展望

引言 在进入2024年的门槛时&#xff0c;我们站在了一个科技飞速发展的新纪元&#xff0c;其中&#xff0c;人工智能&#xff08;AI&#xff09;的持续进步和应用扩展无疑是推动这一变革的强大动力。AI辅助研发&#xff0c;作为将人工智能技术应用于科研和产品开发过程的一种模…

第三百九十一回

文章目录 1. 概念介绍2. 方法与细节2.1 实现方法2.2 具体细节 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何混合选择多个图片和视频文件"相关的内容&#xff0c;本章回中将介绍如何通过相机获取视频文件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. …

windows11编译FFmpeg源码完整步骤

1.安装MSYS2 下载并安装MSYS2 安装GCC GCC安装成功 克隆FFmpeg源码 打开MSYS2终端并进入ffmpeg文件夹,然后输入./configure回车开始生成makefile

「蓝桥·算法双周赛」第七场分级赛——小白入门赛

题目列表 说明 好久没打蓝桥杯的比赛&#xff0c;回来试试水&#xff0c;就开了第1、2、3一共三个题&#xff0c;第4题可惜了。1.thanks,mom【算法赛】 思路&#xff1a; 没什么好说的&#xff0c;但是当时比赛刚开始服务器有问题&#xff0c;基本提交的全WA了。#include <…

线程有几种状态,状态之间的流转是怎样的?

Java中线程的状态分为6种&#xff1a; 1.初始(NEW)&#xff1a;新创建了一个线程对象&#xff0c;但还没有调用start()方法。 2.运行(RUNNABLE)&#xff1a;Java线程中将就绪&#xff08;READY&#xff09;和运行中&#xff08;RUNNING&#xff09;两种状态笼统的称为“运行”…

【Azure 架构师学习笔记】- Azure Private Endpoint

本文属于【Azure 架构师学习笔记】系列。 前言 公有云的其中一个特点是默认允许公网访问&#xff0c; 这就对企业环境带来风险&#xff0c;也是很多年前企业对公有云抵触的其中一个原因&#xff0c;现在这类问题已经很少&#xff0c;因为有了很多技术来确保云上的资源被安全地…

HTML5:七天学会基础动画网页10

继续介绍3D转换: 3D转换:rotate3d 方法与说明 rrotateX(angle)otate3d(x,y,z,angle[角度]) 3D转换&#xff0c;正常取值0/1&#xff0c;0代表当前轴线不进行旋转&#xff0c;1反之&#xff0c;例:rotate3d(1,1,1,30deg)&#xff0c;代表三个轴线都要旋转30度 rotate3d(0…

论文目录3:大模型时代(2023+)

1 instruction tuning & in context learning 论文名称来源主要内容Finetuned Language Models Are Zero-Shot Learners2021 机器学习笔记&#xff1a;李宏毅ChatGPT Finetune VS Prompt_UQI-LIUWJ的博客-CSDN博客 早期做instruction tuning的work MetaICL: Learning to …

HTML 学习笔记(九)颜色值和长度单位

一、颜色 1.通过RGB值来设置颜色 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>table</title&…

⎣模型⎤Claude3——给奥特曼一点点压力!

微信公众号|人工智能技术派 作 者|hws Claude系列模型是由美国创业公司Anthropic基于transformer架构研发&#xff0c;该公司是一群在2021年OpenAI离职员工(包括前首席科学家Ilya Sutskever和Dario Amodei)创建的。Anthropic先前已发布Claude1和Claude2&#xff0c;效果相当不错…

【数据分享】2000-2022年全国1km分辨率的逐日PM10栅格数据

空气质量数据是在我们日常研究中经常使用的数据&#xff01;之前我们给大家分享了2000-2022年全国范围逐日的PM2.5栅格数据和2013-2022年全国范围逐日SO2栅格数据&#xff08;可查看之前的文章获悉详情&#xff09;。 本次我们给大家带来的是2000-2022年全国范围的逐日的PM10栅…

【论文阅读】关于智能合约的漏洞检测

两篇论文&#xff0c;都是关于智能合约漏洞检测的综述文章 [1]崔展齐,杨慧文,陈翔等.智能合约安全漏洞检测研究进展[J/OL].软件学报:1-33[2024-03-05].https://doi.org/10.13328/j.cnki.jos.007046. [2]王丹,黄松,王兴亚.以太坊智能合约测试研究综述[J].信息技术与信息化,2023(…

算法设计与分析(超详解!) 第一节 算法概述

1.算法的定义 算法的非形式化定义&#xff1a;算法是规则的有限集合&#xff0c;是为解决特定问题而规定的一系列操作。 可以理解为&#xff1a;算法&#xff08;algorithm&#xff09;是指在解决问题时&#xff0c;按照某种机械的步骤一定可以得到问题的结果&#xff08;有的…

【黑马程序员】STL实战--演讲比赛管理系统

文章目录 演讲比赛管理系统需求说明比赛规则程序功能 创建管理类功能描述创建演讲比赛管理类 菜单功能添加菜单成员函数声明菜单成员函数实现菜单功能测试 退出功能添加退出功能声明退出成员函数实现退出功能测试 演讲比赛功能功能分析创建选手类比赛成员属性添加初始化属性创建…

数据分析-Pandas最简单的方法画矩阵散点图

数据分析-Pandas直接画矩阵散点图 数据分析和处理中&#xff0c;难免会遇到各种数据&#xff0c;那么数据呈现怎样的规律呢&#xff1f;不管金融数据&#xff0c;风控数据&#xff0c;营销数据等等&#xff0c;莫不如此。如何通过图示展示数据的规律&#xff1f; 数据表&…

有点炫酷有点diao的免费wordpress模板主题

这是一款经典的免费wordpress主题&#xff0c;被广泛应用于多个行业的网站。 https://www.wpniu.com/themes/189.html

vulhub中Weblogic < 10.3.6 ‘wls-wsat‘ XMLDecoder 反序列化漏洞(CVE-2017-10271)复现

Weblogic的WLS Security组件对外提供webservice服务&#xff0c;其中使用了XMLDecoder来解析用户传入的XML数据&#xff0c;在解析的过程中出现反序列化漏洞&#xff0c;导致可执行任意命令。 访问http://your-ip:7001/即可看到一个404页面&#xff0c;说明weblogic已成功启动 …

Kubernetes 安全秘籍:5 个你必须知道的知识点

Kubernetes 安全和身份验证是确保集群和应用安全的关键。今天将深入探讨 Service Account、身份验证和RBAC的关键概念和实践&#xff0c;帮助您构建安全可靠的应用。今天本文将着重于安全相关的内容&#xff0c;并提供更详细的示例和配置说明&#xff0c;帮助兄弟们更深入地理解…

机器人期刊:Science Robotics and IEEE Transactions

文章目录 1. Science Robotics (出版商 AAAS)2. IEEE Transactions on RoboticsReference1. Science Robotics (出版商 AAAS) https://www.science.org/journal/scirobotics 2. IEEE Transactions on Robotics