【Zinx第六章-多路由模式】Golang輕量級併發伺服器框架
我們之前在已經給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 isrunning") 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 serverzinx v-0.6 demoAppsucc, now listenning... Reader Goroutine isrunning Call PingRouter Handle recv from client : msgId= 0 , data= Zinx V0.6 Client0 Test Message Reader Goroutine isrunning 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:[email protected]
github:https://github.com/aceld
原創書籍gitbook:http://legacy.gitbook.com/@aceld