【Zinx第六章-多路由模式】Golang轻量级并发服务器框架

IT无崖子 · · 802 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

【Zinx教程目录】
Zinx源代码: https://github.com/aceld/zinx
Zinx第一章-引言
Zinx第二章-初识Zinx框架
Zinx第三章-基础路由模块
Zinx第四章-全局配置
Zinx第五章-消息封装
Zinx第六章-多路由模式
[连载中...]


​ 我们之前在已经给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代码实现

​ 首先iserverAddRouter()的接口要稍微改一下,增添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

原创声明:未经作者允许请勿转载, 如果转载请注明出处


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:IT无崖子

查看原文:【Zinx第六章-多路由模式】Golang轻量级并发服务器框架

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

802 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传