3.Design and Implementation of the Zinx Framework's Routing Module
Tags: go, zinx
Published: 2023-06-14 07:17:32

### [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.3.tar.gz

---


---

In Zinx, it is necessary to provide users with a customized connection handling interface. Clearly, binding the business handling methods to the format `type HandFunc func(*net.TCPConn, []byte, int)` error is not sufficient. Here, we need to define some `interface{}` to allow users to provide connection handling methods in any format.

Using only the `func` type clearly cannot meet the development requirements. Now, we need to create several abstract interface classes.

## 3.1 IRequest - Abstract Class for Message Requests

In this section, we will combine the connection information and request data of a client's request into a request class called Request. The benefit of this approach is that we can obtain all the client's request information from Request, which also serves as a basis for future framework expansion. If the client has additional meaningful data, it can be stored in this Request class. It can be understood that every time the client sends a complete request, Zinx will put them together in a Request structure.

### 3.1.1 Creating the Abstract IRequest Layer

Create a new file irequest.go in the ziface directory. This file represents the IRequest interface and is located in the abstract layer directory of Zinx (ziface). The interface is defined as follows:

```go
//zinx/ziface/irequest.go
package ziface

/*
    IRequest interface:
    This interface encapsulates the client's connection 
    information and request data into a Request.
*/

type IRequest interface {
    GetConnection() IConnection // Get the connection information of the request
    GetData() []byte // Get the data of the request message
}
```

It is evident that the current abstraction layer only provides two getter methods, indicating that two members must be present. One is the client's connection, and the other is the data passed by the client. Of course, as the Zinx framework becomes more feature-rich, new members should be added to it.

> Note the interface design of GetConnection() in IRequest. It returns IConnection instead of Connection. Although it has no impact on program implementation when returning the latter, considering design patterns and architectural scalability, it is recommended that the interface in the abstraction layer still depends on the abstraction layer. It is primarily about programming against the abstraction layer, so the returned concrete Connection object only needs to be a subclass of IConnection. This also demonstrates the application of the polymorphism feature of object-oriented programming. Therefore, the design of this interface should comply with the "Liskov Substitution Principle."

### 3.1.2 Implementation of the Request class
Create a new file named `request.go` in the znet directory, which will primarily implement the Request class of Zinx. The code is as follows:

```go
// zinx/znet/request.go
package znet

import "zinx/ziface"

type Request struct {
	conn ziface.IConnection // The established connection with the client
	data []byte             // Data requested by the client
}

// GetConnection retrieves the connection information of the request
func (r *Request) GetConnection() ziface.IConnection {
	return r.conn
}

// GetData retrieves the data of the request message
func (r *Request) GetData() []byte {
	return r.data
}
```
Now the Request class is created and it will be used later when configuring the router.

## 3.2 IRouter - Abstract Router Configuration

In this section, we will implement a very basic routing functionality in Zinx, aiming to quickly introduce routing into the framework.

### 3.2.1 Creating the Abstract IRouter Layer

Create a file named `irouter.go` in the `ziface` directory. This file will define the interface for the routing functionality. The code implementation is as follows:
```go
// zinx/ziface/irouter.go
package ziface

/*
	Router interface.
	Routers are used by framework users to configure custom business methods for a connection.
	The IRequest in the router contains the connection information and the request data of that connection.
*/
type IRouter interface {
	PreHandle(request IRequest)  // Hook method executed before handling the conn business
	Handle(request IRequest)     // Method to handle the conn business
	PostHandle(request IRequest) // Hook method executed after handling the conn business
}
```

The role of the Router is to allow the server application to configure the handling business method for a particular connection. In previous versions of Zinx (V0.2), the method for handling connection requests was fixed. Now, it can be customized, and there are three interfaces that can be overridden:

* `Handle`: This is the main business function for handling the current connection.
* `PreHandle`: If there is a need for pre-processing before the main business function, this method can be overridden.
* `PostHandle`: If there is a need for post-processing after the main business function, this method can be overridden.

Each method has a unique parameter, `IRequest` object, which represents the connection and request data coming from the client, and serves as the input data for the business methods.

### 3.2.2 Implementing the Router Class

Create a file named `router.go` in the `znet` directory. This file contains the implementation of the `Router` class. The code is as follows:

```go
// zinx/znet/router.go
package znet

import "zinx/ziface"

// When implementing a router, embed this base class and override its methods as needed
type BaseRouter struct{}

func (br *BaseRouter) PreHandle(req ziface.IRequest) {}
func (br *BaseRouter) Handle(req ziface.IRequest) {}
func (br *BaseRouter) PostHandle(req ziface.IRequest) {}
```

The `BaseRouter` class serves as the parent class for all subclasses implementing the `Router`. It implements the three interfaces of `IRouter`, but the method implementations in `BaseRouter` are empty. This is because some implementation layer routers may not require `PreHandle` or `PostHandle` methods. By inheriting `BaseRouter`, it is not necessary to implement these methods in order to instantiate a router.

At this point, the directory structure of Zinx should look as follows:

```bash
.
├── README.md
├── ziface
│   ├── iconnection.go
│   ├── irequest.go
│   ├── irouter.go
│   └── iserver.go
└── znet
    ├── connection.go
    ├── request.go
    ├── router.go
    ├── server.go
    └── server_test.go
```


## 3.3 Integrating Basic Routing Functionality into Zinx-V0.3
Now that `IRequest` and `IRouter` have been defined, the next step is to integrate them into the Zinx framework.

### 3.3.1 Adding Router Registration Functionality to IServer

The `IServer` interface needs to add an abstract method `AddRouter()`. The purpose is to allow users of the Zinx framework to customize a router for handling business methods. The code is as follows:

```go
// zinx/ziface/iserver.go
package ziface

// Definition of the server interface
type IServer interface {
	// Start the server
	Start()
	// Stop the server
	Stop()
	// Serve the business services
	Serve()
	// Router function: Register a router business method for the current server to handle client connections
	AddRouter(router IRouter)
}
```

The `AddRouter()` method takes an `IRouter` as its parameter, which is an abstract layer and not a concrete implementation.

### 3.3.2 Adding Router Member to Server Class
With the abstract method in place, the `Server` class needs to implement it and add a Router member. The modified `Server` data structure is as follows:

```go
// zinx/znet/server.go

// Implementation of the iServer interface, defining a Server service class
type Server struct {
	// Server name
	Name string
	// IP version (tcp4 or other)
	IPVersion string
	// IP address that the service binds to
	IP string
	// Port that the service binds to
	Port int
	// Callback router bound by the user for the current Server, which is responsible for handling business for registered connections
	Router ziface.IRouter
}
```

Correspondingly, the `NewServer()` method should include default initialization and assignment for the `Router`:

```go
// zinx/znet/server.go

/*
  Create a server handle
 */
func NewServer(name string) ziface.IServer {
	s := &Server{
		Name:      name,
		IPVersion: "tcp4",
		IP:        "0.0.0.0",
		Port:      7777,
		Router:    nil, // Default not specified
	}

	return s
}
```

The `Server` class needs to implement the `AddRouter()` method to add the router. Here, it simply needs to be registered in the `s.Router` member. The `AddRouter()` method is used by developers for business functionality. The code is as follows:
```go
// zinx/znet/server.go

// Router function: Register a router business method for the current server to handle client connections
func (s *Server) AddRouter(router ziface.IRouter) {
	s.Router = router

	fmt.Println("Add Router success!")
}
```

### 3.3.3 Binding a Router Member to the Connection Class

Since the `Server` has integrated the Router functionality, the `Connection` class also needs to be associated with the Router. Modify the data structure of the Connection implementation class and add the `Router` member. The code modifications are as follows:

```go
// zinx/znet/connection.go

type Connection struct {
	// TCP socket for the current connection
	Conn *net.TCPConn
	// Connection ID, also known as SessionID, globally unique
	ConnID uint32
	// Connection close status
	isClosed bool

	// Router for handling the connection
	Router ziface.IRouter

	// Channel to notify that the connection has exited/stopped
	ExitBuffChan chan bool
}
```
When creating a new connection in `NewConnection()`, the `Router` parameter needs to be passed. Modify the code as follows:
```go
// zinx/znet/connection.go

// Method to create a connection
func NewConnection(conn *net.TCPConn, connID uint32, router ziface.IRouter) *Connection {
	c := &Connection{
		Conn:          conn,
		ConnID:        connID,
		isClosed:      false,
		Router:        router,
		ExitBuffChan:  make(chan bool, 1),
	}

	return c
}
```

### 3.3.4 Calling the Registered Router to Handle Business in Connection

After integrating the Router member into the `Connection` class, the registered Router can be called in the logic of `StartReader()` that handles the data reading. The following code adds the invocation of the Router after reading the data:

```go
// 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 {
		// Read the maximum data into the buffer
		buf := make([]byte, 512)
		_, err := c.Conn.Read(buf)
		if err != nil {
			fmt.Println("recv buf err ", err)
			c.ExitBuffChan <- true
			continue
		}
		// Get the Request data of the current client request
		req := Request{
			conn: c,
			data: buf,
		}
		// Find the corresponding Handle registered with the bound Conn from the Routers
		go func(request ziface.IRequest) {
			// Execute the registered router methods
			c.Router.PreHandle(request)
			c.Router.Handle(request)
			c.Router.PostHandle(request)
		}(&req)
	}
}
```

After reading the client data in the `StartReader()` method of the connection, the data and the connection are encapsulated in a `Request` as input data for the Router:

```go
// Get the Request data of the current client request
req := Request{
	conn: c,
	data: buf,
}
```

Then, a Goroutine is started to invoke the registered router business logic in the Zinx framework:

```go
// Find the corresponding Handle registered with the bound Conn from the Routers
go func(request ziface.IRequest) {
	// Execute the registered router methods
	c.Router.PreHandle(request)
	c.Router.Handle(request)
	c.Router.PostHandle(request)
}(&req)
```

If the Router has overridden `PreHandle()`, it will be called; otherwise, the empty method of `BaseRouter()` will be called. The same logic applies to `Handle()` and `PostHandle()`.

## 3.4 Passing the Router Parameter to Connection in Server

After the Server successfully establishes a new connection, a new Connection needs to be created, and the current Router parameter should be passed to the connection. The main modification is in the `NewConnection()` method. The relevant key code changes are as follows:

```go
// zinx/znet/server.go

package znet

import (
	"fmt"
	"net"
	"time"
	"zinx/ziface"
)

// Start the network service
func (s *Server) Start() {
	// ... (partial code omitted)

	// Start a goroutine to handle server listener business
	go func() {
		// 1. Get a TCP Addr
		// ... (partial code omitted)

		// 2. Listen to the server address
		// ... (partial code omitted)
		
		// 3. Start the server network connection business
		for {
			// 3.1 Block and wait for client connection requests
			conn, err := listener.AcceptTCP()
			if err != nil {
				fmt.Println("Accept err ", err)
				continue
			}

			// 3.2 TODO: Set the server's maximum connection control in Server.Start()
			// If the maximum connection is exceeded, close this new connection

			// 3.3 Handle the business method of the new connection request
			// The handler and conn should be bound at this point
			dealConn := NewConnection(conn, cid, s.Router)
			cid++

			// 3.4 Start handling the business of the current connection
			go dealConn.Start()
		}
	}()
}
```

## 3.5 Completing the Application with Zinx-V0.3

Now, with Zinx, a simple routing functionality can be configured.

### 3.5.1 Testing the Server Application Built with Zinx
Now let's continue with the development of the Server.go application. Developers need to define a custom Router class and implement the PreHandle(), Handle(), and PostHandle() methods to handle specific business logic for data requests in Zinx. 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 // Must embed BaseRouter first
}

// Test PreHandle
func (this *PingRouter) PreHandle(request ziface.IRequest) {
	fmt.Println("Call Router PreHandle")

	_, err := request.GetConnection().GetTCPConnection().Write([]byte("before ping ....\n"))
	if err != nil {
		fmt.Println("callback ping ping ping error")
	}
}

// Test Handle
func (this *PingRouter) Handle(request ziface.IRequest) {
	fmt.Println("Call PingRouter Handle")

	_, err := request.GetConnection().GetTCPConnection().Write([]byte("ping...ping...ping\n"))
	if err != nil {
		fmt.Println("callback ping ping ping error")
	}
}

// Test PostHandle
func (this *PingRouter) PostHandle(request ziface.IRequest) {
	fmt.Println("Call Router PostHandle")

	_, err := request.GetConnection().GetTCPConnection().Write([]byte("After ping .....\n"))
	if err != nil {
		fmt.Println("callback ping ping ping error")
	}
}

func main() {
	// Create a server instance
	s := znet.NewServer("[zinx V0.3]")

	s.AddRouter(&PingRouter{})

	// Start the server
	s.Serve()
}
```

The above code defines a custom router similar to the Ping operation. When the client sends data, the server's business logic is to return "ping...ping...ping" to the client. To test it, the current router also implements the PreHandle() and PostHandle() methods. In practice, Zinx will use the template method design pattern to call the PreHandle(), Handle(), and PostHandle() methods sequentially in the framework.

### 3.5.2 Starting the Server and Client

#### 1 Start Server.go
Execute the following command to start the Server program with the registered Router:
```bash
go run Server.go
```

#### 2 Start Client.go
The client code in Client.go remains unchanged from the previous version. Execute the following command to run the program:
```bash
go run Client.go
```

#### 3 Server Output

```bash
$ go run Server.go
Add Router succ!
[START] Server listener at IP: 0.0.0.0, Port 7777, is starting
start Zinx server [zinx V0.3] succ, now listening...
Reader Goroutine is running
Call Router PreHandle
Call PingRouter Handle
Call Router PostHandle
Call Router PreHandle
Call PingRouter Handle
Call Router PostHandle
Call Router PreHandle
Call PingRouter Handle
Call Router PostHandle
Call Router PreHandle
Call PingRouter Handle
Call Router PostHandle
Call Router PreHandle
Call PingRouter Handle
Call Router PostHandle
...
```

#### 4 Client Output
```bash
$ go run Client.go
Client Test ... start
Server callback: before ping ...., cnt = 17
Server callback: ping...ping...ping
After ping ....., cnt = 36
Server callback: before ping ....
ping...ping...ping
After ping ....., cnt = 53
Server callback: before ping ....
ping...ping...ping
After ping ....., cnt = 53
Server callback: before ping ....
ping...ping...ping
After ping ....., cnt = 53
...
```

## 3.6 Conclusion

Now Zinx framework has routing functionality, although currently only one Router can be configured. However, Zinx now allows developers to freely configure business logic. In the next steps, Zinx will add the ability to configure multiple routers.


---

## source code
https://github.com/aceld/zinx/blob/master/examples/zinx_release/zinx-v0.3.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