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