<1.Building Basic Services with Zinx Framework>
<2. Zinx-V0.2 Simple Connection Encapsulation and Binding with Business>
<3.Design and Implementation of the Zinx Framework's Routing Module>
<4.Zinx Global Configuration>
<5.Zinx Message Encapsulation Module Design and Implementation>
<6.Design and Implementation of Zinx Multi-Router Mode>
<7. Building Zinx's Read-Write Separation Model>
在Zinx V0.5中,已配置了路由模式功能,但它只能绑定单个路由处理程序方法。显然,这无法满足服务器的基本要求。现在,我们需要根据先前的实现将多路由器模式添加到Zinx。
多路由器模式是什么?这意味着我们需要将MSGID与其相应的处理逻辑相关联。该关联需要存储在地图数据结构中,定义如下:
Apis map[uint32] ziface.IRouter
地图的密钥是UINT32类型的关键,并存储每种类型的消息的ID。该值是IRouter路由业务的抽象层,该层应为用户覆盖的手柄方法。重要的是要注意,仍然不建议在此处存储特定的实现层路由器类型。原因是设计模块仍然基于抽象层设计。该地图的名称是API。
6.1创建消息管理模块
在本节中,我们将定义一个消息管理模块,以维护API地图中的绑定关系。
6.1.1为消息管理模块创建抽象类
在zinx/ziface
目录中创建一个名为imsghandler.go
的文件。该文件定义了用于消息管理的抽象层接口。消息管理的接口命名为IMsgHandle
,定义如下:
//zinx/ziface/imsghandler.go
package ziface
/*
Abstract layer for message management
*/
type IMsgHandle interface {
DoMsgHandler(request IRequest) // Process messages immediately in a non-blocking manner
AddRouter(msgId uint32, router IRouter) // Add specific handling logic for a message
}
该接口内有两种方法。 AddRouter()
用于在API地图上添加MSGID和路由关系。 DoMsgHandler()
是一个接口,该接口调用路由器中的特定句柄()方法。该参数是类型IRequest
的,因为zinx已经将所有客户端消息请求放入具有所有相关消息属性的IRequest
中。
使用原始的Markdown格式翻译成英文。
6.1.2消息管理模块的实现
在zinx/znet目录中创建一个名为msghandler.go的文件。该文件包含IMSGHANDLE接口的实现代码。具体实现如下:
//zinx/znet/msghandler.go
package znet
import (
"fmt"
"strconv"
"zinx/ziface"
)
type MsgHandle struct {
Apis map[uint32]ziface.IRouter // Map to store the handler methods for each MsgID
}
MsgHandle
struct具有一个Apis
属性,该属性是将MSGID绑定到路由器的地图。接下来,我们为MsgHandle
提供构造方法。实现代码如下:
//zinx/znet/msghandler.go
func NewMsgHandle() *MsgHandle {
return &MsgHandle{
Apis: make(map[uint32]ziface.IRouter),
}
}
在Golang中,初始化地图需要使用make
关键字来分配空间。请注意。 MsgHandle
结构需要实现IMSGHANDLE:DoMsgHandler()
和AddRouter()
的两个接口方法。具体实现如下:
//zinx/znet/msghandler.go
// Process messages in a non-blocking manner
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
}
// Execute the corresponding handler methods
handler.PreHandle(request)
handler.Handle(request)
handler.PostHandle(request)
}
// Add specific handling logic for a message
func (mh *MsgHandle) AddRouter(msgId uint32, router ziface.IRouter) {
// 1. Check if the current msg's API handler method already exists
if _, ok := mh.Apis[msgId]; ok {
panic("repeated api, msgId = " + strconv.Itoa(int(msgId)))
}
// 2. Add the binding relationship between msg and api
mh.Apis[msgId] = router
fmt.Println("Add api msgId =", msgId)
}
DoMsgHandler()
方法由两个步骤组成。首先,它从输入参数request
检索msgid,并使用API映射获取相应的路由器。如果找不到匹配项,则指示未识别的消息,并且开发人员需要为此类型的消息注册回调业务路由器。其次,在获得路由器后,它在模板顺序中顺序执行注册的PreHandle()
,Handle()
和PostHandle()
方法。执行这三个方法后,消息处理完成。
现在已经设计了消息管理模块。下一步是将此模块集成到Zinx框架中,并将其升级到Zinx V0.6。
使用原始的Markdown格式翻译成英文。
6.2 ZINX-V0.6代码的实现
首先,需要修改IServer
抽象层中的AddRouter()
接口以包括MsgID
参数,因为现在我们添加了MSGID差异化。修改如下:
//zinx/ziface/iserver.go
package ziface
// Server interface definition
type IServer interface {
// Start the server
Start()
// Stop the server
Stop()
// Start the business service
Serve()
// Route function: Register a route business method for the current server to handle client connections
AddRouter(msgId uint32, router IRouter)
}
第二,在Server
类的Server
结构中,以前的路由器成员,代表唯一消息处理业务方法的MsgHandler
成员。修改后,应该看起来像这样:
//zinx/znet/server.go
type Server struct {
// Server name
Name string
// IP version, e.g., "tcp4" or "tcp6"
IPVersion string
// IP address the server is bound to
IP string
// Port the server is bound to
Port int
// Message handler module of the current server, used to bind MsgIDs with corresponding handling methods
msgHandler ziface.IMsgHandle
}
还需要修改用于初始化Server
的构造函数函数,以包括msgHandler
对象的初始化:
//zinx/znet/server.go
/*
Create a server handler
*/
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(), // Initialize msgHandler
}
return s
}
当Server
处理连接请求时,连接的创建还需要将msgHandler
作为参数传递给Connection
对象。相关代码修改如下:
//zinx/znet/server.go
// ... (omitted code)
dealConn := NewConntion(conn, cid, s.msgHandler)
// ... (omitted code)
接下来,让我们继续转到Connection
对象。 Connection
对象应具有一个MsgHandler
成员来查找消息的回调路由方法。修改的代码如下:
//zinx/znet/connection.go
type Connection struct {
// Current socket TCP connection
Conn *net.TCPConn
// Current connection ID, also known as SessionID, which is globally unique
ConnID uint32
// Current connection closed state
isClosed bool
// Message handler, which manages MsgIDs and corresponding handling methods
MsgHandler ziface.IMsgHandle
// Channel to notify that the connection has exited/stopped
ExitBuffChan chan bool
}
创建连接的构造方法方法(NewConntion()
)还需要通过MsgHandler
作为参数将其分配给成员:
//zinx/znet/connection.go
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
的完整消息数据封装在Request
中时,当有必要调用路由业务时,我们只需要调用conn
的DoMsgHandler()
方法的DoMsgHandler()
方法。相关代码修改如下:
//zinx/znet/connection.go
func (c *Connection) StartReader() {
// ... (omitted code)
for {
// ... (omitted code)
// Get the Request data for the current client request
req := Request{
conn: c,
msg: msg,
}
// Execute the corresponding Handle method from the bound message and its handling method
go c.MsgHandler.DoMsgHandler(&req)
}
}
通过启动新的goroutine来处理domsghandler()方法,带有不同msgids的消息将匹配不同的处理业务流。
使用原始的Markdown格式翻译成英文。
6.3使用ZINX-V0.6开发应用程序
要使用ZINX-V0.6测试开发,我们将创建一个服务器端应用程序。代码如下:
// Server.go
package main
import (
"fmt"
"zinx/ziface"
"zinx/znet"
)
// Ping test custom router
type PingRouter struct {
znet.BaseRouter
}
// Ping Handle
func (this *PingRouter) Handle(request ziface.IRequest) {
fmt.Println("Call PingRouter Handle")
// Read client data first, then write back 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")
// Read client data first, then write back 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() {
// Create a server handle
s := znet.NewServer()
// Configure routers
s.AddRouter(0, &PingRouter{})
s.AddRouter(1, &HelloZinxRouter{})
// Start the server
s.Serve()
}
服务器设置了两个路由器:一个用于MSGID 0的消息,它将执行PingRouter{}
中覆盖的handle()方法,另一个用于使用MSGID 1的消息,该消息将执行HelloZinxRouter{}
中的handle()方法。<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /p>
接下来,我们将创建两个客户端,这些客户端将以MSGID 0和MSGID 1发送消息,以测试Zinx是否可以处理这些不同的消息类型。
第一个客户端将在Client0.go
中实现,并具有以下代码:
//Client0.go
package main
import (
"fmt"
"io"
"net"
"time"
"zinx/znet"
)
/*
Simulate client
*/
func main() {
fmt.Println("Client Test ... start")
// Wait for 3 seconds before sending the test request to give the server a chance to 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 {
// Pack the 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
}
// Read the head part from the stream
headData := make([]byte, dp.GetHeadLen())
_, err = io.ReadFull(conn, headData) // ReadFull fills the buffer until it's full
if err != nil {
fmt.Println("read head error")
break
}
// Unpack the headData into a message
msgHead, err := dp.Unpack(headData)
if err != nil {
fmt.Println("server unpack err:", err)
return
}
if msgHead.GetDataLen() > 0 {
// The message has data, so we need to read the data part
msg := msgHead.(*znet.Message)
msg.Data = make([]byte, msg.GetDataLen())
// Read the data bytes from the stream based on the dataLen
_, 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)
}
}
客户端0发送带有MSGID 0的消息和内容“ Zinx v0.6客户端测试消息”。
第二个客户端将在Client1.go
中实现,并具有以下代码:
//Client1.go
package main
import (
"fmt"
"io"
"net"
"time"
"zinx/znet"
)
/*
Simulate client
*/
func main() {
fmt.Println("Client Test ... start")
// Wait for 3 seconds before sending the test request to give the server a chance to 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 {
// Pack the 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
}
// Read the head part from the stream
headData := make([]byte, dp.GetHeadLen())
_, err = io.ReadFull(conn, headData) // ReadFull fills the buffer until it's full
if err != nil {
fmt.Println("read head error")
break
}
// Unpack the headData into a message
msgHead, err := dp.Unpack(headData)
if err != nil {
fmt.Println("server unpack err:", err)
return
}
if msgHead.GetDataLen() > 0 {
// The message has data, so we need to read the data part
msg := msgHead.(*znet.Message)
msg.Data = make([]byte, msg.GetDataLen())
// Read the data bytes from the stream based on the dataLen
_, 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
发送带有MSGID 1的消息和内容“ Zinx v0.6客户端测试消息”。
运行服务器和两个客户端,在三个不同的终端中执行以下命令:
$ 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
// ...
从结果中可以看出,服务器代码现在能够处理不同的消息类型并根据消息ID执行不同的逻辑。 Client0仅收到“ ping ... ping ... ping”作为答复,而client1仅收到“你好zinx路由器v0.6”作为答复。
7.4摘要
总而言之,直到Zinx V0.6,可以根据不同的消息ID处理不同的业务逻辑。 Zinx为服务器端网络通信提供了一个基本框架,使开发人员能够定义消息类型,根据消息类型注册不同的句柄功能,然后使用AddRouter()将它们添加到服务器服务对象,从而启用服务器端业务开发功能。下一步将是进一步升级Zinx中的内部模块结构。
<1.Building Basic Services with Zinx Framework>
<2. Zinx-V0.2 Simple Connection Encapsulation and Binding with Business>
<3.Design and Implementation of the Zinx Framework's Routing Module>
<4.Zinx Global Configuration>
<5.Zinx Message Encapsulation Module Design and Implementation>
<6.Design and Implementation of Zinx Multi-Router Mode>
<7. Building Zinx's Read-Write Separation Model>
未完待续...
作者:
不和谐:https://discord.gg/xQ8Xxfyfcz
zinx:https://github.com/aceld/zinx
github:https://github.com/aceld
Aceld的家:https://yuque.com/aceld