Ainx的多路由模式

在这里插入图片描述

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


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

文章目录

  • Ainx的多路由模式
    • 创建消息管理模块
      • 创建消息管理模块抽象类
      • 实现消息管理模块
    • Ainx-V0.6代码实现
    • 使用Ainx-V0.6完成应用程序

Ainx的多路由模式

我们之前在已经给Zinx配置了路由模式,但是很惨,之前的Zinx好像只能绑定一个路由的处理业务方法。显然这是无法满足基本的服务器需求的,那么现在我们要在之前的基础上,给Zinx添加多路由的方式。

Apis map[uint32] ziface.IRouter

这里起名字是Apis,其中key就是msgId, value就是对应的Router,里面应是使用者重写的Handle等方法。

那么这个Apis应该放在哪呢。

我们再定义一个消息管理模块来进行维护这个Apis。

创建消息管理模块

创建消息管理模块抽象类

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

/*
消息管理抽象层
*/
type IMsgHandle interface {DoMsgHandler(request IRequest)          //马上以非阻塞方式处理消息AddRouter(msgId uint32, router IRouter) //为消息添加具体的处理逻辑
}

这里面有两个方法,AddRouter()就是添加一个msgId和一个路由关系到Apis中,那么DoMsgHandler()则是调用Router中具体Handle()等方法的接口。

实现消息管理模块

在ainx/anet下创建msghandler.go文件。

package anetimport ("ainx/ainterface""fmt""strconv"
)type MsgHandle struct {Apis map[uint32]ainterface.IRouter //存放每个MsgId 所对应的处理方法的map属性
}func NewMsgHandle() *MsgHandle {return &MsgHandle{Apis: make(map[uint32]ainterface.IRouter),}
}// 马上以非阻塞方式处理消息
func (mh *MsgHandle) DoMsgHandler(request ainterface.IRequest) {handler, ok := mh.Apis[request.GetMsgID()]if !ok {fmt.Println("api msgId = ", request.GetMsgID(), " is not FOUND!")return}//执行对应处理方法handler.PreHandle(request)handler.Handle(request)handler.PostHandle(request)
}// 为消息添加具体的处理逻辑
func (mh *MsgHandle) AddRouter(msgId uint32, router ainterface.IRouter) {//1 判断当前msg绑定的API处理方法是否已经存在if _, ok := mh.Apis[msgId]; ok {panic("repeated api , msgId = " + strconv.Itoa(int(msgId)))}//2 添加msg与api的绑定关系mh.Apis[msgId] = routerfmt.Println("Add api msgId = ", msgId)
}

Ainx-V0.6代码实现

首先iserver的AddRouter()的接口要稍微改一下,增添MsgId参数

ainx/ainterface/iserver.go

package ainterface// 定义服务器接口
type IServer interface {//启动服务器方法Start()//停止服务器方法Stop()//开启业务服务方法Serve()//路由功能:给当前服务注册一个路由业务方法,供客户端链接处理使用AddRouter(msgId uint32, router IRouter)// todo 路由分组 未来目标 添加类似hertz Group分组,为每个链接分组
}

其次,Server类中 之前有一个Router成员 ,代表唯一的处理方法,现在应该替换成MsgHandler成员

ainx/anet/server.go


type Server struct {// 设置服务器名称Name string// 设置网络协议版本IPVersion string// 设置服务器绑定IPIP string// 设置端口号Port string//当前Server的消息管理模块,用来绑定MsgId和对应的处理方法msgHandler ainterface.IMsgHandle//todo 未来目标提供更多option字段来控制server实例化
}

初始化Server自然也要更正,增加msgHandler初始化

/*
创建一个服务器句柄
*/
func NewServer() ainterface.IServer {//先初始化全局配置文件utils.GlobalSetting.Reload()s := &Server{Name:       utils.GlobalSetting.Name, //从全局参数获取IPVersion:  "tcp4",IP:         utils.GlobalSetting.Host,    //从全局参数获取Port:       utils.GlobalSetting.TcpPort, //从全局参数获取msgHandler: NewMsgHandle(),}return s
}

然后当Server在处理conn请求业务的时候,创建conn的时候也需要把msgHandler作为参数传递给Connection对象

//...
dealConn := NewConntion(conn, cid, s.msgHandler)
//...

那么接下来就是Connection对象了。固然在Connection对象中应该有MsgHandler的成员,来查找消息对应的回调路由方法

ainx/anet/connection.go


type Connection struct {//当前链接的socket TCP套接字Conn *net.TCPConn// 当前链接的ID也可以称作SessionID,ID全局唯一ConnID uint32// 当前链接的关闭状态isClosed bool//消息管理MsgId和对应处理方法的消息管理模块MsgHandler ainterface.IMsgHandle// 告知该链接已经退出/停止的channelExitBuffChan chan bool
}
// 创建链接的方法
func NewConnection(conn *net.TCPConn, connID uint32, msgHandler ainterface.IMsgHandle) *Connection {c := &Connection{Conn:         conn,ConnID:       connID,isClosed:     false,MsgHandler:   msgHandler,ExitBuffChan: make(chan bool, 1),}return c
}

最后,在conn已经拆包之后,需要调用路由业务的时候,我们只需要让conn调用MsgHandler中的DoMsgHander()方法就好了

ainx/anet/connection.go


// 处理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 c.MsgHandler.DoMsgHandler(&req)}
}

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

Server.go

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

Server端设置了2个路由,一个是MsgId为0的消息会执行PingRouter{}重写的Handle()方法,一个是MsgId为1的消息会执行HelloZinxRouter{}重写的Handle()方法。

我们现在写两个客户端,分别发送0消息和1消息来进行测试Zinx是否能够处理2个不同的消息业务。

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.6 Client0 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/776895.shtml

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

相关文章

Maximum Sum(贪心策略,模运算,最大子段和)

文章目录 题目描述输入格式输出格式样例输入1样例输出1样例输入2样例输出2提交链接提示 解析参考代码 题目描述 你有一个由 n n n 个整数组成的数组 a a a 。 你要对它进行 k k k 次操作。其中一个操作是选择数组 a a a 的任意连续子数组(可能为空)&#xff0c;并在数组的…

ubuntu的常用操作

一、用户 1.1 创建新用户 1.1.1 添加、修改、删除 1.2 超级用户root 切换到root用户&#xff0c;有全部权限&#xff0c;可以直接使用任何命令。 我们在什么时候才会切换到root用户进行操作呢&#xff1f;当我们使用某些命令&#xff0c;不想前面一直加sudo的话&#xff0c;…

2024年【道路运输企业主要负责人】考试题库及道路运输企业主要负责人报名考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 道路运输企业主要负责人考试题库根据新道路运输企业主要负责人考试大纲要求&#xff0c;安全生产模拟考试一点通将道路运输企业主要负责人模拟考试试题进行汇编&#xff0c;组成一套道路运输企业主要负责人全真模拟考…

1.7.2 python练习题15道

1、求出1 / 1 1 / 3 1 / 5……1 / 99的和 (1分之一1分之三1分支5....) 2、用循环语句&#xff0c;计算2 - 10之间整数的循环相乘的值 &#xff08;2*3*4*5....10) 3、用for循环打印九九乘法表 4、求每个字符串中字符出现的个数如&#xff1a;helloworld 5、实现把字符串str …

【LVGL-BMP,PNG,JPEG,GIF】

LVGL-BMP&#xff0c;PNG&#xff0c;JPEG&#xff0c;GIF ■ BMP■ 演示一&#xff1a; ■ PNG■ 演示一&#xff1a; ■ JPEG■ 演示一&#xff1a; ■ GIF■ 演示一&#xff1a; ■ BMP 使用 LVGL BMP解码库之前 &#xff0c;必须在 lv_conf.h文件启用 LV_USE_BMP。 如果该…

冥想打坐睡觉功法

睡觉把手机放远一点&#xff0c;有电磁辐射&#xff0c;我把睡觉功法交给你&#xff0c;这样就可以睡好了。

『大模型笔记』提示工程、微调和RAG之间对比

提示工程、微调和RAG之间对比 文章目录 一. 提示工程、微调和RAG之间对比二. 参考文章文章:Prompt Engineering vs Finetuning vs RAG一. 提示工程、微调和RAG之间对比 Prompt EngineeringFinetuning

C++ 字符串完全指南:学习基础知识到掌握高级应用技巧

C 字符串 字符串用于存储文本。 一个字符串变量包含由双引号括起来的一组字符&#xff1a; 示例 创建一个 string 类型的变量并为其赋值&#xff1a; string greeting "Hello";C 字符串连接 字符串连接可以使用 运算符来实现&#xff0c;生成一个新的字符串。…

spikingjelly学习-使用单层全连接snn脉冲神经网络识别mnist数据集

连接【https://spikingjelly.readthedocs.io/zh-cn/0.0.0.0.14/activation_based/lif_fc_mnist.html】 【训练代码的编写需要遵循以下三个要点&#xff1a; 脉冲神经元的输出是二值的&#xff0c;而直接将单次运行的结果用于分类极易受到编码带来的噪声干扰。因此一般认为脉冲…

逆向工程-Nag.exe

初步分析 首先&#xff0c;打开文件&#xff0c;进行文件初步探索。 然后&#xff0c;点击OK发现删除本界面并跳转到新的界面&#xff0c;并且需要我们去除弹出的窗口。 正式分析 拖拽进IDA Pro 和OllyDbg中进行分析。 根据我们初步测试的结果&#xff0c;我们知道一个重要线索…

LeetCode-热题100:153. 寻找旋转排序数组中的最小值

题目描述 已知一个长度为 n 的数组&#xff0c;预先按照升序排列&#xff0c;经由 1 到 n 次 旋转 后&#xff0c;得到输入数组。例如&#xff0c;原数组 nums [0,1,2,4,5,6,7] 在变化后可能得到&#xff1a; 若旋转 4 次&#xff0c;则可以得到 [4,5,6,7,0,1,2] 若旋转 7 次…

Vercel应用绑定自己的域名

准备域名 首先购买自己的域名&#xff0c;可以选择以下渠道进行购买&#xff1a; NamesiloGodaddy腾讯云阿里云 另外你还可以选择从以下平台获取到免费的域名&#xff1a; Freenomeu.org 欧盟组织的免费域名, 需要英国的用户信息才能注册; 在我看来免费的才是最贵的&#…

Rsyslog 日志集中管理实验

1.使用 进行日志集中管理 C/S 架构&#xff1a;客户端将其日志上传到服务器端&#xff0c;通过对服务器端日志的查询&#xff0c;来实现对其他客户端的日志进行集中管理 2.两台机器&#xff1a; &#xff08;server&#xff09;host-5(192.168.1.2)<------------>(192…

一招让你的薪水暴增,每个程序员都应该学会跟老板提加薪

为什么要学会薪资谈判&#xff1f; 在最近的一篇文章中&#xff0c;职业专家奥斯汀贝尔卡克 (Austin Belcak ) 解释了进行一点薪资谈判如何对您的长期收入产生巨大影响。 这是奥斯汀在他的薪资谈判示例中描绘的场景&#xff1a; Amari 和 Taylor 的年薪均为 50,000 美元 未来…

【QT入门】 Qt代码创建布局之水平布局、竖直布局详解

往期回顾&#xff1a; 【QT入门】 Qt实现自定义信号-CSDN博客 【QT入门】 Qt自定义信号后跨线程发送信号-CSDN博客 【QT入门】 Qt内存管理机制详解-CSDN博客 【QT入门】 Qt代码创建布局之水平布局、竖直布局详解 先看两个问题&#xff1a; 1、ui设计器设计界面很方便&#xf…

ffmpeg命令行

ffmpeg 如果要在linux gdb 调试&#xff0c;需要在configure 时候不优化 开启调试 ./configure --enable-debug --disable-optimizations make如何开启gdb 调试 gdb ffmpeg_gset args -i test.hevc -c:v copy -c:a copy output_265.mp4rh264 的流生成mp4 文件&#xff0c;不转…

spring-boot-devtools配置和原理

一、前言 昨天&#xff0c;一个同事Eclipse在启动SpringBoot项目时一直不停地加载&#xff0c;后来发现是因为spring-boot-devtools造成的问题&#xff0c;因为我们把日志输出的目录设置在当前项目里&#xff08;~/mnt/logs/&#xff0c;这样设置是因为mac电脑没有根目录权限&…

摸鱼工具—终端热搜榜,实在是上班摸鱼必备之工具,妙啊

本文介绍我用Python语言开发的热搜榜&#xff0c;聚合有百度、头条、微博、知乎和CSDN等网站热搜信息。该工具运行于终端中&#xff0c;比如cmder、powershell或者git bash等&#xff0c;实在是上班、摸鱼之必备工具。 —、工具执行效果 1.1 项目代码 项目代码地址存在gitee中…

Linux用户及用户组权限

一、用户和用户组 功能项命令实例作用用户组cat /etc/group查看当前系统存在的用户组groupadd testing添加一个新的用户组testingcat /etc/group查看组是否被新增成功groupmod -n test testing将testing重命名成testgroupdel test删除组testgroups root查看用户root所在的所有…

linux centos7中使用 Postfix 和Dovecot搭建邮件系统

作者主页&#xff1a;点击&#xff01; Linux专栏&#xff1a;点击&#xff01; Postfix Postfix是一个开源的邮件传输代理&#xff08;MTA&#xff09;&#xff0c;用于路由和传送电子邮件。它是一个可靠、安全且高性能的邮件服务器软件&#xff0c;常用于搭建邮件系统的核心…