Zinx源代码
github
https://github.com/aceld/zinx
gitee码云
https://gitee.com/Aceld/zinx
在线开发教程
【B站】
zinx视频教程-Golang轻量级TCP服务器框架-适合自学者
【YouTube】
zinx开发YouTube中国版
微信端文档
【Zinx教程目录】
完整教程电子版(在线高清)-下载
Zinx框架视频教程(框架篇)(完整版下载)链接在下面正文
Zinx框架视频教程(应用篇)(完整版下载)链接在下面正文
Zinx开发API文档
Zinx第一章-引言
Zinx第二章-初识Zinx框架
Zinx第三章-基础路由模块
Zinx第四章-全局配置
Zinx第五章-消息封装
Zinx第六章-多路由模式
Zinx第七章-读写分离模型
Zinx第八章-消息队列及多任务
Zinx第九章-链接管理
Zinx第十章-连接属性设置
【Zinx应用案例-MMO多人在线游戏】
(1)案例介绍
(2)AOI兴趣点算法
(3)数据传输协议protocol buffer
(4)Proto3协议定义
(5)构建项目及用户上线
(6)世界聊天
(7)上线位置信息同步
(8)移动位置与AOI广播
(9)玩家下线
(10)模拟客户端AI模块
我们之前在已经给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: [email protected]
github: https://github.com/aceld
原创书籍gitbook: http://legacy.gitbook.com/@aceld
原创声明:未经作者允许请勿转载, 如果转载请注明出处