实验介绍 本节实验中,我们将完成 Zinx 框架的基础路由的模块。如下面的思维导图中所表示的这些功能。
知识点 路由功能模块
准备 现在我们就给用户提供一个自定义的 conn 处理业务的接口吧,很显然,我们不能把业务处理业务的方法绑死在type HandFunc func(*net.TCPConn, []byte, int) error这种格式中,我们需要定一些interface{}来让用户填写任意格式的连接处理业务方法。
那么,很显然 func 是满足不了我们需求的,我们需要再做几个抽象的接口类。
IRequest 消息请求抽象类 我们现在需要把客户端请求的连接信息和请求的数据,放在一个叫 Request 的请求类里,这样的好处是我们可以从 Request 里得到全部客户端的请求信息,也为我们之后拓展框架有一定的作用,一旦客户端有额外的含义的数据信息,都可以放在这个 Request 里。可以理解为每次客户端的全部请求数据,Zinx 都会把它们一起放到一个 Request 结构体里。
创建抽象 IRequest 层 在 ziface 下创建新文件 irequest.go。
zinx/ziface/irequest.go
1 2 3 4 5 6 7 package zifacetype IRequest interface { GetConnection() IConnection GetData() []byte }
不难看出,当前的抽象层只提供了两个 Getter 方法,所以有个成员应该是必须的,一个是客户端连接,一个是客户端传递进来的数据,当然随着 Zinx 框架的功能丰富,这里面还应该继续添加新的成员。
实现 Request 类 在 znet 下创建 IRequest 抽象接口的一个实例类文件 request.go
zinx/znet/request.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package znetimport ( "zinx/ziface" ) type Request struct { conn ziface.IConnection data []byte } func (r *Request) GetConnection () ziface .IConnection { return r.conn } func (r *Request) GetData () []byte { return r.data }
IRouter 路由配置抽象类 现在我们来给 Zinx 实现一个非常简单基础的路由功能,目的当然就是为了快速的让 Zinx 步入到路由的阶段。后续我们会不断的完善路由功能。
创建抽象的 IRouter 层 我们知道 router 实际上的作用就是,服务端应用可以给 Zinx 框架配置当前链接的处理业务方法,之前的 Zinx-V0.2 我们的 Zinx 框架处理链接请求的方法是固定的,现在是可以自定义,并且有 3 种接口可以重写。
Handle:是处理当前链接的主业务函数
PreHandle:如果需要在主业务函数之前有前置业务,可以重写这个方法
PostHandle:如果需要在主业务函数之后又后置业务,可以重写这个方法
当然每个方法都有一个唯一的形参 IRequest 对象,也就是客户端请求过来的连接和请求数据,作为我们业务方法的输入数据。
1 2 3 4 5 6 7 8 9 10 package zifacetype IRouter interface { PreHandle(request IRequest) Handle(request IRequest) PostHandle(request IRequest) }
实现 Router 类 在 znet 下创建 router.go 文件
1 2 3 4 5 6 7 8 9 10 11 12 package znetimport "zinx/ziface" type BaseRouter struct {}func (br *BaseRouter) PreHandle (req ziface.IRequest) {}func (br *BaseRouter) Handle (req ziface.IRequest) {}func (br *BaseRouter) PostHandle (req ziface.IRequest) {}
Zinx-V0.3-集成简单路由功能 IServer 增添路由添加功能 我们需要给 IServer 类,增加一个抽象方法 AddRouter,目的也是让 Zinx 框架使用者,可以自定一个 Router 处理业务方法。
zinx/ziface/iserver.go
1 2 3 4 5 6 7 8 9 10 11 12 package zifacetype IServer interface { Start() Stop() Serve() AddRouter(router IRouter) }
Server 类增添 Router 成员 有了抽象的方法,自然 Server 就要实现,并且还要添加一个 Router 成员.
zinx/znet/server.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Server struct { Name string IPVersion string IP string Port int Router ziface.IRouter } copy
然后NewServer()方法, 初始化 Server 对象的方法也要加一个初始化成员
1 2 3 4 5 6 7 8 9 10 11 12 13 func NewServer (name string ) ziface .IServer { s:= &Server { Name :name, IPVersion:"tcp4" , IP:"0.0.0.0" , Port:7777 , Router: nil , } return s }
Connection 类绑定一个 Router 成员 zinx/znet/connection.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Connection struct { Conn *net.TCPConn ConnID uint32 isClosed bool Router ziface.IRouter ExitBuffChan chan bool }
在 Connection 调用注册的 Router 处理业务 zinx/znet/connection.go
这里我们在 conn 读取完客户端数据之后,将数据和 conn 封装到一个 Request 中,作为 Router 的输入数据。
然后我们开启一个 goroutine 去调用给 Zinx 框架注册好的路由业务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 func (c *Connection) StartReader () { fmt.Println("Reader Goroutine is running" ) defer fmt.Println(c.RemoteAddr().String()," conn reader exit!" ) defer c.Stop() for { buf := make ([]byte ,512 ) cnt,err := c.Conn.Read(buf) if err != nil { fmt.Println("recv buf err" ,err) c.ExitBuffChan <- true continue } req := Request{ conn: c, data: buf, } go func (request ziface.IRequest) { c.Router.PreHandle(request) c.Router.Handle(request) c.Router.PostHandle(request) }(&req) } }
zinx/znet/server.go
1 2 3 4 5 func (s *Server) AddRouter (router ziface.IRouter) { s.Router = router fmt.Println("Add Router succ!" ) }
1 2 dealConn := NewConnection(conn,cid,s.Router)
zinx/znet/conneciont.go
1 2 3 4 5 6 7 8 9 10 11 12 func NewConnection (conn *net.TCPConn,connID uint32 ,router ziface.IRouter) *Connection { c := &Connection{ Conn: conn, ConnID: connID, isClosed: false , Router: router, ExitBuffChan: make (chan bool ,1 ), } return c }
1 2 _,err := c.Conn.Read(buf)
测试基于 Zinx 完成的服务端应用 Server.go
我们这里自定义了一个类似 Ping 操作的路由,就是当客户端发送数据,我们的处理业务就是返回给客户端”ping…ping..ping..”, 为了测试,当前路由也同时实现了 PreHandle 和 PostHandle 两个方法。实际上 Zinx 会利用模板的设计模式,依次在框架中调用PreHandle、Handle、PostHandle三个方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package mainimport ( "zinx/znet" "fmt" "zinx/ziface" ) type PingRouter struct { znet.BaseRouter } func (this *PingRouter) PreHandle (request ziface.IRequest) { fmt.Println(" Call Router PreHandle" ) if _,err := request.GetConnection().GetTCPConnection().Write([]byte ("before ping ....\n" )){ fmt.Println("call back ping err" ) } } func (this *PingRouter) Handle (request ziface.IRequest) { fmt.Println(" Call Router Handle" ) if _,err := request.GetConnection().GetTCPConnection().Write([]byte (" ping ....\n" )){ fmt.Println("call back ping err" ) } } func (this *PingRouter) PostHandle (request ziface.IRequest) { fmt.Println(" Call Router PostHandle" ) if _,err := request.GetConnection().GetTCPConnection().Write([]byte ("after ping ....\n" )){ fmt.Println("call back ping err" ) } } func main () { s := znet.NewServer("[zinx V0.1]" ) s.AddRouter(&PingRouter{}) s.Serve() }
客户端应用测试程序 和之前的 Client.go 一样 没有改变 。
这里我们进行测试的时候,和上一节一样,大家再测试的时候千万别忘了,启动的时候先启动 Server 再启动 Client。关闭的时候也是先关闭 Server 再关闭 Client。
执行结果如下: