Ainx框架基础路由模块

在这里插入图片描述

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


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

文章目录

  • Ainx框架基础路由模块
    • IRequest 消息请求抽象类
      • 创建抽象IRequest层
      • 实现Request类
    • IRouter 路由配置抽象类
      • 创建抽象的IRouter层
      • 实现Router类
    • Ainx-V0.3-集成简单路由功能
      • IServer增添路由添加功能
      • Server类增添Router成员
      • Connection类绑定一个Router成员
      • 在Connection调用注册的Router处理业务
    • Ainx-V0.3代码实现
    • 使用Ainx-V0.3完成应用程序

Ainx框架基础路由模块

现在我们就给用户提供一个自定义的conn处理业务的接口吧,很显然,我们不能把业务处理的方法绑死在type HandFunc func(*net.TCPConn, []byte, int) error这种格式中,我们需要定一些interface{}来让用户填写任意格式的连接处理业务方法。

那么,很显然func是满足不了我们需求的,我们需要再做几个抽象的接口类。

IRequest 消息请求抽象类

我们现在需要把客户端请求的连接信息 和 请求的数据,放在一个叫Request的请求类里,这样的好处是我们可以从Request里得到全部客户端的请求信息,也为我们之后拓展框架有一定的作用,一旦客户端有额外的含义的数据信息,都可以放在这个Request里。可以理解为每次客户端的全部请求数据,Zinx都会把它们一起放到一个Request结构体里。

创建抽象IRequest层

在ainterface下创建新文件irequest.go。

ainx/ainterface/irequest.go

package ainterface/*
IRequest 接口
实际是把客户端请求链接信息和请求数据包放在Request里
*/
type IRequest interface {GetConnection() IConnection //获取请求链接信息GetData() []byte            //获取请求消息的数据GetMsgID() uint32           //获取消息ID
}

不难看出,当前的抽象层只提供了两个Getter方法,所以有个成员应该是必须的,一个是客户端连接,一个是客户端传递进来的数据,当然随着Zinx框架的功能丰富,这里面还应该继续添加新的成员。

实现Request类

在anet下创建IRequest抽象接口的一个实例类文件request.go

ainx/anet/request.go

package anetimport "ainx/ainterface"type Request struct {conn ainterface.IConnection //已经和客户端建立好的链接date []byte                 //客户端请求数据
}// 获取请求链接信息
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()
}

IRouter 路由配置抽象类

现在我们来给Zinx实现一个非常简单基础的路由功能,目的当然就是为了快速的让Zinx步入到路由的阶段。后续我们会不断的完善路由功能。

创建抽象的IRouter层

在ainterface下创建irouter.go文件

ainx/ainterface/irouter.go

package ainterface/*路由接口,这里路由是 使用框架框架者给该链接自定的 处理业务方法路由里的IRequest 则包含用该链接的链接信息和该链接的请求数据信息
*/
type IRouter interface {PreHandle(request IRequest)  //在处理conn业务之前的钩子方法Handle(request IRequest)     //处理conn业务的方法PostHandle(request IRequest) //处理conn业务之后的钩子方法
}

我们知道router实际上的作用就是,服务端应用可以给Zinx框架配置当前链接的处理业务方法,之前的Zinx-V0.2我们的Zinx框架处理链接请求的方法是固定的,现在是可以自定义,并且有3种接口可以重写。

Handle:是处理当前链接的主业务函数

PreHandle:如果需要在主业务函数之前有前置业务,可以重写这个方法

PostHandle:如果需要在主业务函数之后又后置业务,可以重写这个方法

当然每个方法都有一个唯一的形参IRequest对象,也就是客户端请求过来的连接和请求数据,作为我们业务方法的输入数据。

实现Router类

在anet下创建router.go文件

package anetimport ("ainx/ainterface"
)// 实现router时,先嵌入这个基类,然后根据需要对这个基类的方法进行重写
type BaseRouter struct{}// 这里之所以BaseRouter的方法都为空,
// 是因为有的Router不希望有PreHandle或PostHandle
// 所以Router全部继承BaseRouter的好处是,不需要实现PreHandle和PostHandle也可以实例化
func (br *BaseRouter) PreHandle(req ainterface.IRequest)  {}
func (br *BaseRouter) Handle(req ainterface.IRequest)     {}
func (br *BaseRouter) PostHandle(req ainterface.IRequest) {}

我们当前的ainx目录结构应该如下:

├─ainterface
│      iconnection.go
│      irequest.go
│      irouter.go
│      iserver.go
│
└─anetconnection.gorequest.gorouter.goserver.goserver_test.go

Ainx-V0.3-集成简单路由功能

IServer增添路由添加功能

我们需要给IServer类,增加一个抽象方法AddRouter,目的也是让Ainx框架使用者,可以自定一个Router处理业务方法。

ainx/ainterface/irouter.go

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

Server类增添Router成员

有了抽象的方法,自然Server就要实现,并且还要添加一个Router成员.

ainx/anet/server.go

type Server struct {// 设置服务器名称Name string// 设置网络协议版本IPVersion string// 设置服务器绑定IPIP string// 设置端口号Port string//当前Server由用户绑定的回调router,也就是Server注册的链接对应的处理业务Router ainterface.IRouter//todo 未来目标提供更多option字段来控制server实例化
}

然后NewServer()方法, 初始化Server对象的方法也要加一个初始化成员


/*
创建一个服务器句柄
*/
func NewServer(name string) ainterface.IServer {s := &Server{Name:      name,IPVersion: "tcp4",IP:        "0.0.0.0",Port:      "8080",Router:    nil,}return s
}

Connection类绑定一个Router成员

ainx/anet/connection.go

type Connection struct {//当前链接的socket TCP套接字Conn *net.TCPConn// 当前链接的ID也可以称作SessionID,ID全局唯一ConnID uint32// 当前链接的关闭状态isClosed bool//该连接的处理方法routerRouter ainterface.IRouter// 告知该链接已经退出/停止的channelExitBuffChan chan bool
}

在Connection调用注册的Router处理业务

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 {//读取我们最大的数据到buf中buf := make([]byte, 512)_, err := c.Conn.Read(buf)if err != nil {fmt.Println("recv buf err ", err)c.ExitBuffChan <- truecontinue}//得到当前客户端请求的Request数据req := Request{conn: c,data: buf,}//从路由Routers 中找到注册绑定Conn的对应Handlego func(request ainterface.IRequest) {//执行注册的路由方法c.Router.PreHandle(request)c.Router.Handle(request)c.Router.PostHandle(request)}(&req)}
}

这里我们在conn读取完客户端数据之后,将数据和conn封装到一个Request中,作为Router的输入数据。

然后我们开启一个goroutine去调用给Zinx框架注册好的路由业务。

Ainx-V0.3代码实现

ainx/anet/server.go

package anetimport ("ainx/ainterface""errors""fmt""net""time"
)type Server struct {// 设置服务器名称Name string// 设置网络协议版本IPVersion string// 设置服务器绑定IPIP string// 设置端口号Port string//当前Server由用户绑定的回调router,也就是Server注册的链接对应的处理业务Router ainterface.IRouter//todo 未来目标提供更多option字段来控制server实例化
}// ============== 定义当前客户端链接的handle api ===========
func CallBackToClient(conn *net.TCPConn, data []byte, cnt int) error {//回显业务fmt.Println("[Conn Handle] CallBackToClient ...")if _, err := conn.Write(data[:cnt]); err != nil {fmt.Println("write back buf err", err)return errors.New("CallBackToClient error")}return nil
}// ============== 实现 ainterface.IServer 里的全部接口方法 ========
// 开启网络服务
func (s *Server) Start() {fmt.Printf("[START] Server listenner at IP: %s, Port %s, is starting\n", s.IP, s.Port)// 开启一个go去做服务端的Listener业务// todo 未来目标是提供更多协议,可以利用if或者switch对IPVersion进行判断而选择采取哪种协议,下面整个方法要重写go func() {//1 获取一个TCP的Addraddr, err := net.ResolveTCPAddr(s.IPVersion, s.IP+":"+s.Port)if err != nil {fmt.Println("resolve tcp addr err: ", err)return}// 2 监听服务器地址listener, err := net.ListenTCP(s.IPVersion, addr)if err != nil {fmt.Println("listen", s.IPVersion, "err", err)return}//	  已经成功监听fmt.Println("start Ainx server  ", s.Name, " success, now listenning...")//TODO server.go 应该有一个自动生成ID的方法var cid uint32cid = 0//3 启动server网络连接业务for {//3.1 阻塞等待客户端建立连接请求conn, err := listener.AcceptTCP()if err != nil {fmt.Println("Accept err ", err)continue}//3.2 TODO Server.Start() 设置服务器最大连接控制,如果超过最大连接,那么则关闭此新的连接//3.3 处理该新连接请求的 业务 方法, 此时应该有 handler 和 conn是绑定的dealConn := NewConnection(conn, cid, s.Router)cid++//3.4 启动当前链接的处理业务go dealConn.Start()}}()
}
func (s *Server) Stop() {fmt.Println("[STOP] Zinx server , name ", s.Name)//TODO  Server.Stop() 将其他需要清理的连接信息或者其他信息 也要一并停止或者清理
}
func (s *Server) Serve() {s.Start()//TODO Server.Serve() 是否在启动服务的时候 还要处理其他的事情呢 可以在这里添加//阻塞,否则主Go退出, listenner的go将会退出for {time.Sleep(10 * time.Second)}
}
func (s *Server) AddRouter(router ainterface.IRouter) {s.Router = routerfmt.Println("Add Router succ! ")
}/*
创建一个服务器句柄
*/
func NewServer(name string) ainterface.IServer {s := &Server{Name:      name,IPVersion: "tcp4",IP:        "0.0.0.0",Port:      "8080",Router:    nil,}return s
}

ainx/anet/conneciont.go

package anetimport ("ainx/ainterface""fmt""net"
)type Connection struct {//当前链接的socket TCP套接字Conn *net.TCPConn// 当前链接的ID也可以称作SessionID,ID全局唯一ConnID uint32// 当前链接的关闭状态isClosed bool//该连接的处理方法routerRouter ainterface.IRouter// 告知该链接已经退出/停止的channelExitBuffChan chan bool
}func (c *Connection) GetConnection() net.Conn {return c.Conn
}// 创建链接的方法
func NewConnection(conn *net.TCPConn, connID uint32, router ainterface.IRouter) *Connection {c := &Connection{Conn:         conn,ConnID:       connID,isClosed:     false,Router:       router,ExitBuffChan: make(chan bool, 1),}return c
}// 处理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 {//读取我们最大的数据到buf中buf := make([]byte, 512)_, err := c.Conn.Read(buf)if err != nil {fmt.Println("recv buf err ", err)c.ExitBuffChan <- truecontinue}//得到当前客户端请求的Request数据req := Request{conn: c,data: buf,}//从路由Routers 中找到注册绑定Conn的对应Handlego func(request ainterface.IRequest) {//执行注册的路由方法c.Router.PreHandle(request)c.Router.Handle(request)c.Router.PostHandle(request)}(&req)}
}// 启动连接,让当前链接工作
func (c *Connection) Start() {// 开启处理该链接读取到客户端数据之后的请求业务go c.StartReader()for {select {case <-c.ExitBuffChan:// 得到退出消息,不再阻塞return}}
}// 停止链接,结束当前链接状态M
func (c *Connection) Stop() {//1.如果当前链接关闭if c.isClosed == true {return}c.isClosed = true//TODO Connection Stop() 如果用户注册了该链接的关闭回调业务,那么在此刻应该显示调用// 关闭socket链接err := c.Conn.Close()if err != nil {return}//通知从缓冲队列读数据的业务,该链接已经关闭c.ExitBuffChan <- true//关闭该链接全部管道close(c.ExitBuffChan)
}// 从当前链接获取原始的socket TCPConn
func (c *Connection) GetTCPConnection() *net.TCPConn {return c.Conn
}// 获取当前链接ID
func (c *Connection) GetConnID() uint32 {return c.ConnID
}// 获取远程客户端地址信息
func (c *Connection) RemoteAddr() net.Addr {return c.Conn.RemoteAddr()
}

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

接下来我们在基于Ainx写服务器,就可以配置一个简单的路由功能了。

Server.go

package mainimport ("fmt""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 {_, err := conn.Write([]byte("Ainx V0.3"))if err != nil {fmt.Println("write error err ", err)return}buf := make([]byte, 512)cnt, err := conn.Read(buf)if err != nil {fmt.Println("read buf error ")return}fmt.Printf(" server call back : %s, cnt = %d\n", buf, cnt)time.Sleep(1 * time.Second)}
}

Client.go

package mainimport ("ainx/ainterface""ainx/anet""fmt"
)// ping test 自定义路由
type PingRouter struct {anet.BaseRouter //一定要先基础BaseRouter
}// Test PreHandle
func (this *PingRouter) PreHandle(request ainterface.IRequest) {fmt.Println("Call Router PreHandle")_, err := request.GetConnection().GetConnection().Write([]byte("before ping ....\n"))if err != nil {fmt.Println("call back ping ping ping error")}
}// Test Handle
func (this *PingRouter) Handle(request ainterface.IRequest) {fmt.Println("Call PingRouter Handle")_, err := request.GetConnection().GetConnection().Write([]byte("ping...ping...ping\n"))if err != nil {fmt.Println("call back ping ping ping error")}
}// Test PostHandle
func (this *PingRouter) PostHandle(request ainterface.IRequest) {fmt.Println("Call Router PostHandle")_, err := request.GetConnection().GetConnection().Write([]byte("After ping .....\n"))if err != nil {fmt.Println("call back ping ping ping error")}
}func main() {//创建一个server句柄s := anet.NewServer("[ainx V0.3]")s.AddRouter(&PingRouter{})//2 开启服务s.Serve()
}

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Vue 3, TypeScript 和 Element UI Plus:前端开发的高级技巧与最佳实践

Vue 3、TypeScript 和 Element UI Plus 结合使用时&#xff0c;可以提供一个强大且灵活的前端开发环境。以下是一些高级用法和技巧&#xff0c;帮助你更有效地使用这些技术&#xff1a; 1. Vue 3 高级特性 Composition API 使用 setup 函数: Vue 3 引入了 Composition API&am…

Linux内核队列queue.h

文章目录 一、简介二、SLIST单向无尾链表2.1 介绍2.2 操作2.3 例子 三、STAILQ单向有尾链表四、LIST双向无尾链表五、TAILQ双向有尾链表六、CIRCLEQ循环链表七、queue源码参考 一、简介 queue.h是一个非常经典的文件&#xff0c;定义了一系列宏的操作&#xff0c;它定义了一系…

MWC 2024丨美格智能推出5G RedCap系列FWA解决方案,开启5G轻量化新天地

2月27日&#xff0c;在MWC 2024世界移动通信大会上&#xff0c;美格智能正式推出5G RedCap系列FWA解决方案。此系列解决方案具有低功耗、低成本等优势&#xff0c;可以显著降低5G应用复杂度&#xff0c;快速实现5G网络接入&#xff0c;提升FWA部署的经济效益。 RedCap技术带来了…

leetcode 2.27

leetcode hot 100 哈希1.字母异位词分组2.最长连续序列 双指针1.盛最多水的容器2.和为 K 的子数组 数组1.除自身以外数组的乘积 哈希 1.字母异位词分组 49. 字母异位词分组 方法一&#xff1a;排序 由于互为字母异位词的两个字符串包含的字母相同&#xff0c;因此对两个字符…

Python入门到精通(九)——Python数据可视化

Python数据可视化 一、JSON数据格式 1、定义 2、python数据和JSON数据转换 二、pyecharts 三、折线图 四、地图 五、动态柱状图 一、JSON数据格式 1、定义 JSON是一种轻量级的数据交互格式。可以按照JSON指定的格式去组织和封装数据JSON本质上是一个带有特定格式的字符…

vue项目从后端下载文件显示进度条或者loading

//API接口 export const exportDownload (params?: Object, peCallback?: Function) > {return new Promise((resolve, reject) > {axios({method: get,url: ,headers: {access_token: ${getToken()},},responseType: blob,params,onDownloadProgress: (pe) > {peC…

数据结构2月21日

双向链表: func函数&#xff1a; #include <stdio.h> #include <stdlib.h> …

数据分析-Pandas数据探查初步:离散点图

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

若依前后端分离版开源项目学习

前言&#xff1a;vscode中vue代码没有高亮显示&#xff0c;可以下载vetur插件解决&#xff0c;ctrl点击无法跳转函数定义问题&#xff0c;可以下载vue-helper插件解决&#xff1b;idea中ctrl点击函数即可跳转函数定义。 一、登录 1.生成验证码 基本思路&#xff1a; 后端生…

基于HT32的智能家居demo(蓝牙上位机)

参加合泰杯作品的部分展示&#xff0c;基于HT32的智能家居&#xff0c;这里展示灯光的相关控制&#xff0c;是用蓝牙进行的数据透传&#xff0c;参考了一些资料&#xff0c;美化封装了一下之前的上位机界面。 成果展示 点击主界面的蓝牙设置&#xff0c;进行连接&#xff0c;下…

【推荐算法系列六】WideDeep模型

文章目录 参考资料 模型结构模型的记忆能力模型的泛化能力问题 参考资料 见微知著&#xff0c;你真的搞懂Google的Wide&Deep模型了吗&#xff1f;keras实现的代码参考 模型结构 它是由左侧的 Wide 部分和右侧的 Deep 部分组成的。Wide 部分的结构太简单了&#xff0c;就是…

Eslint在Vscode中使用技巧的相关技巧

ps :该文章会详细结论构建一个脚手架遇到的问题&#xff0c;会持续更新&#xff0c;请定时查看 Eslint相关​ 在vscode中使用eslint插件 在vscode中用户配置没有开启eslint.enable 在vscode中工作区配置开启eslint.enable settings.json中没有做eslint相关配置 在编写的vue…

Jenkins参数化构建项目(Git+docker部署+Python+flask项目)

目录 一、概述二、环境三、部署流程3.1 gitee上传代码3.2 jenkins配置3.2.1 Gitee配置3.2.2 SSH配置3.2.3 新建任务 3.3 执行过程3.3.1初始化构建3.3.2 重新提交代码构建 一、概述 使用Jenkins进行CI/CD自动化部署&#xff0c;参数化构建Git代码拉取&#xff0c;docker镜像打包…

开创5G无线新应用:笙科电子5.8GHz 射频芯片

笙科电子(AMICCOM) 5.8GHz A5133射频芯片是一款专门设计用于在5.8GHz频率范围内&#xff08;5725MHz - 5850MHz)进行射频信号处理的集成电路。这些集成电路通常包括各种功能模块&#xff0c;如射频前端、混合器、功率放大器、局部振荡器等&#xff0c;以支持无线通信系统的各种…

3D可视化项目,选择unity3D还是three.js,是时候挑明了。

2023-08-10 23:07贝格前端工场 Hi&#xff0c;我是贝格前端工场&#xff0c;在开发3D可视化项目中&#xff0c;是选择U3D还是three,js时&#xff0c;很多老铁非常的迷茫&#xff0c;本文给老铁们讲清楚该如何选择&#xff0c;欢迎点赞评论分享转发。 一、Unity3D和three.js简…

Android Activity启动模式

文章目录 Android Activity启动模式概述四种启动模式Intent标记二者区别 Android Activity启动模式 概述 Activity 的管理方式是任务栈。栈是先进后出的结构。 四种启动模式 启动模式说明适用场景standard标准模式默认模式&#xff0c;每次启动Activity都会创建一个新的Act…

10W 音频功率放大电路芯片TDA2003,可用于汽车收音机及收录机中作音频功率放大器,内部具有短路保护和过热保护等功能

TDA2003 用于汽车收音机及收录机中作音频功率放大器。 采用 TO220B5 封装形式。 主要特点&#xff1a; ⚫ 内部具有短路保护和过热保护。内部具有地线开路、电源极性接 反和负载泄放电压反冲等保护电路。 ⚫ 输出电流大。 ⚫ 负载电阻可低至 1.6 。 …

Linux:Ansible的常用模块

模块帮助 ansible-doc -l 列出ansible的模块 ansible-doc 模块名称 # 查看指定模块的教程 ansible-doc command 查看command模块的教程 退出教程时候建议不要使用ctrlc 停止&#xff0c;某些shell工具会出现错误 command ansible默认的模块,执行命令&#xff0c;注意&#x…

ARM系列 -- 虚拟化(一)

今天来研究一个有意思的话题&#xff0c;虚拟化&#xff08;virtualization&#xff09;。 开始前&#xff0c;先闲扯一下&#xff0c;最近一个词比较火&#xff0c;“元宇宙&#xff08;Metaverse&#xff09;”。在维基百科里面是这么定义元宇宙的&#xff0c;“The Metaver…

web学习笔记(二十一)

目录 1.构造函数创建对象 1.1规则 1.2 new关键字调用构造函数时&#xff0c;函数内部做了什么事情&#xff1f; 1.3总结 2.混合模式创建对象 3.JavaScript 继承---借助构造函数 4.原型链 1.构造函数创建对象 1.1规则 &#xff08;1&#xff09;构造函数----函数名的首字…