1.建立Zinx框架的基本服务
#go #zinx

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


当前,对于服务器的GO中有许多应用程序框架,但是很少有轻量级企业框架可以在游戏或其他长连接字段中应用。作者设计的Zinx是为了使学习的开发人员能够理解基于Zinx框架的TCP服务器的整体轮廓。这将使更多的发烧友以一种简单易懂的方式学习和理解这一领域。

ZINX框架项目是通过编码和学习教程同步创建的,所有发展进步和迭代思维都纳入了教程。与其为每个人学习一个非常完整的框架,不如让许多不知道如何开始的人感到困惑。

在本章中,ZINX Framework构造的内容将按版本迭代版本,并且每个版本的函数的添加将是最小的。这样,即使读者是服务框架中的新手,他们也会通过曲线线性学习方法逐渐理解服务器框架的字段。

提示:我们希望更多的人加入Zinx并提供宝贵的反馈。谢谢您的关注!

zinx源代码:
github地址:https://github.com/aceld/zinx
Gitee(开源中国)地址:https://gitee.com/Aceld/zinx


1.1探索Zinx架构

Zinx的架构设计非常简单,由三个主要部分组成。第一部分是服务通信的设计,如上图所示。 Zinx使用一种读写分离方法来处理消息并执行基本数据读取和写作。读取数据已移交给第二部分,即消息工作池,进行并发处理。对于数据的上层业务,工人工作池将注册许多不同的业务回调操作,该操作由图中的API表示。这些是开发人员已经注册的不同消息的集合来处理上层业务层中的不同业务,这是第三部分。架构图是最初的设计思想,如图1.1所示。

Image description

图1.1

启动过程中完整的zinx服务器的运行效果如图1.2所示。

Image description

图1.2

1.2 ZINX-V0.1基本服务器

在框架开始时,应首先确定当前项目的文件结构。 Zinx构建了Ziface和Znet的两个基本模块。
(1)Ziface主要将所有模块的抽象接口类存储在Zinx框架中。最基本的服务接口是iServer,它在Ziface模块中定义。
(2)ZNET模块是Zinx框架中与网络相关函数的实现,所有与网络相关的模块都将在ZNET模块中定义。

1.2.1 ZINX-V0.1代码实现

1.创建Zinx框架

在$ gopath/src下创建一个zinx文件夹,该文件夹是zinx项目的项目根目录。

2.创建Ziface和Znet模块

在zinx/下创建ziface和znet文件夹,以使当前的文件路径看起来像这样:

└── zinx
  ├── ziface
  │ └──
  └── znet
    ├──

3.创建服务模块抽象层iServer.go在Ziface中

//zinx/ziface/iserver.go
package ziface

// Define the server interface
type IServer interface{
    // Start the server method
    Start()
    // Stop the server method
    Stop()
    // Start the business service method
    Serve()
}

在这里,iServer接口首先定义,该接口为服务器提供了三种方法。 start()方法是启动通信服务的方法,stop()是一种停止服务的方法,而服务()是启动总体业务服务的方法。

4.在znet下实现服务模块服务器。

定义iServer接口后,实现层可以实现此服务的基本接口。代码如下:

//zinx/znet/server.go
package znet

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

// Implementation of the iServer interface, defining a Server service class.
type Server struct {
    // Name of the server.
    Name string
    // TCP4 or other.
    IPVersion string
    // IP address that the service is bound to.
    IP string
    // Port that the service is bound to.
    Port int
}

定义了服务器结构,其属性包括:

(1)名称:当前服务的名称。尽管名称没有实际效果,但它可以帮助开发人员将来将其用于记录或比较服务版本。

(2)iPversion:当前IP协议的版本号。当前,Zinx使用IPv4,此参数是提供给GO网络编程标准库的参数之一。

(3)ip:使用虚线十进制符号(例如192.168.1.101等)的字符串类型IP地址。

(4)端口:整数端口号。

接下来,我们将在iServer接口中实现三个抽象接口start(),stop()和serve()。开始()方法的实现如下:

//zinx/znet/server.go

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

    // Start a goroutine to do the server Linster 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.
        listener, err := net.ListenTCP(s.IPVersion, addr)
        if err != nil {
            fmt.Println("listen", s.IPVersion, "err", err)
            return

        }

        // The server has successfully started listening.
        fmt.Println("start Zinx server  ", s.Name, " succ, now listening...")

        // 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 Server.Start() sets the server's maximum connection control. If the maximum connection is exceeded, then close this new connection.

            // 3.3. TODO Server.Start() handles the business method of the new connection request. At this time, handler and conn should be bound.

            // We will temporarily create an echo service with a maximum of 512 bytes.
            go func() {
                // Continuously loop to get data from the client.
                for {
                    buf := make([]byte, 512)
                    cnt, err := conn.Read(buf)
                    if err != nil {
                        fmt.Println("recv buf err ", err)
                        continue

                    }
                    // Echo back the received data.
                    if _, err := conn.Write(buf[:cnt]); err != nil {
                        fmt.Println("write back buf err ", err)
                        continue

                    }

                }

            }()

        }

    }()
}

start()方法实际上创建了一个goroutine来处理插座的听力能力。这个新的Goroutine将永远循环,如果有新的连接,将返回Accept()块,将建立服务器和客户端连接,并获得新的连接“ conn”。然后,将创建另一个Goroutine来处理该建立连接“ CONN”的数据读取和编写。它实现了非常简单的应用程序层业务,通过当前连接“ conn”将其写回客户端,回应了客户端输入的数据输入。当读取客户端的所有输入数据时,Conn.Read()将读取EOF,并且“ ERR”将不会为零,并且当前循环将退出,并且业务Goroutine将退出。这两个goroutines是异步的,因此在处理客户端连接数据时,它不会阻止服务器继续收听其他新连接的创建。

这是由于Goroutines和Golang并发的固有性质。开发人员不必担心业务Goroutines的数量,因为Golang的Goroutine调度程序已经优化了基础物理线程的开关成本,这是Golang编写服务器程序的优势。

接下来,让我们看一下stop()方法,该方法实现如下:

//zinx/znet/server.go

func (s *Server) Stop() {
    fmt.Println("[STOP] Zinx server , name " , s.Name)
    //TODO  Server.Stop() needs to stop or clean up other connection information or other information that needs to be cleared.
}

当前,stop()仅打印一个日志消息,将来将添加清洁功能。

现在让我们看一下Iserver的最后一个接口,Serve()方法的实现如下:

//zinx/znet/server.go

func (s *Server) Serve() {
    s.Start()

    select{}
}

serve()实际上是启动()的包装器,其目的是将服务器的整体启动与服务听力功能的各个启动分开。这也促进了开发人员考虑将来通过将其添加到Serve()。

来考虑是否添加其他逻辑。

在服务器()的末尾,有一个代码块,该代码块使用select {}永久封锁,该代码块可防止当前GO例程退出并导致start()的GO例程到退出。

接下来,让我们提供服务器类的构造函数,这是用于创建服务器对象的公共接口。通常,它将从方法名称开始。具体实现如下:


//zinx/znet/server.go


func NewServer (name string) ziface.IServer {
    s:= &Server {
        Name :name,
        IPVersion:"tcp4",
        IP:"0.0.0.0",
        Port:7777,
    }

    return s
}


好吧,我们已经完成了Zinx-V0.1的基本框架,尽管它仅涉及回声返回客户端数据的基本功能(我们稍后将自定义客户端业务方法)。现在,该测试Zinx-v0.1是否功能。

1.2.2 ZINX框架单元测试示例

现在,我们可以导入ZINX框架并编写服务器程序和用于测试的客户端程序。在这里,我们使用GO单元测试功能执行单元测试。首先,在zinx/ znet/文件夹下创建一个名为“ server_test.go”的文件,代码如下:

//zinx/znet/server_test.go
package znet

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

/*
    Simulate the client
*/
func ClientTest() {

    fmt.Println("Client Test ... start")

    // Wait for 3 seconds to give the server a chance to start its service
    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("hello ZINX"))
        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)
    }
}

// Test function for the Server module
func TestServer(t *testing.T) {

    /*
        Server test
    */
    // 1. Create a server handle s
    s := NewServer("[zinx V0.1]")

    /*
        Client test
    */
    go ClientTest()

    // 2. Start the service
    s.Serve()
}

模拟客户端的一部分位于clienttest()函数中,这很简单逻辑:首先,连接到服务器正在收听的IP地址和端口号,并获得新的连接conn。然后,在循环中,每一秒钟,将字符串“ Hello Zinx”发送到服务器,然后阻止并等待服务器中的响应数据,打印日志并继续循环。

TestServer()是一种单位测试方法。首先,它启动了一个新服务器,其名称为“ [Zinx v0.1]”。然后,它启动了一个goroutine来启动模拟客户端,最后服务器开始其服务。

要执行单元测试,请在Zinx/Znet目录中执行以下代码:
$ 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

现在可以使用最简单的Zinx框架。

1.2.3用zinx-v0.1构建应用程序

如果您发现运行单元测试的“ GO Test”命令,则还可以构建两个应用程序,Server.go和client.go,完全基于Zinx,如下所示:

//Server.go
package main

import (
    "zinx/znet"
)

//Server module's test function
func main() {

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

    //2 start the server
    s.Serve()
}

server.go是使用ZINX Framework开发的后端程序,具有Main()函数和Zinx/Znet软件包的导入。使用ZINX框架构建服务器当前只需要两个步骤:创建服务器对象并启动服务器。

启动服务器。go,使用以下命令:

go run Server.go

接下来,让我们实现client.go的代码,如下:

//Client.go
package main

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

func main() {

    fmt.Println("Client Test ... start")
    //After 3 seconds, initiate a test request to give the server a chance to start the service
    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)
    }
}

在此,客户连接过程的代码是在Main()函数中实现的,该函数与单元测试中的模拟客户端代码几乎相同。启动客户端进行以下命令进行测试:

go run Client.go

以这种方式,即使没有单位测试,您也可以测试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