DEV Community

Cover image for 2. Zinx-V0.2 Simple Connection Encapsulation and Binding with Business
Aceld
Aceld

Posted on • Updated on

2. Zinx-V0.2 Simple Connection Encapsulation and Binding with Business

[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>
<8.Zinx Message Queue and Task Worker Pool Design and Implementation>
<9. Zinx Connection Management and Property Setting>

[Zinx Application - MMO Game Case Study]

<10. Application Case Study using the Zinx Framework>
<11. MMO Online Game AOI Algorithm>
<12.Data Transmission Protocol: Protocol Buffers>
<13. MMO Game Server Application Protocol>
<14. Building the Project and User Login>
<15. World Chat System Implementation>
<16. Online Location Information Synchronization>
<17. Moving position and non-crossing grid AOI broadcasting>
<18. Player Logout>
<19. Movement and AOI Broadcast Across Grids>


source code

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


V0.1 version has implemented a basic Server framework, and now we need to further encapsulate the client connections and bind different business logic to different client connections with another layer of interface encapsulation. First, we need to build the architecture.

2.1 Zinx-V0.2 Code Implementation

Next, we will use a few steps to implement Zinx-V0.2 version.

2.1.1 Create iconnection.go in ziface

Create an interface file called iconnection.go under the ziface directory as an interface file, and the corresponding implementation file will be placed in connection.go under znet.

The interface file is implemented as follows:

//zinx/ziface/iconnection.go
package ziface

import "net"

// Define the connection interface
type IConnection interface {
    // Start the connection, making the current connection work
    Start()
    // Stop the connection, ending the current connection state
    Stop()
    // Get the raw socket TCPConn from the current connection
    GetTCPConnection() *net.TCPConn
    // Get the current connection ID
    GetConnID() uint32
    // Get the remote client's address information
    RemoteAddr() net.Addr
}

// Define an interface for handling connection business uniformly
type HandFunc func(*net.TCPConn, []byte, int) error
Enter fullscreen mode Exit fullscreen mode

Some of the basic methods of this interface are as follows:

(1) Start(), start the connection, and let the current connection start reading and writing related work.

(2) Stop(), stop the connection, end the current connection state, and recycle related resources and close relevant logic.

(3) GetTCPConnection(), get the socket TCPConn type connection data structure.

(4) GetConnID(), get the current connection ID. Each connection will be assigned a connection ID. The purpose is to distinguish different connections or manage multiple connections uniformly, and count the number of connections.

(5) RemoteAddr(), get the remote client connection. For each connection that has been established, there will be address information for the remote end of the socket.

It is important to note the HandFunc function type:

type HandFunc func(*net.TCPConn, []byte, int) error
Enter fullscreen mode Exit fullscreen mode

This is the function interface for handling business for all conn connections. The first parameter is the raw socket connection, the second parameter is the data requested by the client, and the third parameter is the length of the data requested by the client. In this way, if you want to specify a conn's business processing, just define a function of type HandFunc and bind it to the connection.

2.1.2 Create connection.go

in the /znet/ directory to implement Connection that implements the IConnection interface. The specific code is as follows:

//zinx/znet/connection.go
package znet

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

type Connection struct {
    // Current connection's socket TCP socket
    Conn *net.TCPConn
    // Current connection's ID, also known as SessionID, ID is 
    globally unique
    ConnID uint32
    // Current connection's close status
    isClosed bool

    // The handle function of this connection's api
    handleAPI ziface.HandFunc

    // Channel to inform that the connection has exited/stopped
    ExitBuffChan chan bool
}
Enter fullscreen mode Exit fullscreen mode

The Connection struct includes the following attributes:
(1) Conn, the TCP socket of the current connection, encapsulated standard library TCPConn structure.
(2) ConnID, the ID of the current connection, which requires globally unique.
(3) isClosed, the close status of the current connection.
(4) handleAPI, the handling method API bound to the current connection, which is the callback business method registered by the developer.
(5) ExitBuffChan, a channel used to notify that the connection has exited/stopped for communication synchronization purposes.

Next, a construction method for Connection is provided, named NewConnection, and the implementation code is as follows:

//zinx/znet/connection.go

func NewConntion(conn *net.TCPConn, connID uint32, callback_api ziface.HandFunc) *Connection{
    c := &Connection{
        Conn:     conn,
        ConnID:   connID,
        isClosed: false,
        handleAPI: callback_api,
        ExitBuffChan: make(chan bool, 1),
    }

    return c
}
Enter fullscreen mode Exit fullscreen mode

Next, we provide a functional method for Connection, StartReader(). The main logic of this function is an infinite loop that waits for server messages in a blocking manner. When data arrives, it is read into local memory. Once the data is complete, it is passed to the handleAPI registered by the developer to handle business. Any exceptions or errors will break out of this loop and end the current method. The specific implementation code is as follows:

//zinx/znet/connection.go

/* Goroutine to handle reading data from the connection */
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 largest amount of data to buf
        buf := make([]byte, 512)
        cnt, err := c.Conn.Read(buf)
        if err != nil {
             fmt.Println("recv buf err ", err)
             c.ExitBuffChan <- true
             continue
        }
        // Call the handle function registered by the developer for this connection to handle the business logic
        if err := c.handleAPI(c.Conn, buf, cnt); err !=nil {
             fmt.Println("connID ", c.ConnID, " handle is error")
             c.ExitBuffChan <- true
             return
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

If an exception occurs in the middle, the reader goroutine will send a message through the ExitBuffChan, writing a bool value to notify other goroutines that the current connection has exited, so as to handle some other cleanup tasks. The Start() method implemented by Connection is as follows:

//zinx/znet/connection.go

func (c *Connection) Start() {

    go c.StartReader()

    for {
        select {
        case <- c.ExitBuffChan:
            return
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Start a Goroutine to execute the business logic of reading data, and the main logic is permanently blocked until ExitBuffChan has a message to read, indicating that the connection has exited, and additional recovery actions can be performed before exiting. The implementation of the Stop() method for Connection is as follows:

//zinx/znet/connection.go

// Stop the connection and end the current connection state.
func (c *Connection) Stop() {
    //1. If the current connection is already closed
    if c.isClosed == true {
        return
    }
    c.isClosed = true

    // TODO Connection Stop() If the user has registered the close callback business of the connection, it should be called explicitly at this moment.

    // Close the socket connection
    c.Conn.Close()

    // Notify the business that reads data from the buffer queue that the connection has been closed
    c.ExitBuffChan <- true

    // Close all channels for this connection
    close(c.ExitBuffChan)
}
Enter fullscreen mode Exit fullscreen mode

The Stop() method is used to actively close the connection. The implementation of GetTCPConnection(), GetConnID(), and RemoteAddr() methods for Connection is as follows:

//zinx/znet/connection.go

// Get the original socket TCPConn from the current connection
func (c *Connection) GetTCPConnection() *net.TCPConn {
    return c.Conn
}

// Get the current connection ID
func (c *Connection) GetConnID() uint32{
    return c.ConnID
}

// Get the remote client address information
func (c *Connection) RemoteAddr() net.Addr {
    return c.Conn.RemoteAddr()
}
Enter fullscreen mode Exit fullscreen mode

2.1.3 Correct the connection business logic handling for conn in server.go.

In server.go, the defined Connection object is integrated, and a connection handling callback method CallBackToClient() is defined. The current business is the echo business, which echoes the data transmitted by the peer back to the peer. The modified code for server.go is as follows:

//zinx/znet/server.go
package znet

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

// iServer implementation, defines a Server service class
type Server struct {
    // the name of the server
    Name string
    // tcp4 or other
    IPVersion string
    // the IP address that the server is bound to
    IP string
    // the port that the server is bound to
    Port int
}

//============== Define the handle API for the current client connection ===========
func CallBackToClient(conn *net.TCPConn, data []byte, cnt int) error {
    // echo business
    fmt.Println("[Conn Handle] CallBackToClient ... ")
    if _, err := conn.Write(data[:cnt]); err !=nil {
        fmt.Println("write back buf err ", err)
        return errors.New("CallBackToClient error")
    }
    return nil
}

//============== Implement all interface methods in ziface.IServer ========

// start the network service
func (s *Server) Start() {
    fmt.Printf("[START] Server listenner at IP: %s, Port %d, is starting\n", s.IP, s.Port)

// start a go routine to do the server listener business
    go func() {
        // 1. get a TCP address
        addr, err := net.ResolveTCPAddr(s.IPVersion, fmt.Sprintf("%s:%d", s.IP, s.Port))
        if err != nil {
            fmt.Println("resolve tcp addr err: ", err)
            return
        }

        // 2. listen to the server address
        listenner, err:= net.ListenTCP(s.IPVersion, addr)
        if err != nil {
            fmt.Println("listen", s.IPVersion, "err", err)
            return
        }

        // the listener has started successfully
        fmt.Println("start Zinx server  ", s.Name, " succ, now listenning...")

        // TODO server.go should have a method to generate ID automatically
        var cid uint32
        cid = 0

        // 3. start the server network connection business
        for {
            // 3.1. block and wait for the client to establish a connection request
            conn, err := listenner.AcceptTCP()
            if err != nil {
                fmt.Println("Accept err ", err)
                continue
            }

            // 3.2. TODO Server.Start() set the maximum connection control of the server. If the maximum connection is exceeded, then close this new connection

            // 3.3. handle the new connection request business, where handler and conn are bound
            dealConn := NewConntion(conn, cid, CallBackToClient)
            cid ++

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

// ... ...
Enter fullscreen mode Exit fullscreen mode

CallBackToClient() is a handle method bound to the conn object, and currently, the server-side is forcibly bound to the echo business. Later, the framework will be enriched so that users can customize the handle.
In the Start() method, the following modifications are mainly made:

    //…

    //3.3 处理该新连接请求的 业务 方法, 此时应该有 handler 和 conn是绑定的
    dealConn := NewConntion(conn, cid, CallBackToClient)
    cid ++

    //3.4 启动当前链接的处理业务
    go dealConn.Start()

    //…
Enter fullscreen mode Exit fullscreen mode

Okay, now the Connection connection and handleAPI have been bound. Let's test the usage of Zinx-V0.2 framework.

2.2 Using Zinx-V0.2 to complete the application

Actually, the external interface of the Zinx framework has not changed, so the V0.1 test is still valid and the code remains the same:

//Server.go
package main

import (
    "zinx/znet"
)

//Test function for Server module
func main() {

    //1 create a server handle s
    s := znet.NewServer("[zinx V0.1]")

    //2 start the service
    s.Serve()
}
Enter fullscreen mode Exit fullscreen mode

Start Server.go and execute the following command:

go run Server.go
Enter fullscreen mode Exit fullscreen mode

The client code is the same as before, as follows:

//Client.go
package main

import (
    "fmt"
    "net"
    "time"
)

func main() {

    fmt.Println("Client Test ... start")
    //Wait for 3 seconds before making a test request to give the server a chance to start up
    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 {
        _, err := conn.Write([]byte("hahaha"))
        if err !=nil {
            fmt.Println("write error err ", err)
            return
        }

        buf :=make([]byte, 512)
        cnt, err := conn.Read(buf)
        if err != nil {
            fmt.Println("read buf error ")
            return
        }

        fmt.Printf(" server call back : %s, cnt = %d\n", buf,  cnt)

        time.Sleep(1*time.Second)
    }
}
Enter fullscreen mode Exit fullscreen mode

Start Client.go to test, the command is as follows:

go run Client.go
Enter fullscreen mode Exit fullscreen mode

The result is the same as before, and the callback method of Connection has been called, as follows:

[START] Server listenner at IP: 0.0.0.0, Port 7777, is starting
Client Test ... start
listen tcp4 err listen tcp4 0.0.0.0:7777: bind: address already in use
server call back : hello ZINX, cnt = 6
server call back : hello ZINX, cnt = 6
server call back : hello ZINX, cnt = 6
server call back : hello ZINX, cnt = 6
Enter fullscreen mode Exit fullscreen mode

2.4 Summary

Now we have constructed the prototype of Zinx, defined the abstract layer IServer and IConnection, and implemented the corresponding implementation layer classes Server and Connection. We have completed the basic business method binding, but we are still far from a true framework. Next, we will continue to improve the Zinx framework.


[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>
<8.Zinx Message Queue and Task Worker Pool Design and Implementation>
<9. Zinx Connection Management and Property Setting>

[Zinx Application - MMO Game Case Study]

<10. Application Case Study using the Zinx Framework>
<11. MMO Online Game AOI Algorithm>
<12.Data Transmission Protocol: Protocol Buffers>
<13. MMO Game Server Application Protocol>
<14. Building the Project and User Login>
<15. World Chat System Implementation>
<16. Online Location Information Synchronization>
<17. Moving position and non-crossing grid AOI broadcasting>
<18. Player Logout>
<19. Movement and AOI Broadcast Across Grids>


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

Top comments (0)