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

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()
}

猜你喜欢

转载自blog.csdn.net/qq_47431008/article/details/130987579