6.Design and Implementation of Zinx Multi-Router Mode
Tags: zinx, go
Published: 2023-07-03 03:27:18

### [Zinx]

[<1.Building Basic Services with Zinx Framework>](https://dev.to/aceld/1building-basic-services-with-zinx-framework-296e)
[<2. Zinx-V0.2 Simple Connection Encapsulation and Binding with Business>](https://dev.to/aceld/2-zinx-v02-simple-connection-encapsulation-and-binding-with-business-4p9g)
[<3.Design and Implementation of the Zinx Framework's Routing Module>](https://dev.to/aceld/3design-and-implementation-of-the-zinx-frameworks-routing-module-583n)
[<4.Zinx Global Configuration>](https://dev.to/aceld/4zinx-global-configuration-3jam)
[<5.Zinx Message Encapsulation Module Design and Implementation>](https://dev.to/aceld/5zinx-message-encapsulation-module-design-and-implementation-3fmc)
[<6.Design and Implementation of Zinx Multi-Router Mode>](https://dev.to/aceld/6design-and-implementation-of-zinx-multi-router-mode-19nn)
[<7. Building Zinx's Read-Write Separation Model>](https://dev.to/aceld/7-building-zinxs-read-write-separation-model-ihj)
[<8.Zinx Message Queue and Task Worker Pool Design and Implementation>](https://dev.to/aceld/8zinx-message-queue-and-task-worker-pool-design-and-implementation-12gl)
[<9. Zinx Connection Management and Property Setting>](https://dev.to/aceld/9-zinx-connection-management-and-property-setting-28ha)

### [Zinx Application - MMO Game Case Study]

[<10. Application Case Study using the Zinx Framework>](https://dev.to/aceld/10-application-case-study-using-the-zinx-framework-3ich)
[<11. MMO Online Game AOI Algorithm>](https://dev.to/aceld/11-mmo-online-game-aoi-algorithm-l7d)
[<12.Data Transmission Protocol: Protocol Buffers>](https://dev.to/aceld/12data-transmission-protocol-protocol-buffers-1jek)
[<13. MMO Game Server Application Protocol>](https://dev.to/aceld/13-mmo-game-server-application-protocol-5adl)
[<14. Building the Project and User Login>](https://dev.to/aceld/14-building-the-project-and-user-login-b7p)
[<15. World Chat System Implementation>](https://dev.to/aceld/157-world-chat-system-implementation-18li)
[<16. Online Location Information Synchronization>](https://dev.to/aceld/16-online-location-information-synchronization-3cp1)
[<17. Moving position and non-crossing grid AOI broadcasting>](https://dev.to/aceld/17-moving-position-and-non-crossing-grid-aoi-broadcasting-4nb9)
[<18. Player Logout>](https://dev.to/aceld/18-player-logout-3eln)
[<19. Movement and AOI Broadcast Across Grids>](https://dev.to/aceld/19-movement-and-aoi-broadcast-across-grids-46fe)

---

## source code
https://github.com/aceld/zinx/blob/master/examples/zinx_release/zinx-v0.6.tar.gz

---


In Zinx v0.5, the routing mode functionality has been configured, but it can only bind a single routing handler method. Obviously, this cannot meet the basic requirements of a server. Now, we need to add a multi-router mode to Zinx based on the previous implementation.

What is the multi-router mode? It means that we need to associate MsgIDs with their corresponding processing logic. This association needs to be stored in a map data structure, defined as follows:

```go
Apis map[uint32] ziface.IRouter
```

The key of the map is of type uint32 and stores the IDs of each type of message. The value is an abstraction layer of the IRouter routing business, which should be the Handle method overridden by the user. It is important to note that it is still not recommended to store specific implementation layer Router types here. The reason is that the design module is still based on abstract layer design. The name of this map is Apis.

## 6.1 Creating the Message Management Module
In this section, we will define a message management module to maintain the binding relationship in the Apis map.

### 6.1.1 Creating the Abstract Class for the Message Management Module
Create a file named `imsghandler.go` in the `zinx/ziface` directory. This file defines the abstract layer interface for message management. The interface for message management is named `IMsgHandle` and is defined as follows:

```go
//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
}
```

There are two methods inside this interface. `AddRouter()` is used to add a MsgID and a routing relationship to the Apis map. `DoMsgHandler()` is an interface that calls the specific Handle() method in the Router. The parameter is of type `IRequest` because Zinx has already put all client message requests into an `IRequest` with all the relevant message properties.

Translated into English, using the original Markdown format.

### 6.1.2 Implementation of the Message Management Module
Create a file named msghandler.go in the zinx/znet directory. This file contains the implementation code for the IMsgHandle interface. The specific implementation is as follows:

```go
//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
}
```

The `MsgHandle` struct has an `Apis` attribute, which is a map that binds MsgIDs to Routers. Next, we provide the constructor method for `MsgHandle`. The implementation code is as follows:

```go
//zinx/znet/msghandler.go

func NewMsgHandle() *MsgHandle {
	return &MsgHandle{
		Apis: make(map[uint32]ziface.IRouter),
	}
}
```

In Golang, initializing a map requires using the `make` keyword to allocate space. Please note this. The `MsgHandle` struct needs to implement the two interface methods of IMsgHandle: `DoMsgHandler()` and `AddRouter()`. The specific implementation is as follows:

```go
//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)
}
```

The `DoMsgHandler()` method consists of two steps. First, it retrieves the MsgID from the input parameter `request` and uses the Apis map to get the corresponding Router. If it cannot find a match, it indicates an unrecognized message, and the developer needs to register the callback business Router for that type of message in advance. Second, after obtaining the Router, it sequentially executes the registered `PreHandle()`, `Handle()`, and `PostHandle()` methods in the template order. Once these three methods are executed, the message processing is completed.

The message management module has now been designed. The next step is to integrate this module into the Zinx framework and upgrade it to Zinx v0.6.

Translated into English, using the original Markdown format.

## 6.2 Implementation of Zinx-V0.6 Code
First, the `AddRouter()` interface in the `IServer` abstract layer needs to be modified to include the `MsgID` parameter, as now we have added MsgID differentiation. The modification is as follows:
```go
//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)
}
```
Second, in the `Server` struct of the `Server` class, the previous Router member, representing the unique message handling business method, should be replaced with the `MsgHandler` member. After the modification, it should look like this:
```go
//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
}
```
The constructor function for initializing the `Server` also needs to be modified to include the initialization of the `msgHandler` object:

```go
//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
}
```
When the `Server` is handling connection requests, the creation of a connection also needs to pass the `msgHandler` as a parameter to the `Connection` object. The relevant code modification is as follows:
```go
//zinx/znet/server.go

// ... (omitted code)

dealConn := NewConntion(conn, cid, s.msgHandler)

// ... (omitted code)
```

Next, let's move on to the `Connection` object. The `Connection` object should have a `MsgHandler` member to look up the callback route method for a message. The modified code is as follows:

```go
//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
}
```
The constructor method for creating a connection (`NewConntion()`) also needs to pass the `MsgHandler` as a parameter to assign it to the member:
```go
//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
}
```
Finally, after reading the complete Message data from the `conn`, when encapsulating it in a `Request`, and when it's necessary to invoke the routing business, we only need to call the `DoMsgHandler()` method of `MsgHandler` from the `conn`. The relevant code modification is as follows:
```go
//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)
	}
}
```

By starting a new Goroutine to handle the DoMsgHandler() method, messages with different MsgIDs will match different processing business flows.

Translated into English, using the original Markdown format.

## 6.3 Developing an Application using Zinx-V0.6

To test the development using Zinx-V0.6, we will create a server-side application. The code is as follows:
```go
// 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()
}
```

The server sets up two routers: one for messages with MsgID 0, which will execute the Handle() method overridden in `PingRouter{}`, and another for messages with MsgID 1, which will execute the Handle() method overridden in `HelloZinxRouter{}`.

Next, we'll create two clients that will send messages with MsgID 0 and MsgID 1 to test if Zinx can handle these different message types.

The first client will be implemented in `Client0.go`, with the following code:
```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)
	}
}
```
The Client0 sends a message with MsgID 0 and the content "Zinx V0.6 Client0 Test Message".

The second client will be implemented in `Client1.go`, with the following code:
```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)
	}
}
```
The `Client1` sends a message with MsgID 1 and the content "Zinx V0.6 Client1 Test Message".

To run the server and the two clients, execute the following commands in three different terminals:

```go
$ go run Server.go
$ go run Client0.go
$ go run Client1.go
```

The server output will be as follows:

```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
// ...
```

The Client0 output will be as follows:
```go
$ 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
// ...
```
The Client1 output will be as follows:
```go
$ 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
// ...
```

From the results, it can be observed that the server code is now able to handle different message types and perform different logic based on the message ID. Client0 receives only "ping...ping...ping" as a reply, while Client1 receives only "Hello Zinx Router V0.6" as a reply.

## 7.4 Summary
In conclusion, up to Zinx V0.6, it is possible to handle different business logic based on different message IDs. Zinx provides a basic framework for server-side network communication, allowing developers to define message types, register different handle functions based on the message types, and add them to the server service object using AddRouter(), thus enabling server-side business development capabilities. The next step would be to further upgrade the internal module structure handling in Zinx.


---

## source code

https://github.com/aceld/zinx/blob/master/examples/zinx_release/zinx-v0.6.tar.gz


---
### [Zinx]

[<1.Building Basic Services with Zinx Framework>](https://dev.to/aceld/1building-basic-services-with-zinx-framework-296e)
[<2. Zinx-V0.2 Simple Connection Encapsulation and Binding with Business>](https://dev.to/aceld/2-zinx-v02-simple-connection-encapsulation-and-binding-with-business-4p9g)
[<3.Design and Implementation of the Zinx Framework's Routing Module>](https://dev.to/aceld/3design-and-implementation-of-the-zinx-frameworks-routing-module-583n)
[<4.Zinx Global Configuration>](https://dev.to/aceld/4zinx-global-configuration-3jam)
[<5.Zinx Message Encapsulation Module Design and Implementation>](https://dev.to/aceld/5zinx-message-encapsulation-module-design-and-implementation-3fmc)
[<6.Design and Implementation of Zinx Multi-Router Mode>](https://dev.to/aceld/6design-and-implementation-of-zinx-multi-router-mode-19nn)
[<7. Building Zinx's Read-Write Separation Model>](https://dev.to/aceld/7-building-zinxs-read-write-separation-model-ihj)
[<8.Zinx Message Queue and Task Worker Pool Design and Implementation>](https://dev.to/aceld/8zinx-message-queue-and-task-worker-pool-design-and-implementation-12gl)
[<9. Zinx Connection Management and Property Setting>](https://dev.to/aceld/9-zinx-connection-management-and-property-setting-28ha)

### [Zinx Application - MMO Game Case Study]

[<10. Application Case Study using the Zinx Framework>](https://dev.to/aceld/10-application-case-study-using-the-zinx-framework-3ich)
[<11. MMO Online Game AOI Algorithm>](https://dev.to/aceld/11-mmo-online-game-aoi-algorithm-l7d)
[<12.Data Transmission Protocol: Protocol Buffers>](https://dev.to/aceld/12data-transmission-protocol-protocol-buffers-1jek)
[<13. MMO Game Server Application Protocol>](https://dev.to/aceld/13-mmo-game-server-application-protocol-5adl)
[<14. Building the Project and User Login>](https://dev.to/aceld/14-building-the-project-and-user-login-b7p)
[<15. World Chat System Implementation>](https://dev.to/aceld/157-world-chat-system-implementation-18li)
[<16. Online Location Information Synchronization>](https://dev.to/aceld/16-online-location-information-synchronization-3cp1)
[<17. Moving position and non-crossing grid AOI broadcasting>](https://dev.to/aceld/17-moving-position-and-non-crossing-grid-aoi-broadcasting-4nb9)
[<18. Player Logout>](https://dev.to/aceld/18-player-logout-3eln)
[<19. Movement and AOI Broadcast Across Grids>](https://dev.to/aceld/19-movement-and-aoi-broadcast-across-grids-46fe)


---
Author:
discord: https://discord.gg/xQ8Xxfyfcz
zinx:   https://github.com/aceld/zinx
github: https://github.com/aceld
aceld's home: https://yuque.com/aceld