说明 本文档按照实验楼–Go 并发服务器框架 Zinx 入门的文档同步学习记录(大部分内容相同)https://www.lanqiao.cn/courses/1639 主要有以下原因: 1、模仿大神写教程的风格 2、验证每一个步骤,而不是简简单单的复制教程中的代码。简单重现
实验介绍 本节实验中,我们将完成 Zinx 框架的多路由模块。如下面的思维导图中所表示的这些功能。
知识点
准备工作 我们之前在已经给 Zinx 配置了路由模式,但是很惨,之前的 Zinx 好像只能绑定一个路由的处理业务方法。显然这是无法满足基本的服务器需求的,那么现在我们要在之前的基础上,给 Zinx 添加多路由的方式。
既然是多路由的模式,我们这里就需要给 MsgId 和对应的处理逻辑进行捆绑。所以我们需要一个 Map。
1 Apis map [uint32 ] ziface.IRouter
这里起名字是Apis,其中 key 就是 msgId, value 就是对应的 Router,里面应是使用者重写的 Handle 等方法。
那么这个 Apis 应该放在哪呢。
下面,我们再定义一个消息管理模块来进行维护这个Apis。
创建消息管理模块 创建消息管理模块抽象类 在zinx/ziface下创建imsghandler.go文件, 定义出我们之前图片中的方法。
1 2 3 4 5 6 package zifacetype IMsgHandler interface { DoMsgHandler(request IRequest) AddRouter(msgId uint32 ,router IRouter) }
这里面有两个方法,AddRouter()就是添加一个 msgId 和一个路由关系到 Apis 中,那么DoMsgHandler()则是调用 Router 中具体Handle()等方法的接口。
实现消息管理模块 在zinx/znet下创建msghandler.go文件。
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 package znetimport ( "fmt" "strconv" "zinx/ziface" ) type MsgHandle struct { Apis map [uint32 ] ziface.IRouter } func NewMsgHandle *MsgHandle { return &MsgHandle { Apis: make (map [uint32 ]ziface.IRouter), } } func (mh *MsgHandle) DoMsgHandler (request ziface.IRequest) { handler,ok := mh.Apis[request.GetMsgId()] if !ok { fmt.Println("api msgId= " ,request.GetMsgId()," is not Round!" ) return } handler.PreHandle(request) handler.Handle(request) handler.PostHandle(request) } func (mh *MsgHandle) AddRouter (msgId uint32 ,router ziface.IRouter) { if _,ok := mh.Apis[msgId]; ok { panic ("repeated api ,msgId = " + strconv.Itoa(int (msgId))) } mh.Apis[msgId] = router fmt.Println(" Add api msgId =" ,msgId) }
Zinx-V0.6 代码实现 首先iserver的AddRouter()的接口要稍微改一下,增添 MsgId 参数.
iserver.go:
1 2 3 4 5 6 7 8 9 10 11 12 package zifacetype IServer interface { Start() Stop() Serve() AddRouter(msgId uint32 , router IRouter) }
其次,Server类中 之前有一个Router成员 ,代表唯一的处理方法,现在应该替换成MsgHandler成员。
zinx/znet/server.go
1 2 3 4 5 6 7 8 9 10 11 12 type Server struct { Name string IPVersion string IP string Port int msgHandler ziface.IMsgHandle }
初始化 Server 自然也要更正,增加 msgHandler 初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func NewServer () ziface .IServer { utils.GlobalObject.Reload() s:= &Server { Name :utils.GlobalObject.Name, IPVersion:"tcp4" , IP:utils.GlobalObject.Host, Port:utils.GlobalObject.TcpPort, msgHandler: NewMsgHandle(), } return s }
然后当 Server 在处理 conn 请求业务的时候,创建 conn 的时候也需要把 msgHandler 作为参数传递给 Connection 对象。也就是在我们 server.go 的 Start() 方法中的 3.3 注释下进行如下修改:
1 2 3 dealConn := NewConntion(conn, cid, s.msgHandler)
最后,我们的 AddRouter 方法做了修改,所以要重新实现接口方法:
1 2 3 4 func (s *Server) AddRouter (msgId uint32 , router ziface.IRouter) { s.msgHandler.AddRouter(msgId,router) }
那么接下来就是 Connection 对象了。固然在 Connection 对象中应该有 MsgHandler 的成员,来查找消息对应的回调路由方法。
zinx/znet/connection.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 type Connection struct { Conn *net.TCPConn ConnID uint32 isClosed bool MsgHandler ziface.IMsgHandle ExitBuffChan chan bool } func NewConntion (conn *net.TCPConn, connID uint32 , msgHandler ziface.IMsgHandle) *Connection { c := &Connection{ Conn: conn, ConnID: connID, isClosed: false , MsgHandler: msgHandler, ExitBuffChan: make (chan bool , 1 ), } return c }
最后,在 conn 已经拆包之后,需要调用路由业务的时候,我们只需要让 conn 调用 MsgHandler 中的DoMsgHander()方法就好了。
zinx/znet/connection.go
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 func (c *Connection) StartReader () { fmt.Println("[Reader Goroutine is running]" ) defer fmt.Println(c.RemoteAddr().String(), "[conn Reader exit!]" ) defer c.Stop() for { dp := NewDataPack() headData := make ([]byte , dp.GetHeadLen()) if _, err := io.ReadFull(c.GetTCPConnection(), headData); err != nil { fmt.Println("read msg head error " , err) break } msg , err := dp.Unpack(headData) if err != nil { fmt.Println("unpack error " , err) break } var data []byte if msg.GetDataLen() > 0 { data = make ([]byte , msg.GetDataLen()) if _, err := io.ReadFull(c.GetTCPConnection(), data); err != nil { fmt.Println("read msg data error " , err) continue } } msg.SetData(data) req := Request{ conn:c, msg:msg, } go c.MsgHandler.DoMsgHandler(&req) } }
好了,大功告成,我们来测试一下 Zinx 的多路由设置功能吧。
使用 Zinx-V0.6 完成应用程序 这里我们既然完成了多路由模式,那么就可以进行一个服务端,多个客户端的方式进行测试我们的功能模块了。
我们在 Server 端设置 2 个路由,一个是 MsgId 为 0 的消息会执行 PingRouter{}重写的Handle()方法,一个是 MsgId 为 1 的消息会执行 HelloZinxRouter{}重写的Handle()方法。
Server.go
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 package mainimport ( "fmt" "zinx/ziface" "zinx/znet" ) type PingRouter struct { znet.BaseRouter } func (this *PingRouter) Handle (request ziface.IRequest) { fmt.Println("Call PingRouter Handle" ) fmt.Println("recv from client : msgId=" , request.GetMsgID(), ", data=" , string (request.GetData())) err := request.GetConnection().SendMsg(0 , []byte ("ping...ping...ping" )) if err != nil { fmt.Println(err) } } type HelloZinxRouter struct { znet.BaseRouter } func (this *HelloZinxRouter) Handle (request ziface.IRequest) { fmt.Println("Call HelloZinxRouter Handle" ) fmt.Println("recv from client : msgId=" , request.GetMsgID(), ", data=" , string (request.GetData())) err := request.GetConnection().SendMsg(1 , []byte ("Hello Zinx Router V0.6" )) if err != nil { fmt.Println(err) } } func main () { s := znet.NewServer() s.AddRouter(0 , &PingRouter{}) s.AddRouter(1 , &HelloZinxRouter{}) s.Serve() }
我们现在写两个客户端,分别发送 0 消息和 1 消息来进行测试 Zinx 是否能够处理 2 个不同的消息业务。
Client01.go:
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 49 50 51 52 53 54 55 56 57 package mainimport ( "fmt" "io" "net" "time" "zinx/znet" ) func main () { fmt.Println("Client Test ... start" ) time.Sleep(3 * time.Second) conn,err := net.Dial("tcp" , "127.0.0.1:7777" ) if err != nil { fmt.Println("client start err, exit!" ) return } for { dp := znet.NewDataPack() msg, _ := dp.Pack(znet.NewMsgPackage(1 ,[]byte ("Zinx V0.6 Client1 Test Message" ))) _, err := conn.Write(msg) if err !=nil { fmt.Println("write error err " , err) return } headData := make ([]byte , dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) if err != nil { fmt.Println("read head error" ) break } msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("server unpack err:" , err) return } if msgHead.GetDataLen() > 0 { msg := msgHead.(*znet.Message) msg.Data = make ([]byte , msg.GetDataLen()) _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("server unpack data err:" , err) return } fmt.Println("==> Recv Msg: ID=" , msg.Id, ", len=" , msg.DataLen, ", data=" , string (msg.Data)) } time.Sleep(1 *time.Second) } }
测试结果:
实验总结 今天我们完成了 zinx 框架的多路由模式,使得其有了对多个客户端提供服务的功能,下一小节中,我们将继续实现 zinx 的读写分离模块。
学习笔记 1 handler,ok := mh.Apis[request.GetMsgId()]