[Zinx Chapter 2 - First Understanding of Zinx Framework] Liu Danbing-Chuanzhi Podcast-Golang Lightweight Concurrent Server Framework

Keywords: network github socket Mobile

[Zinx tutorial catalogue]
Zinx source code: https://github.com/aceld/zinx
Zinx Develops API Documents
Zinx Chapter 1 - Introduction
Chapter 2 of Zinx - First Understanding of Zinx Framework
Zinx Chapter 3 - Basic Routing Module
Zinx Chapter 4 - Global Configuration
Zinx Chapter 5 - Message Encapsulation
Zinx Chapter 6 - Multi-Routing Patterns
Zinx Chapter VII: A Reading-Writing Separation Model
Chapter 8 of Zinx - Message Queuing and Multitasks
Chapter 9 of Zinx - Link Management
Chapter 10 of Zinx - Connection Property Settings
[In the serials... ]

[Zinx application case-MMO multiplayer online game]
(1) Case introduction
(2)AOI Interest Point Algorithms
(3) protocol buffer of data transmission protocol
(4)Proto3 Protocol Definition
(5) Building projects and putting users online
(6) World Chat
(7) On-line location information synchronization
(8) Mobile Location and AOI Broadcasting
(9) Players offline
[In the serials... ]

Second, First Understanding of Zinx Framework

Here's a look at Zinx's simplest Server prototype.

1. Zinx-V0.1-Basic Server

In order to better see the Zinx framework, Zinx first builds Zinx's two most basic modules, ziface and znet.

Ziface is mainly an abstract layer interface class that stores all modules of Zinx framework. The most basic of Zinx framework is the service class interface iserver, which is defined in ziface module.

znet module is the realization of network-related functions in zinx framework. All network-related modules are defined in znet module.

1.1 Zinx-V0.1 Code Implementation

A Create zinx framework

Create zinx folder under $GOPATH/src

B Create ziface and znet modules

Create ziface and znet folders under zinx/, so that the current file path is as follows:

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

C creates the service module abstraction layer iserver.go under ziface.

zinx/ziface/iserver.go

package ziface

//Define Server Interface
type IServer interface{
	//Start Server Method
	Start()
	//Stop Server Method
	Stop()
	//Opening Business Service Method
	Serve()
}

D implements service module server.go under znet

package znet

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

//iServer interface implementation, defining a Server service class
type Server struct {
	//The name of the server
	Name string
	//tcp4 or other
	IPVersion string
	//IP address of service binding
	IP string
	//Ports for service binding
	Port int
}


//============== Implementation of all interface methods in ziface.IServer========

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

	//Open a go to do service-side Linster business
	go func() {
		//1 Get a TCP Adder
		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 Monitor Server Address
		listenner, err:= net.ListenTCP(s.IPVersion, addr)
		if err != nil {
			fmt.Println("listen", s.IPVersion, "err", err)
			return
		}

		//Has been monitored successfully
		fmt.Println("start Zinx server  ", s.Name, " succ, now listenning...")

		//3 Start server Network Connection Service
		for {
			//3.1 Blocking Waiting for Client to Establish Connection Request
			conn, err := listenner.AcceptTCP()
			if err != nil {
				fmt.Println("Accept err ", err)
				continue
			}

			//3.2 TODO Server.Start() sets the server's maximum connection control and closes the new connection if it exceeds the maximum connection.

			//3.3 TODO Server.Start() Business method for handling the new connection request, at which point handler and conn should be bound

			//Let's do a maximum 512 bytes echo service for the time being.
			go func () {
				//Continuous loops to retrieve 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 display
					if _, err := conn.Write(buf[:cnt]); err !=nil {
						fmt.Println("write back buf err ", err)
						continue
					}
				}
			}()
		}
	}()
}

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

	//TODO Server. Stop () also stops or cleans up other connection information or other information that needs to be cleaned up.
}

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

	//Does TODO Server.Serve() have to deal with other things when starting the service?


	//Block, otherwise the main Go will quit and listener's go will quit
	for {
		time.Sleep(10*time.Second)
	}
}


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

	return s
}

Well, we have completed the basic prototype of Zinx-V0.1, although it is only a basic write-back client data (we will customize the client business method later), then we should test whether our current zinx-V0.1 can be used.

1.2 Zinx Framework Unit Test Sample

In theory, we should be able to import the zinx framework now, then write a server program, and then write a client program for testing, but we can do unit testing through the unit Test function of Go.

Create zinx/znet/server_test.go

package znet

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

/*
	Analog Client
 */
 func ClientTest() {

 	fmt.Println("Client Test ... start")
 	//Three seconds later, test requests are initiated to give the server the opportunity to open 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("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 of Server module
func TestServer(t *testing.T) {

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

	/*
		Client Testing
	*/
	go ClientTest()

	//2 Open Services
	s.Serve()
}

Execution under zinx/znet

$ go test

The results of implementation are 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

Explain that our zinx framework is already available.

1.3 Use Zinx-V0.1 to complete the application

Of course, if go test feels like a hassle, then we can write two applications based entirely on zinx, Server. go, Client. go.

Server.go

package main

import (
	"zinx/znet"
)

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

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

	//2 Open Services
	s.Serve()
}

Start Server.go

go run Server.go

Client.go

package main

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

func main() {

	fmt.Println("Client Test ... start")
	//Three seconds later, test requests are initiated to give the server the opportunity to open 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)
	}
}

Start Client.go for testing

go run Client.go

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

Version V0.1 has already implemented a basic Server framework. Now we need to encapsulate the different services processed by client links and different client links. Of course, we need to build the architecture first.

Now create an interface file iconnection.go under ziface, which belongs to the link. Of course, we put its implementation file in connection.go under znet.

2.1 Zinx-V0.2 Code Implementation

A ziface creates iconnection.go

zinx/ziface/iconnection.go

package ziface

import "net"

//Define the connection interface
type IConnection interface {
	//Start the connection to make the current connection work
	Start()
	//Stop Connection, End Current Connection State M
	Stop()
	//Get the original socket TCPConn from the current connection
	GetTCPConnection() *net.TCPConn
	//Get the current connection ID
	GetConnID() uint32
	//Get the address information of the remote client
	RemoteAddr() net.Addr
	//Send data directly to remote TCP clients
	Send(data []byte) error
	//Send the data to the buffer queue and write it to the client through go, which reads the data from the buffer queue.
	SendBuff(data []byte) error
}

//Define a unified interface for handling linked services
type HandFunc func(*net.TCPConn, []byte, int) error

Some basic methods of this interface, code annotations have been introduced very clearly, here we briefly describe a HandFunc function type, which is the function interface of all conn links in dealing with business. The first parameter is the socket native link, the second parameter is the data requested by the client, and the third parameter is the data length requested by the client. In this way, if we want to specify a conn's processing business, we just need to define a function of HandFunc type and then bind to the link.

B znet Creates iconnection.go

zinx/znet/connection.go

package znet

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

type Connection struct {
	//Currently connected socket TCP sockets
	Conn *net.TCPConn
	//The ID of the current connection can also be called Session ID, which is globally unique.
	ConnID uint32
	//Closed state of current connection
	isClosed bool

	//Processing method api for this connection
	handleAPI ziface.HandFunc

	//Inform the channel that the link has exited/stopped
	ExitBuffChan chan bool
}


//Method of creating connections
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
}

/* Goroutine Processing conn Reading Data */
func (c *Connection) StartReader() {
	fmt.Println("Reader Goroutine is  running")
	defer fmt.Println(c.RemoteAddr().String(), " conn reader exit!")
	defer c.Stop()

	for  {
		//Read our biggest data into 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 current link business (where the current conn's bound handle method is executed)
		if err := c.handleAPI(c.Conn, buf, cnt); err !=nil {
			fmt.Println("connID ", c.ConnID, " handle is error")
			c.ExitBuffChan <- true
			return
		}
	}
}

//Start the connection to make the current connection work
func (c *Connection) Start() {

	//Open the request service to process the link after reading the client data
	go c.StartReader()

	for {
		select {
		case <- c.ExitBuffChan:
			//Get the exit message, no more blocking
			return
		}
	}
}

//Stop Connection, End Current Connection State M
func (c *Connection) Stop() {
	//1. If the current link is closed
	if c.isClosed == true {
		return
	}
	c.isClosed = true

	//TODO Connection Stop() If the user registers the shutdown callback service for the link, the call should be displayed at this point

	// Close socket links
	c.Conn.Close()

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

	//Close all pipes of the link
	close(c.ExitBuffChan)
}

//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 address information of the remote client
func (c *Connection) RemoteAddr() net.Addr {
	return c.Conn.RemoteAddr()
}

//Send data directly to remote TCP clients
func (c *Connection) Send(data []byte) error {
	return nil
}

//Send the data to the buffer queue and write it to the client through go, which reads the data from the buffer queue.
func (c *Connection) SendBuff(data []byte) error {
	return nil
}

C. Re-corrects the connection business for conn in Server.go

zinx/znet/server.go

package znet

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

//iServer interface implementation, defining a Server service class
type Server struct {
	//The name of the server
	Name string
	//tcp4 or other
	IPVersion string
	//IP address of service binding
	IP string
	//Ports for service binding
	Port int
}

//============== Define handle api for the current client link===========
func CallBackToClient(conn *net.TCPConn, data []byte, cnt int) error {
	//Explicit 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
}

//============== Implementation of all interface methods in ziface.IServer========

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

	//Open a go to do service-side Linster business
	go func() {
		//1 Get a TCP Adder
		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 Monitor Server Address
		listenner, err:= net.ListenTCP(s.IPVersion, addr)
		if err != nil {
			fmt.Println("listen", s.IPVersion, "err", err)
			return
		}

		//Has been monitored successfully
		fmt.Println("start Zinx server  ", s.Name, " succ, now listenning...")

		//TODO server.go should have an automatic ID generation method
		var cid uint32
		cid = 0

		//3 Start server Network Connection Service
		for {
			//3.1 Blocking Waiting for Client to Establish Connection Request
			conn, err := listenner.AcceptTCP()
			if err != nil {
				fmt.Println("Accept err ", err)
				continue
			}

			//3.2 TODO Server.Start() sets the server's maximum connection control and closes the new connection if it exceeds the maximum connection.

			//3.3 Business methods for handling this new connection request, where handler and conn are bound
			dealConn := NewConntion(conn, cid, CallBackToClient)
			cid ++

			//3.4 Start Processing Business for Current Links
			go dealConn.Start()
		}
	}()
}

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

	//TODO Server. Stop () also stops or cleans up other connection information or other information that needs to be cleaned up.
}

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

	//Does TODO Server.Serve() have to deal with other things when starting the service?

	//Block, otherwise the main Go will quit and listener's go will quit
	for {
		time.Sleep(10*time.Second)
	}
}

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

	return s
}

CallBackToClient is our handle method to bind the conn object of the current client. Of course, it is now the echo service of the server-side compulsory binding. We will enrich the framework later so that the user can customize the specified handle.

In the start() method, we mainly make the following modifications:

			//3.3 Business methods for handling this new connection request, where handler and conn are bound
			dealConn := NewConntion(conn, cid, CallBackToClient)
			cid ++

			//3.4 Start Processing Business for Current Links
			go dealConn.Start()

Okay, now that we have bound connection and handle, let's test whether Zinx-V0.2 framework can be used.

2.2 Complete the application using Zinx-V0.2

In fact, the external interface of Zinx framework has not changed at present, so the test of V0.1 is still valid.

Server.go

package main

import (
	"zinx/znet"
)

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

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

	//2 Open Services
	s.Serve()
}

Start Server.go

go run Server.go

Client.go

package main

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

func main() {

	fmt.Println("Client Test ... start")
	//Three seconds later, test requests are initiated to give the server the opportunity to open 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)
	}
}

Start Client.go for testing

go run Client.go

Now we've simply started the prototype of Zinx, but it's still a long way from our real framework. Next, let's improve the Zinx framework.

### About the author:

Author: Aceld (Liu Danbing)
Brief Book Number: IT No Cliff

mail: danbing.at@gmail.com
github: https://github.com/aceld
Original book gitbook: http://legacy.gitbook.com/@aceld

Original Statement: Do not reproduce without permission of the author, or reproduce, please indicate the source!

Posted by kylebud on Mon, 18 Mar 2019 02:48:26 -0700