2. ZINX-V0.2简单的连接封装并与业务结合
#go #zinx

<1.Building Basic Services with Zinx Framework>
<2. Zinx-V0.2 Simple Connection Encapsulation and Binding with Business>


v0.1版本已经实现了一个基本的服务器框架,现在我们需要进一步封装客户端连接并将不同的业务逻辑绑定到不同的客户端连接,并使用另一层接口封装。首先,我们需要构建体系结构。

2.1 ZINX-V0.2代码实现

接下来,我们将使用几个步骤实现Zinx-V0.2版本。

2.1.1创建iConnection.go在Ziface中

创建一个称为iConnection.go在Ziface目录下的接口文件作为接口文件,并将相应的实现文件放置在连接中。

接口文件的实现如下:

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

该接口的一些基本方法如下:

(1)start(),启动连接,让当前连接开始读取和写作相关的工作。

(2)stop(),停止连接,结束当前连接状态并回收相关资源并关闭相关逻辑。

(3)getTCPConnection(),获取套接字TCPCONN类型连接数据结构。

(4)getConnid(),获取当前的连接ID。每个连接将分配一个连接ID。目的是区分不同的连接或统一管理多个连接,并计算连接的数量。

(5)remoteaddr(),获取远程客户端连接。对于已建立的每个连接,将有插座遥控端的地址信息。

注意手续功能类型很重要:

type HandFunc func(*net.TCPConn, []byte, int) error

这是用于处理所有CONN连接业务的功能接口。第一个参数是原始套接字连接,第二个参数是客户端请求的数据,第三个参数是客户端请求的数据的长度。这样,如果您想指定Conn的业务处理,只需定义类型的函数并将其绑定到连接。

2.1.2创建Connection.go

在 / znet /目录中实现实现ICOnnection接口的连接。特定代码如下:

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

连接结构包括以下属性:
(1)Conn,当前连接的TCP插座,封装的标准库TCPCONN结构。
(2)Connid,当前连接的ID,需要全球唯一的唯一。
(3)序列,当前连接的接近状态。
(4)handleapi,绑定到当前连接的处理方法API,这是开发人员注册的回调业务方法。
(5)Exitbuffchan,用于通知连接已退出/停止的通道,以进行通信同步。

接下来,提供了一种用于连接的构造方法,命名为NewConnection,实现代码如下:

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

接下来,我们提供了连接的功能方法,Startreader()。此功能的主要逻辑是无限循环,该循环以阻塞方式等待服务器消息。当数据到达时,将其读取到本地内存中。数据完成后,将其传递给开发人员注册的Handleapi来处理业务。任何异常或错误都会爆发出此循环并结束当前方法。特定的实施代码如下:

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

如果中间发生异常,读者Goroutine将通过Exitbuffchan发送一条消息,编写一个布尔值,以通知其他Goroutines,以了解当前连接已退出,以处理其他清理任务。连接实现的start()方法如下:

//zinx/znet/connection.go

func (c *Connection) Start() {

    go c.StartReader()

    for {
        select {
        case <- c.ExitBuffChan:
            return
        }
    }
}

启动一个goroutine来执行阅读数据的业务逻辑,并且主要逻辑被永久阻止,直到ExitBuffchan有一条消息要阅读,表明该连接已退出,并且可以在退出之前执行其他恢复操作。连接stop()方法的实现如下:

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

stop()方法用于主动关闭连接。连接的getTCPConnection(),getConnid()和remoteaddr()方法的实现如下:

//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()
}

2.1.3更正服务器中的CONN的连接业务逻辑处理。

在server.go中,已集成了定义的连接对象,并且定义了连接处理回调方法callbacktoclient()。当前的业务是回声业务,它回应了同伴传输的数据回到同伴。服务器的修改代码。GO如下:

//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()
        }
    }()
}

// ... ...

callbacktoclient()是绑定到conn对象的句柄方法,目前,服务器端被强行绑定到回声业务。稍后,该框架将得到丰富,以便用户可以自定义句柄。
在开始()方法中,主要进行以下修改:

    //…

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

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

    //…

好吧,现在连接连接和Handleapi已被绑定。让我们测试Zinx-V0.2框架的用法。

2.2使用zinx-v0.2完成应用程序

实际上,ZINX框架的外部接口尚未更改,因此V0.1测试仍然有效,并且代码保持不变:

//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()
}

start server.go并执行以下命令:

go run Server.go

客户端代码与之前相同,如下所示:

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

启动client.go要测试,命令如下:

go run Client.go

结果与以前相同,并且已经调用了连接回调方法,如下所示:

[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

2.4摘要

现在,我们构建了Zinx的原型,定义了抽象层iServer和Iconnection,并实现了相应的实现层类服务器和连接。我们已经完成了基本的业务方法绑定,但我们远非真实的框架。接下来,我们将继续改善Zinx框架。


<1.Building Basic Services with Zinx Framework>
<2. Zinx-V0.2 Simple Connection Encapsulation and Binding with Business>

未完待续...

作者:
不和谐:https://discord.gg/xQ8Xxfyfcz
zinx:https://github.com/aceld/zinx
github:https://github.com/aceld
Aceld的家:https://yuque.com/aceld