内容简介: 我们之前在已经给Zinx配置了路由模式,但是很惨,之前的Zinx好像只能绑定一个路由的处理业务方法。显然这是无法满足基本的服务器需求的,那么现在我们要在之前的基础上,给Zinx添加多路由的方式。 既然是多路由的模式,我们这里就需要给MsgId和对应的处理逻辑进行捆绑。所以我们需要一个Map。 这里起名字是
我们之前在已经给Zinx配置了路由模式,但是很惨,之前的Zinx好像只能绑定一个路由的处理业务方法。显然这是无法满足基本的服务器需求的,那么现在我们要在之前的基础上,给Zinx添加多路由的方式。
既然是多路由的模式,我们这里就需要给MsgId和对应的处理逻辑进行捆绑。所以我们需要一个Map。
Apis map[uint32] ziface.IRouter
这里起名字是 Apis
,其中key就是msgId, value就是对应的Router,里面应是使用者重写的Handle等方法。
那么这个Apis应该放在哪呢。
我们再定义一个消息管理模块来进行维护这个 Apis
。
6.1 创建消息管理模块
A) 创建消息管理模块抽象类
在 zinx/ziface
下创建 imsghandler.go
文件。
package ziface /* 消息管理抽象层 */ type IMsgHandle interface{ DoMsgHandler(request IRequest) //马上以非阻塞方式处理消息 AddRouter(msgId uint32, router IRouter) //为消息添加具体的处理逻辑 }
这里面有两个方法, AddRouter()
就是添加一个msgId和一个路由关系到Apis中,那么 DoMsgHandler()
则是调用Router中具体 Handle()
等方法的接口。
B) 实现消息管理模块
在 zinx/znet
下创建 msghandler.go
文件。
package znet import ( "fmt" "strconv" "zinx/ziface" ) type MsgHandle struct{ Apis map[uint32] ziface.IRouter //存放每个MsgId 所对应的处理方法的map属性 } 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 FOUND!") return } //执行对应处理方法 handler.PreHandle(request) handler.Handle(request) handler.PostHandle(request) } //为消息添加具体的处理逻辑 func (mh *MsgHandle) AddRouter(msgId uint32, router ziface.IRouter) { //1 判断当前msg绑定的API处理方法是否已经存在 if _, ok := mh.Apis[msgId]; ok { panic("repeated api , msgId = " + strconv.Itoa(int(msgId))) } //2 添加msg与api的绑定关系 mh.Apis[msgId] = router fmt.Println("Add api msgId = ", msgId) }
6.2 Zinx-V0.6代码实现
首先 iserver
的 AddRouter()
的接口要稍微改一下,增添MsgId参数
zinx/ziface/iserver.go
package ziface //定义服务器接口 type IServer interface{ //启动服务器方法 Start() //停止服务器方法 Stop() //开启业务服务方法 Serve() //路由功能:给当前服务注册一个路由业务方法,供客户端链接处理使用 AddRouter(msgId uint32, router IRouter) }
其次, Server
类中 之前有一个 Router
成员 ,代表唯一的处理方法,现在应该替换成 MsgHandler
成员
zinx/znet/server.go
type Server struct { //服务器的名称 Name string //tcp4 or other IPVersion string //服务绑定的IP地址 IP string //服务绑定的端口 Port int //当前Server的消息管理模块,用来绑定MsgId和对应的处理方法 msgHandler ziface.IMsgHandle }
初始化Server自然也要更正,增加msgHandler初始化
/* 创建一个服务器句柄 */ 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(), //msgHandler 初始化 } return s }
然后当Server在处理conn请求业务的时候,创建conn的时候也需要把msgHandler作为参数传递给Connection对象
//... dealConn := NewConntion(conn, cid, s.msgHandler) //...
那么接下来就是Connection对象了。固然在Connection对象中应该有MsgHandler的成员,来查找消息对应的回调路由方法
zinx/znet/connection.go
type Connection struct { //当前连接的socket TCP套接字 Conn *net.TCPConn //当前连接的ID 也可以称作为SessionID,ID全局唯一 ConnID uint32 //当前连接的关闭状态 isClosed bool //消息管理MsgId和对应处理方法的消息管理模块 MsgHandler ziface.IMsgHandle //告知该链接已经退出/停止的channel 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
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() //读取客户端的Msg head headData := make([]byte, dp.GetHeadLen()) if _, err := io.ReadFull(c.GetTCPConnection(), headData); err != nil { fmt.Println("read msg head error ", err) c.ExitBuffChan <- true continue } //拆包,得到msgid 和 datalen 放在msg中 msg , err := dp.Unpack(headData) if err != nil { fmt.Println("unpack error ", err) c.ExitBuffChan <- true continue } //根据 dataLen 读取 data,放在msg.Data中 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) c.ExitBuffChan <- true continue } } msg.SetData(data) //得到当前客户端请求的Request数据 req := Request{ conn:c, msg:msg, } //从绑定好的消息和对应的处理方法中执行对应的Handle方法 go c.MsgHandler.DoMsgHandler(&req) } }
好了,大功告成,我们来测试一下Zinx的多路由设置功能吧。
6.3 使用Zinx-V0.6完成应用程序
Server.go
package main import ( "fmt" "zinx/ziface" "zinx/znet" ) //ping test 自定义路由 type PingRouter struct { znet.BaseRouter } //Ping Handle func (this *PingRouter) Handle(request ziface.IRequest) { fmt.Println("Call PingRouter Handle") //先读取客户端的数据,再回写ping...ping...ping 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) } } //HelloZinxRouter Handle type HelloZinxRouter struct { znet.BaseRouter } func (this *HelloZinxRouter) Handle(request ziface.IRequest) { fmt.Println("Call HelloZinxRouter Handle") //先读取客户端的数据,再回写ping...ping...ping 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() { //创建一个server句柄 s := znet.NewServer() //配置路由 s.AddRouter(0, &PingRouter{}) s.AddRouter(1, &HelloZinxRouter{}) //开启服务 s.Serve() }
Server端设置了2个路由,一个是MsgId为0的消息会执行PingRouter{}重写的 Handle()
方法,一个是MsgId为1的消息会执行HelloZinxRouter{}重写的 Handle()
方法。
我们现在写两个客户端,分别发送0消息和1消息来进行测试Zinx是否能够处理2个不同的消息业务。
Client0.go
package main import ( "fmt" "io" "net" "time" "zinx/znet" ) /* 模拟客户端 */ func main() { fmt.Println("Client Test ... start") //3秒之后发起测试请求,给服务端开启服务的机会 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 { //发封包message消息 dp := znet.NewDataPack() msg, _ := dp.Pack(znet.NewMsgPackage(0,[]byte("Zinx V0.6 Client0 Test Message"))) _, err := conn.Write(msg) if err !=nil { fmt.Println("write error err ", err) return } //先读出流中的head部分 headData := make([]byte, dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止 if err != nil { fmt.Println("read head error") break } //将headData字节流 拆包到msg中 msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("server unpack err:", err) return } if msgHead.GetDataLen() > 0 { //msg 是有data数据的,需要再次读取data数据 msg := msgHead.(*znet.Message) msg.Data = make([]byte, msg.GetDataLen()) //根据dataLen从io中读取字节流 _, 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) } }
Client1.go
package main import ( "fmt" "io" "net" "time" "zinx/znet" ) /* 模拟客户端 */ func main() { fmt.Println("Client Test ... start") //3秒之后发起测试请求,给服务端开启服务的机会 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 { //发封包message消息 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 } //先读出流中的head部分 headData := make([]byte, dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止 if err != nil { fmt.Println("read head error") break } //将headData字节流 拆包到msg中 msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("server unpack err:", err) return } if msgHead.GetDataLen() > 0 { //msg 是有data数据的,需要再次读取data数据 msg := msgHead.(*znet.Message) msg.Data = make([]byte, msg.GetDataLen()) //根据dataLen从io中读取字节流 _, 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) } }
分别执行服务端和两个客户端
$go run Server.go
$go run Client0.go
$go run Client1.go
服务端显示结果
$ go run Server.go Add api msgId = 0 Add api msgId = 1 [START] Server name: zinx v-0.6 demoApp,listenner at IP: 127.0.0.1, Port 7777 is starting [Zinx] Version: V0.4, MaxConn: 3, MaxPacketSize: 4096 start Zinx server zinx v-0.6 demoApp succ, now listenning... Reader Goroutine is running Call PingRouter Handle recv from client : msgId= 0 , data= Zinx V0.6 Client0 Test Message Reader Goroutine is running Call HelloZinxRouter Handle recv from client : msgId= 1 , data= Zinx V0.6 Client1 Test Message Call PingRouter Handle recv from client : msgId= 0 , data= Zinx V0.6 Client0 Test Message Call HelloZinxRouter Handle recv from client : msgId= 1 , data= Zinx V0.6 Client1 Test Message Call PingRouter Handle recv from client : msgId= 0 , data= Zinx V0.6 Client0 Test Message Call HelloZinxRouter Handle recv from client : msgId= 1 , data= Zinx V0.6 Client1 Test Message
客户端0显示结果
$ go run Client0.go Client Test ... start ==> Recv Msg: ID= 0 , len= 18 , data= ping...ping...ping ==> Recv Msg: ID= 0 , len= 18 , data= ping...ping...ping ==> Recv Msg: ID= 0 , len= 18 , data= ping...ping...ping
客户端1显示结果
$ go run Client1.go Client Test ... start ==> Recv Msg: ID= 1 , len= 22 , data= Hello Zinx Router V0.6 ==> Recv Msg: ID= 1 , len= 22 , data= Hello Zinx Router V0.6 ==> Recv Msg: ID= 1 , len= 22 , data= Hello Zinx Router V0.6
关于作者:
作者: Aceld(刘丹冰)
简书号: IT无崖子
mail: danbing.at@gmail.com
github: https://github.com/aceld
原创书籍gitbook: http://legacy.gitbook.com/@aceld
原创声明:未经作者允许请勿转载, 如果转载请注明出处
以上所述就是小编给大家介绍的《【Zinx第六章-多路由模式】Golang轻量级并发服务器框架》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
追踪Linux TCP/IP代码运行
秦健 / 北京航空航天大学出版社 / 2010-4-1 / 69.00元
本书以应用程序为线索,详细描述了数据包在协议栈的分段、重组、发送、接收过程,同时分析了路由的初始化和设置过程,主要包括socket应用程序、 TCP/IP协议、路由、通知链、邻居子系统等内容。全书涵盖了协议栈的全部知识点,对于广大的读者来说这是一本极其难得的技术资料。同时,书中论述了网络设备的工作原理,解释了RTL8169和嵌入式CS8900、DM9000网卡设备的核心过程。一起来看看 《追踪Linux TCP/IP代码运行》 这本书的介绍吧!