9. Zinx Connection Management and Property Setting
Tags: zinx, go
Published: 2023-08-15 02:33:25

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

---


In this chapter, we will add connection limitations to the Zinx framework. If the number of client connections exceeds a certain threshold, Zinx will reject new connection requests in order to ensure timely response for existing connections. Additionally, we will introduce connection properties to Zinx, allowing developers to associate business-specific parameters with connections for easy access during business processing.

## 9.1 Connection Management
We need to create a connection management module for Zinx, which consists of an abstract layer and an implementation layer. We'll start by implementing the abstract layer in the `iconnmanager.go` file located in the `zinx/ziface` directory:
```go
// zinx/ziface/iconnmanager.go

package ziface

/*
   Connection management abstract layer
*/
type IConnManager interface {
    Add(conn IConnection)                            // Add a connection
    Remove(conn IConnection)                         // Remove a connection
    Get(connID uint32) (IConnection, error)           // Get a connection using the connection ID
    Len() int                                        // Get the current number of connections
    ClearConn()                                      // Remove and stop all connections
}
```

The `IConnManager` interface defines the following methods:

* `Add`: Add a connection to the connection manager.
* `Remove`: Remove a connection from the connection manager. This does not close the connection; it simply removes it from the management.
* `Get`: Retrieve a connection object based on the connection ID.
* `Len`: Get the total number of connections managed by the connection manager.
* `ClearConn`: Remove all connections from the manager and close them.

Next, we'll create the implementation layer for IConnManager in the `connmanager.go` file in the `zinx/znet` directory:

```go
// zinx/znet/connmanager.go

package znet

import (
    "errors"
    "fmt"
    "sync"
    "zinx/ziface"
)

/*
   Connection manager module
*/
type ConnManager struct {
    connections map[uint32]ziface.IConnection // Map to hold connection information
    connLock    sync.RWMutex                  // Read-write lock for concurrent access to the map
}
```

The `ConnManager` struct contains a `connections` map that stores all the connection information. The key is the connection ID, and the value is the connection itself. The `connLock` is a read-write lock used to protect concurrent access to the map.

The constructor for `ConnManager` initializes the map using the `make` function:

```go
// zinx/znet/connmanager.go

/*
   Create a connection manager
*/
func NewConnManager() *ConnManager {
    return &ConnManager{
        connections: make(map[uint32]ziface.IConnection),
    }
}
```

The `Add` method, which adds a connection to the manager, is implemented as follows:

```go
// zinx/znet/connmanager.go

// Add a connection
func (connMgr *ConnManager) Add(conn ziface.IConnection) {
    // Protect shared resource (map) with a write lock
    connMgr.connLock.Lock()
    defer connMgr.connLock.Unlock()

    // Add the connection to ConnManager
    connMgr.connections[conn.GetConnID()] = conn

    fmt.Println("Connection added to ConnManager successfully: conn num =", connMgr.Len())
}
```

Since Go's standard library map is not thread-safe, we need to use a lock to protect concurrent write operations. Here, we use a write lock (`connLock`) to ensure mutual exclusion when modifying the map.

The `Remove` method, which removes a connection from the manager, is implemented as follows:

```go
// zinx/znet/connmanager.go

// Remove a connection
func (connMgr *ConnManager) Remove(conn ziface.IConnection) {
    // Protect shared resource (map) with a write lock
    connMgr.connLock.Lock()
    defer connMgr.connLock.Unlock()

    // Remove the connection
    delete(connMgr.connections, conn.GetConnID())

    fmt.Println("Connection removed: ConnID =", conn.GetConnID(), "successfully: conn num =", connMgr.Len())
}
```

The `Remove` method simply removes the connection from the map without stopping the connection's business processing.

The `Get` and `Len` methods are implemented as follows:

```go
// zinx/znet/connmanager.go

// Get a connection using the connection ID
func (connMgr *ConnManager) Get(connID uint32) (ziface.IConnection, error) {
    // Protect shared resource (map) with a read lock
    connMgr.connLock.RLock()
    defer connMgr.connLock.RUnlock()

    if conn, ok := connMgr.connections[connID]; ok {
        return conn, nil
    } else {
        return nil, errors.New("connection not found")
    }
}

// Get the current number of connections
func (connMgr *ConnManager) Len() int {
    return len(connMgr.connections)
}
```

The `Get` method uses a read lock (`connLock.RLock()`) to allow concurrent read access to the map, ensuring data consistency. If a connection with the given ID is found, it is returned; otherwise, an error is returned.

The `ClearConn` method is implemented as follows:

```go
// zinx/znet/connmanager.go

// Remove and stop all connections
func (connMgr *ConnManager) ClearConn() {
    // Protect shared resource (map) with a write lock
    connMgr.connLock.Lock()
    defer connMgr.connLock.Unlock()

    // Stop and remove all connections
    for connID, conn := range connMgr.connections {
        // Stop the connection
        conn.Stop()
        // Remove the connection
        delete(connMgr.connections, connID)
    }

    fmt.Println("All connections cleared successfully: conn num =", connMgr.Len())
}
```

The `ClearConn` method stops each connection's business processing by calling `conn.Stop()`, and then removes all connections from the map.

### 9.1.2 Integrating Connection Management Module into Zinx

#### 1. Adding ConnManager to Server
We need to add the `ConnManager` to the `Server` struct and initialize it in the server's constructor. The `Server` struct in the `zinx/znet/server.go` file will have a new member property called `ConnMgr`, which is of type `ziface.IConnManager`:

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

// Server is a server service class implementing the IServer interface
type Server struct {
    // Server name
    Name string
    // IP version (e.g., tcp4 or other)
    IPVersion string
    // IP address to bind the server to
    IP string
    // Port to bind the server to
    Port int
    // Message handler for binding MsgID and corresponding processing methods
    MsgHandler ziface.IMsgHandle
    // Connection manager for the server
    ConnMgr ziface.IConnManager
}
```
In the server's constructor `NewServer()`, we need to initialize the `ConnMgr`:

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

// NewServer creates a server instance
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(),
        ConnMgr:   NewConnManager(), // Create a ConnManager
    }
    return s
}
```

The `NewServer()` function creates a new server instance and initializes the `ConnMgr` property.

To provide access to the `ConnMgr` from the server, we need to add a method `GetConnMgr()` to the `IServer `interface in the `zinx/ziface/iserver.go` file:

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

type IServer interface {
    // Start the server
    Start()
    // Stop the server
    Stop()
    // Serve the business services
    Serve()
    // Register a router business method for the current service, used by client connection processing
    AddRouter(msgID uint32, router IRouter)
    // Get the connection manager
    GetConnMgr() IConnManager
}
```
The `GetConnMgr()` method should return the `ConnMgr` property of the server:
```go
// zinx/znet/server.go

// GetConnMgr returns the connection manager
func (s *Server) GetConnMgr() ziface.IConnManager {
    return s.ConnMgr
}
```
By implementing the `GetConnMgr()` method, we provide a way to access the connection manager from the server.

Because the connection (`Connection`) sometimes needs access to the connection manager (`ConnMgr`) in the server (`Server`), we need to establish a mutual reference relationship between the `Server` and `Connection` objects. In the `Connection` struct, we will add a member called `TcpServer`, which represents the server that the current connection belongs to. Add the `TcpServer` member to the `Connection` struct in the `zinx/znet/connection.go` file as follows:

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

type Connection struct {
    // The server to which the current connection belongs
    TcpServer    ziface.IServer  // Add this line to indicate the server to which the connection belongs
    // The TCP socket of the current connection
    Conn         *net.TCPConn
    // The ID of the current connection (also known as SessionID, globally unique)
    ConnID       uint32
    // The closing state of the current connection
    isClosed     bool
    // The message handler that manages MsgID and corresponding processing methods
    MsgHandler   ziface.IMsgHandle
    // The channel that informs that the connection has exited/stopped
    ExitBuffChan chan bool
    // The unbuffered channel used for message communication between the reading and writing goroutines
    msgChan      chan []byte
    // The buffered channel used for message communication between the reading and writing goroutines
    msgBuffChan  chan []byte
}
```

By adding the `TcpServer` member to the `Connection` struct, we establish a reference to the server that the connection belongs to. The `TcpServer` property is of type `ziface.IServer`.

#### 2. Adding Connection to the Connection Manager

When initializing a connection, we need to add the connection to the server's connection manager. In the `zinx/znet/connection.go` file, modify the `NewConnection()` function to include the server object:

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

// NewConnection creates a connection
func NewConnection(server ziface.IServer, conn *net.TCPConn, connID uint32, msgHandler ziface.IMsgHandle) *Connection {
    c := &Connection{
        TcpServer:    server,      // Set the server object
        Conn:         conn,
        ConnID:       connID,
        isClosed:     false,
        MsgHandler:   msgHandler,
        ExitBuffChan: make(chan bool, 1),
        msgChan:      make(chan []byte),
        msgBuffChan:  make(chan []byte, utils.GlobalObject.MaxMsgChanLen),
    }

    // Add the newly created connection to the connection manager
    c.TcpServer.GetConnMgr().Add(c)
    return c
}
```

In the `NewConnection()` function, we pass the server object as a parameter and set it in the `TcpServer` property of the connection object. Then we add the connection to the connection manager using `c.TcpServer.GetConnMgr().Add(c)`.

#### 3. Checking Connection Count in Server

In the `Start()` method of the server, after a successful connection is established with a client, we can check the number of connections and terminate the connection creation if it exceeds the maximum connection count. Modify the `Start()` method in the `zinx/znet/server.go` file as follows:

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

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

    // Start a goroutine to handle the server listener
    go func() {
        // ... (omitted code)

        // Start the server network connection business
        for {
            // 1. Block and wait for client connection requests
            // ... (omitted code)

            // 2. Set the maximum connection limit for the server
            //    If the limit is exceeded, close the new connection
            if s.ConnMgr.Len() >= utils.GlobalObject.MaxConn {
                conn.Close()
                continue
            }

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

In the server's `Start()` method, we check the connection count using `s.ConnMgr.Len()` and compare it with the maximum connection limit. If the limit is reached, we close the new connection (`conn.Close()`) and continue to the next iteration.

Developers can define the maximum connection count in the configuration file `zinx.json` or in the `GlobalObject` global configuration using the `MaxConn` attribute.


#### 4. Removing a Connection

When a connection is closed, it should be removed from the ConnManager. In the Stop() method of the Connection struct, we add the removal action from the ConnManager. Modify the Stop() method in the zinx/znet/connection.go file as follows:

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

func (c *Connection) Stop() {
    fmt.Println("Conn Stop()... ConnID =", c.ConnID)

    if c.isClosed == true {
        return
    }
    c.isClosed = true

    c.Conn.Close()
    c.ExitBuffChan <- true

    // Remove the connection from the ConnManager
    c.TcpServer.GetConnMgr().Remove(c)

    close(c.ExitBuffChan)
    close(c.msgBuffChan)
}
```

In the `Stop()` method, after closing the connection, we remove the connection from the ConnManager using `c.TcpServer.GetConnMgr().Remove(c)`.

Additionally, when stopping the server in the Stop() method, we need to clear all connections as well:

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

func (s *Server) Stop() {
    fmt.Println("[STOP] Zinx server, name", s.Name)

    // Stop or clean up other necessary connection information or other information
    s.ConnMgr.ClearConn()
}
```

In the `Stop()` method, we call `s.ConnMgr.ClearConn()` to stop and remove all connections from the `ConnManager`.

With the above code, we have successfully integrated the connection management into Zinx.

### 9.1.3 Buffered Message Sending Method for Connection

Previously, a method called `SendMsg()` was provided for the `Connection` struct, which sends data to an unbuffered channel called `msgChan`. However, if there are a large number of client connections and the recipient is unable to process the messages promptly, it may lead to temporary blocking. To provide a non-blocking sending experience, a buffered message sending method can be added.

IConnection Interface Definition (zinx/ziface/iconnection.go)

```go
// Connection interface definition
type IConnection interface {
    // Start the connection, allowing the current connection to start working
    Start()
    // Stop the connection, ending the current connection state
    Stop()
    // Get the raw TCPConn of the current connection
    GetTCPConnection() *net.TCPConn
    // Get the current connection ID
    GetConnID() uint32
    // Get the remote client address information
    RemoteAddr() net.Addr
    // Send Message data directly to the remote TCP client (unbuffered)
    SendMsg(msgID uint32, data []byte) error
    // Send Message data directly to the remote TCP client (buffered)
    SendBuffMsg(msgID uint32, data []byte) error  // Add buffered message sending interface
}
```

In addition to the `SendMsg()` method, we will provide a `SendBuffMsg()` method in the `IConnection `interface. The `SendBuffMsg()` method is similar to SendMsg() but uses a buffered channel for communication between two goroutines. The definition of the Connection struct in the `zinx/znet/connection.go` file will be modified as follows:

Connection Struct Definition (zinx/znet/connection.go)
```go
type Connection struct {
    // The server to which the current connection belongs
    TcpServer    ziface.IServer
    // The TCP socket of the current connection
    Conn         *net.TCPConn
    // The ID of the current connection (also known as SessionID, globally unique)
    ConnID       uint32
    // The closing state of the current connection
    isClosed     bool
    // The message handler that manages MsgID and corresponding processing methods
    MsgHandler   ziface.IMsgHandle
    // The channel that informs that the connection has exited/stopped
    ExitBuffChan chan bool
    // The unbuffered channel used for message communication between the reading and writing goroutines
    msgChan      chan []byte
    // The buffered channel used for message communication between the reading and writing goroutines
    msgBuffChan  chan []byte
}
```

To implement the buffered message sending functionality, a `msgBuffChan` of type `chan []byte` is added to the `Connection` struct. The `msgBuffChan` will be used for communication between the reading and writing goroutines. Make sure to initialize the `msgBuffChan` member in the connection's constructor method:

NewConnection Constructor (zinx/znet/connection.go)
```go
func NewConnection(server ziface.IServer, conn *net.TCPConn, connID uint32, msgHandler ziface.IMsgHandle) *Connection {
    // Initialize Conn properties
    c := &Connection{
        TcpServer:    server,
        Conn:         conn,
        ConnID:       connID,
        isClosed:     false,
        MsgHandler:   msgHandler,
        ExitBuffChan: make(chan bool, 1),
        msgChan:      make(chan []byte),
        msgBuffChan:  make(chan []byte, utils.GlobalObject.MaxMsgChanLen), // Don't forget to initialize
    }

    // Add the newly created Conn to the connection manager
    c.TcpServer.GetConnMgr().Add(c)
    return c
}
```

The `SendBuffMsg()` method implementation is similar to `SendMsg()`. It packs and sends the data to the client through the `msgBuffChan`. Here's the implementation:

SendBuffMsg() Method Implementation (zinx/znet/connection.go)

```go
func (c *Connection) SendBuffMsg(msgID uint32, data []byte) error {
    if c.isClosed {
        return errors.New("Connection closed when sending buffered message")
    }
    // Pack the data and send it
    dp := NewDataPack()
    msg, err := dp.Pack(NewMsgPackage(msgID, data))
    if err != nil {
        fmt.Println("Pack error msg ID =", msgID)
        return errors.New("Pack error message")
    }

    // Write to the client
    c.msgBuffChan <- msg

    return nil
}
```

The `StartWriter()` method in the `Connection` struct needs to handle the `msgBuffChan` for data transmission. Here's the implementation:

StartWriter() Method Implementation (zinx/znet/connection.go)
```go
func (c *Connection) StartWriter() {
    fmt.Println("[Writer Goroutine is running]")
    defer fmt.Println(c.RemoteAddr().String(), "[conn Writer exit!]")

    for {
        select {
        case data := <-c.msgChan:
            // Data to be written to the client
            if _, err := c.Conn.Write(data); err != nil {
                fmt.Println("Send Data error:", err, "Conn Writer exit")
                return
            }

        case data, ok := <-c.msgBuffChan:
            // Handling data for buffered channel
            if ok {
                // Data to be written to the client
                if _, err := c.Conn.Write(data); err != nil {
                    fmt.Println("Send Buffered Data error:", err, "Conn Writer exit")
                    return
                }
            } else {
                fmt.Println("msgBuffChan is Closed")
                break
            }

        case <-c.ExitBuffChan:
            return
        }
    }
}
```

The `StartWriter()` method listens to both the `msgChan` and msgBuffChan channels. If there's data in the `msgChan`, it writes it directly to the client. If there's data in the `msgBuffChan`, it writes it to the client after processing it accordingly.


### 9.1.5 Registering Connection Start/Stop Custom Hook Methods for Link Initialization/Shutdown

During the lifecycle of a connection, there are two moments when developers need to register callback functions to execute custom business logic. These moments occur after the connection is created and before it is disconnected. To meet this requirement, Zinx needs to add callback functions, also known as hook functions, that are triggered after the connection is created and before it is disconnected.

The `IServer` interface in the `zinx/ziface/iserver.go` file provides methods for registering connection hooks that can be used by developers. The interface definition is as follows:

```go
type IServer interface {
    // Start the server
    Start()
    // Stop the server
    Stop()
    // Start the business service
    Serve()
    // Register a routing business method for the current server to be used for client connection processing
    AddRouter(msgID uint32, router IRouter)
    // Get the connection manager
    GetConnMgr() IConnManager
    // Set the hook function to be called when a connection is created for this server
    SetOnConnStart(func(IConnection))
    // Set the hook function to be called when a connection is about to be disconnected for this server
    SetOnConnStop(func(IConnection))
    // Invoke the OnConnStart hook function for the connection
    CallOnConnStart(conn IConnection)
    // Invoke the OnConnStop hook function for the connection
    CallOnConnStop(conn IConnection)
}
```

Four new hook methods are added:

1.`SetOnConnStart`: Set the hook function to be called when a connection is created for the current server.
2.`SetOnConnStop`: Set the hook function to be called when a connection is about to be disconnected for the current server.
3.`CallOnConnStart`: Invoke the hook function after a connection is created.
4.`CallOnConnStop`: Invoke the hook function before a connection is about to be disconnected.

The `Server` struct in the `zinx/znet/server.go` file is updated to include two new fields for the hook functions:

```go
type Server struct {
    // Server name
    Name string
    // TCP version (e.g., tcp4 or other)
    IPVersion string
    // IP address to which the server is bound
    IP string
    // Port to which the server is bound
    Port int
    // Message handler for the server, used to bind MsgID with corresponding handling methods
    MsgHandler ziface.IMsgHandle
    // Connection manager for the server
    ConnMgr ziface.IConnManager
    
    // New hook function prototypes //
    
    // Hook function to be called when a connection is created for this server
    OnConnStart func(conn ziface.IConnection)
    // Hook function to be called when a connection is about to be disconnected for this server
    OnConnStop func(conn ziface.IConnection)
}
```

The `Server` struct now includes `OnConnStart` and `OnConnStop` fields to hold the addresses of the hook functions passed by developers.

The implementation of the four new hook methods is as follows:
```go
// Set the hook function to be called when a connection is created for the server
func (s *Server) SetOnConnStart(hookFunc func(ziface.IConnection)) {
    s.OnConnStart = hookFunc
}

// Set the hook function to be called when a connection is about to be disconnected for the server
func (s *Server) SetOnConnStop(hookFunc func(ziface.IConnection)) {
    s.OnConnStop = hookFunc
}

// Invoke the OnConnStart hook function for the connection
func (s *Server) CallOnConnStart(conn ziface.IConnection) {
    if s.OnConnStart != nil {
        fmt.Println("---> CallOnConnStart....")
        s.OnConnStart(conn)
    }
}

// Invoke the OnConnStop hook function for the connection
func (s *Server) CallOnConnStop(conn ziface.IConnection) {
    if s.OnConnStop != nil {
        fmt.Println("---> CallOnConnStop....")
        s.OnConnStop(conn)
    }
}
```

Now, let's determine the positions where these two hook methods should be called. The first position is after the connection is created, which is the last step in the `Start()` method of the `Connection` struct:

```go
// Start the connection, allowing it to begin working
func (c *Connection) Start() {
    // 1. Start the Goroutine for reading data from the client
    go c.StartReader()
    // 2. Start the Goroutine for writing data back to the client
    go c.StartWriter()

    // Call the registered hook method for connection creation according to the user's requirements
    c.TcpServer.CallOnConnStart(c)
}
```
The second position is just before the connection is stopped, which is when the `Stop()` method of the `Connection` struct is called. It should be called before the `Close()` action of the socket because once the socket is closed, the communication with the remote end is terminated. If the hook method involves writing data back to the client, it will not be able to communicate properly. Therefore, the hook method should be called before the `Close()` method. Here's the code:

```go
// Stop the connection, ending the current connection state
func (c *Connection) Stop() {
    fmt.Println("Conn Stop()...ConnID = ", c.ConnID)
    // If the current connection is already closed
    if c.isClosed == true {
        return
    }
    c.isClosed = true

    // ==================
    // If the user registered a callback function for this connection's closure, it should be called explicitly at this moment
    c.TcpServer.CallOnConnStop(c)
    // ==================

    // Close the socket connection
    c.Conn.Close()
    // Close the writer
    c.ExitBuffChan <- true

    // Remove the connection from the connection manager
    c.TcpServer.GetConnMgr().Remove(c)

    // Close all channels of this connection
    close(c.ExitBuffChan)
    close(c.msgBuffChan)
}
```

### 9.1.5 Using Zinx-V0.9 to Complete the Application

By now, all the connection management functionality has been integrated into Zinx. The next step is to test whether the connection management module is functional. Let's test a server that demonstrates the ability to handle connection management hook function callbacks. 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 the data from the client first, then write back ping...ping...ping
	fmt.Println("recv from client: msgId=", request.GetMsgID(), ", data=", string(request.GetData()))

	err := request.GetConnection().SendBuffMsg(0, []byte("ping...ping...ping"))
	if err != nil {
		fmt.Println(err)
	}
}

type HelloZinxRouter struct {
	znet.BaseRouter
}

// HelloZinxRouter Handle
func (this *HelloZinxRouter) Handle(request ziface.IRequest) {
	fmt.Println("Call HelloZinxRouter Handle")
	// Read the data from the client first, then write back Hello Zinx Router V0.8
	fmt.Println("recv from client: msgId=", request.GetMsgID(), ", data=", string(request.GetData()))

	err := request.GetConnection().SendBuffMsg(1, []byte("Hello Zinx Router V0.8"))
	if err != nil {
		fmt.Println(err)
	}
}

// Executed when a connection is created
func DoConnectionBegin(conn ziface.IConnection) {
	fmt.Println("DoConnectionBegin is Called...")
	err := conn.SendMsg(2, []byte("DoConnection BEGIN..."))
	if err != nil {
		fmt.Println(err)
	}
}

// Executed when a connection is lost
func DoConnectionLost(conn ziface.IConnection) {
	fmt.Println("DoConnectionLost is Called...")
}

func main() {
	// Create a server handler
	s := znet.NewServer()

	// Register connection hook callback functions
	s.SetOnConnStart(DoConnectionBegin)
	s.SetOnConnStop(DoConnectionLost)

	// Configure routers
	s.AddRouter(0, &PingRouter{})
	s.AddRouter(1, &HelloZinxRouter{})

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

The server-side business code registers two hook functions: `DoConnectionBegin()` to be executed after the connection is created and `DoConnectionLost()` to be executed before the connection is lost.

* `DoConnectionBegin()`: After the connection is created, it sends a message with ID 2 to the client and prints a debug message on the server side saying "DoConnectionBegin is Called...".

* `DoConnectionLost()`: Before the connection is lost, it prints a debug message on the server side saying "DoConnectionLost is Called...".

The code for the client `Client.go` remains unchanged. To test the server and client, open different terminals and start the server and client using the following commands:

1.Start the server:
```bash
$ go run Server.go
```

2.Start the Client:
```bash
$ go run Client.go
```

The server-side output will be as follows:
```bash
$ go run Server.go
Add api msgId = 0
Add api msgId =  1
[START] Server name: zinx v-0.8 demoApp, listener at IP: 127.0.0.1, Port 7777 is starting
[Zinx] Version: V0.4, MaxConn: 3, MaxPacketSize: 4096
start Zinx server zinx v-0.8 demoApp succ, now listening...
Worker ID =  9  is started.
Worker ID =  5  is started.
Worker ID =  6  is started.
Worker ID =  7  is started.
Worker ID =  8  is started.
Worker ID =  1  is started.
Worker ID =  0  is started.
Worker ID =  2  is started.
Worker ID =  3  is started.
Worker ID =  4  is started.
connection add to ConnManager successfully: conn num =  1
---> CallOnConnStart....
DoConnectionBegin is Called...
[Writer Goroutine is running]
[Reader Goroutine is running]
Add ConnID= 0  request msgID= 0 to workerID= 0
Call PingRouter Handle
recv from client: msgId= 0 , data= Zinx V0.8 Client0 Test Message
Add ConnID= 0  request msgID= 0 to workerID= 0
Call PingRouter Handle
recv from client: msgId= 0 , data= Zinx V0.8 Client0 Test Message
Add ConnID= 0  request msgID= 0 to workerID= 0
Call PingRouter Handle
recv from client: msgId= 0 , data= Zinx V0.8 Client0 Test Message
Add ConnID= 0  request msgID= 0 to workerID= 0
Call PingRouter Handle
recv from client: msgId= 0 , data= Zinx V0.8 Client0 Test Message
Add ConnID= 0  request msgID= 0 to workerID= 0
Call PingRouter Handle
recv from client: msgId= 0 , data= Zinx V0.8 Client0 Test Message
read msg head error read tcp4 127.0.0.1:7777->127.0.0.1:49510: read: connection reset by peer
Conn Stop()...ConnID =  0
---> CallOnConnStop....
DoConnectionLost is Called...
connection Remove ConnID= 0  successfully: conn num =  0
127.0.0.1:49510 [conn Reader exit!]
127.0.0.1:49510 [conn Writer exit!]
```

The client-side output will be as follows:
```bash
$ go run Client0.go
Client Test... start
==> Recv Msg: ID= 2 , len= 21 , data= DoConnection BEGIN...
==> 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
==> Recv Msg: ID= 0 , len= 18 , data= ping...ping...ping
^Csignal: interrupt
```

From the above results, we can see that the client is successfully created, and the callback hooks have been executed. The connection has been added to the `ConnManager` in the server, and the current connection count conn num is 1. When we manually press "CTRL+C" to close the client, the ConnManager on the server side successfully removes the connection, and the connection count `conn num` becomes 0. Additionally, the server-side debug information for connection stop callback is printed.

## 9.2 Setting Connection Attributes in Zinx

When dealing with connections, developers often want to bind certain user data or parameters to a connection. This allows them to retrieve the passed parameters from the connection during handle processing and carry out business logic accordingly. In order to provide this capability, Zinx needs to establish interfaces or methods for setting attributes on a current connection. This section will implement the functionality to set attributes for connections.

### 9.2.1 Adding Connection Configuration Interface
To begin with, in the IConnection abstract layer, three related interfaces for configuring connection attributes are added. The code is as follows:

```go
//zinx/ziface/iconnection.go

// Definition of the connection interface
type IConnection interface {
	// Start the connection and initiate its operations
	Start()
	// Stop the connection and terminate its current state
	Stop()

	// Get the underlying TCPConn from the current connection
	GetTCPConnection() *net.TCPConn
	// Get the connection's unique ID
	GetConnID() uint32
	// Get the remote client's address information
	RemoteAddr() net.Addr

	// Send Message data directly to the remote TCP client (unbuffered)
	SendMsg(msgId uint32, data []byte) error
	// Send Message data directly to the remote TCP client (buffered)
	SendBuffMsg(msgId uint32, data []byte) error

	// Set connection attributes
	SetProperty(key string, value interface{})
	// Get connection attributes
	GetProperty(key string) (interface{}, error)
	// Remove connection attributes
	RemoveProperty(key string)
}

```

In the provided code snippet, IConnection has three added methods: `SetProperty()`, `GetProperty()`, and `RemoveProperty()`. The key parameter in each method is of type string, and the value parameter is of the versatile interface{} type. The subsequent step involves defining the specific types of properties within the Connection.

### 9.2.2 Implementation of Connection Property Methods

In the implementation layer, the Connection structure is augmented with a member attribute named property. This attribute will hold all user-provided parameters passed through the connection, and it is defined as a `map[string]interface{}` type. The definition is as follows:

```go
//zinx/znet/connction.go

type Connection struct {
	// Current Conn belongs to which Server
	TcpServer ziface.IServer
	// The TCP socket of the current connection
	Conn *net.TCPConn
	// ID of the current connection, also known as SessionID; globally unique
	ConnID uint32
	// Current connection's closure state
	isClosed bool
	// Message manager module for managing MsgId and corresponding message handling methods
	MsgHandler ziface.IMsgHandle
	// Channel to signal that the connection has exited/stopped
	ExitBuffChan chan bool
	// Unbuffered channel for message communication between read and write goroutines
	msgChan chan []byte
	// Buffered channel for message communication between read and write goroutines
	msgBuffChan chan []byte
	// Connection properties
	property     map[string]interface{}
	// Lock for protecting concurrent property modifications
	propertyLock sync.RWMutex
}
```

The `property` map is not concurrency-safe, and read and write operations on the map need to be protected with the `propertyLock`. Additionally, the constructor of `Connection` must initialize both `property` and `propertyLock`. The code snippet for this is provided below:

```go
//zinx/znet/connction.go

// Method to create a connection
func NewConntion(server ziface.IServer, conn *net.TCPConn, connID uint32, msgHandler ziface.IMsgHandle) *Connection {
	// Initialize Conn properties
	c := &Connection{
		TcpServer:    server,
		Conn:         conn,
		ConnID:       connID,
		isClosed:     false,
		MsgHandler:   msgHandler,
		ExitBuffChan: make(chan bool, 1),
		msgChan:      make(chan []byte),
		msgBuffChan:  make(chan []byte, utils.GlobalObject.MaxMsgChanLen),
		property:     make(map[string]interface{}), // Initialize connection property map
	}

	// Add the newly created Conn to the connection manager
	c.TcpServer.GetConnMgr().Add(c)
	return c
}
```
The implementation of the three methods for handling connection properties is straightforward. They respectively involve adding, reading, and removing entries from the map. The implementations of these methods are as follows:

```go
//zinx/znet/connction.go

// Set connection property
func (c *Connection) SetProperty(key string, value interface{}) {
	c.propertyLock.Lock()
	defer c.propertyLock.Unlock()

	c.property[key] = value
}

// Get connection property
func (c *Connection) GetProperty(key string) (interface{}, error) {
	c.propertyLock.RLock()
	defer c.propertyLock.RUnlock()

	if value, ok := c.property[key]; ok {
		return value, nil
	} else {
		return nil, errors.New("no property found")
	}
}

// Remove connection property
func (c *Connection) RemoveProperty(key string) {
	c.propertyLock.Lock()
	defer c.propertyLock.Unlock()

	delete(c.property, key)
}
```
These methods manage the interaction with the property map, allowing for adding, retrieving, and removing properties associated with a connection.

### 9.1.3 Connection Property Testing in Zinx-V0.10

After encapsulating the functionality for connection properties, we can use the relevant interfaces on the server side to set some properties and test whether the setting and extraction of properties are functional. Below is the server-side code:

```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 reply with ping...ping...ping
	fmt.Println("recv from client: msgId=", request.GetMsgID(), ", data=", string(request.GetData()))

	err := request.GetConnection().SendBuffMsg(0, []byte("ping...ping...ping"))
	if err != nil {
		fmt.Println(err)
	}
}

type HelloZinxRouter struct {
	znet.BaseRouter
}

// HelloZinxRouter Handle
func (this *HelloZinxRouter) Handle(request ziface.IRequest) {
	fmt.Println("Call HelloZinxRouter Handle")
	// Read client data first, then reply with ping...ping...ping
	fmt.Println("recv from client: msgId=", request.GetMsgID(), ", data=", string(request.GetData()))

	err := request.GetConnection().SendBuffMsg(1, []byte("Hello Zinx Router V0.10"))
	if err != nil {
		fmt.Println(err)
	}
}

// Executed when a connection is created
func DoConnectionBegin(conn ziface.IConnection) {
	fmt.Println("DoConnectionBegin is Called ... ")

	// Set two connection properties after the connection is created
	fmt.Println("Set conn Name, Home done!")
	conn.SetProperty("Name", "Aceld")
	conn.SetProperty("Home", "https://github.com/aceld/zinx")

	err := conn.SendMsg(2, []byte("DoConnection BEGIN..."))
	if err != nil {
		fmt.Println(err)
	}
}

// Executed when a connection is lost
func DoConnectionLost(conn ziface.IConnection) {
	// Before the connection is destroyed, query the "Name" and "Home" properties of the conn
	if name, err := conn.GetProperty("Name"); err == nil {
		fmt.Println("Conn Property Name =", name)
	}

	if home, err := conn.GetProperty("Home"); err == nil {
		fmt.Println("Conn Property Home =", home)
	}

	fmt.Println("DoConnectionLost is Called ... ")
}

func main() {
	// Create a server handle
	s := znet.NewServer()

	// Register connection hook callback functions
	s.SetOnConnStart(DoConnectionBegin)
	s.SetOnConnStop(DoConnectionLost)

	// Configure routers
	s.AddRouter(0, &PingRouter{})
	s.AddRouter(1, &HelloZinxRouter{})

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

The key focus is on the implementations of the `DoConnectionBegin()` and `DoConnectionLost()` functions. Within these functions, connection properties are set and extracted using the hook callbacks. Once a connection is established, the properties "Name" and "Home" are assigned to the connection using the `conn.SetProperty()` method. Later, these properties can be retrieved using the `conn.GetProperty()` method.

Open a terminal to launch the server program, and observe the following results:

```go
$ go run Server.go 
Add api msgId =  0
Add api msgId =  1
[START] Server name: zinx v-0.10 demoApp,listener at IP: 127.0.0.1, Port 7777 is starting
[Zinx] Version: V0.4, MaxConn: 3, MaxPacketSize: 4096
start Zinx server   zinx v-0.10 demoApp  succ, now listening...
Worker ID =  9  is started.
Worker ID =  5  is started.
Worker ID =  6  is started.
Worker ID =  7  is started.
Worker ID =  8  is started.
Worker ID =  1  is started.
Worker ID =  0  is started.
Worker ID =  2  is started.
Worker ID =  3  is started.
Worker ID =  4  is started.
connection add to ConnManager successfully: conn num =  1
---> CallOnConnStart....
DoConnecionBegin is Called ... 
Set conn Name, Home done!
[Writer Goroutine is running]
[Reader Goroutine is running]
Add ConnID= 0  request msgID= 0 to workerID= 0
Call PingRouter Handle
recv from client : msgId= 0 , data= Zinx V0.8 Client0 Test Message
Add ConnID= 0  request msgID= 0 to workerID= 0
Call PingRouter Handle
recv from client : msgId= 0 , data= Zinx V0.8 Client0 Test Message
Add ConnID= 0  request msgID= 0 to workerID= 0
Call PingRouter Handle
recv from client : msgId= 0 , data= Zinx V0.8 Client0 Test Message
read msg head error  read tcp4 127.0.0.1:7777->127.0.0.1:55208: read: connection reset by peer
Conn Stop()...ConnID =  0
---> CallOnConnStop....
Conn Property Name =  Aceld
Conn Property Home =  https://github.com/aceld/zinx
DoConneciotnLost is Called ... 
connection Remove ConnID= 0  successfully: conn num =  0
127.0.0.1:55208 [conn Reader exit!]
127.0.0.1:55208 [conn Writer exit!]
```

In a new terminal, start the client program, and observe the following results:

```bash
$ go run Client0.go 
Client Test ... start
==> Recv Msg: ID= 2 , len= 21 , data= DoConnection BEGIN...
==> Recv Msg: ID= 0 , len= 18 , data= ping...ping...ping
==> Recv Msg: ID= 0 , len= 18 , data= ping...ping...ping
^Csignal: interrupt
```

The server output is particularly noteworthy:

```bash
---> CallOnConnStop....
Conn Property Name =  Aceld
Conn Property Home =  https://github.com/aceld/zinx
DoConneciotnLost is Called ... 

```

## 9.3 Summary

In this chapter, two functionalities were introduced to enhance the capabilities of connections in Zinx. The first one is the connection management module, which gathers all the connections within the Zinx server, providing an aggregated view and counting of the total number of connections. Currently, Zinx simply employs a basic incremental calculation for connection IDs. Readers are encouraged to optimize this aspect by replacing ConnID with a more common distributed ID, ensuring uniqueness across IDs.

The connection management module aids in controlling the concurrent load on the server by limiting the number of connections. The second addition, connection properties, enriches the convenience of handling business logic within Zinx. Developers can utilize the `SetProperty()` and `GetProperty()` methods to associate different attributes with different connections. This enables the linking of various markers or attributes to different connections, facilitating flexible business logic handling.

---

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