Develop go-tcp framework from 0 to 1 [1-build server, encapsulate connection and business binding, implement basic Router, extract global configuration file]

Develop go-tcp framework from 0 to 1 [1-build server, package connection and business binding, implement basic Router]

This issue mainly completes the construction of the Server, encapsulation of connection and business binding, implementation of the basic Router (the part that handles the business), and the extraction of the global configuration file of the framework

  • Read data from the configuration file (server listening port, listening IP, etc.), and complete specific business operations through custom Router

First version final project structure:
insert image description here

1 Build the basic server[V1.0]

1.1 Write server side

  • Write iserver.go to define the server interface
  • Write server.go, define the server structure, and implement the interface

①/zinx/ziface/iserver.go:

package ziface

type IServer interface {
    
    
	Start()
	Stop()
	Serve()
}

②/zinx/znet/server.go

package znet

import (
	"fmt"
	"net"
)

type Server struct {
    
    
	Name      string
	IPVersion string
	IP        string
	Port      int
}

func NewServer(name string) *Server {
    
    
	s := &Server{
    
    
		Name:      name,
		IPVersion: "tcp4",
		IP:        "0.0.0.0",
		Port:      8090,
	}
	return s
}

func (s *Server) Start() {
    
    
	//启动服务监听端口
	fmt.Printf("[start] Server listener at IP:%s, Port %d is starting\n", s.IP, s.Port)

	go func() {
    
    
		addr, err := net.ResolveTCPAddr(s.IPVersion, fmt.Sprintf("%s:%d", s.IP, s.Port))
		if err != nil {
    
    
			fmt.Printf("resolve tcp addr error %v\n", err)
			return
		}
		listener, err := net.ListenTCP(s.IPVersion, addr)
		if err != nil {
    
    
			fmt.Println("listen ", s.IPVersion, " err ", err)
			return
		}
		fmt.Println("[start] Zinx server success ", s.Name, "Listening...")
		//阻塞连接,处理业务
		for {
    
    
			conn, err := listener.AcceptTCP()
			if err != nil {
    
    
				fmt.Println("Accept err ", err)
				continue
			}
			//处理业务:回显消息
			go func() {
    
    
				for {
    
    
					buf := make([]byte, 512)
					cnt, err := conn.Read(buf)
					if err != nil {
    
    
						fmt.Println("read buf err ", err)
						continue
					}
					fmt.Printf("receive client buf %s, cnt %d \n", buf, cnt)
					//回显读取到的字节数
					if _, err := conn.Write(buf[:cnt]); err != nil {
    
    
						fmt.Println("write buf err ", err)
						continue
					}
				}

			}()
		}
	}()
}

func (s *Server) Stop() {
    
    

}

func (s *Server) Serve() {
    
    
	s.Start()
	//阻塞,一直读取客户端所发送过来的消息
	select {
    
    }
}

1.2 Test server-side functions

① Create Server.go and Client.go

  1. Write myDemo/zinxV1.0/Server.go
package main

import "myTest/zinx/znet"

func main() {
    
    
	s := znet.NewServer("[Zinx v1.0]")
	s.Serve()
}
  1. Write myDemo/zinxV1.0/Client.go
package main

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

/*
模拟客户端
*/
func main() {
    
    
	fmt.Println("client start...")
	time.Sleep(time.Second * 1)
	//1 创建服务器连接
	conn, err := net.Dial("tcp", "127.0.0.1:8090")
	if err != nil {
    
    
		fmt.Println("client start err ", err)
		return
	}
	for {
    
    
		//2 调用连接向服务器发数据
		_, err := conn.Write([]byte("Hello Zinx v0.1"))
		if err != nil {
    
    
			fmt.Println("write conn err ", err)
			return
		}
		// 3 读取服务器返回的数据
		buf := make([]byte, 512)
		cnt, err := conn.Read(buf)
		if err != nil {
    
    
			fmt.Println("client read buf err ", err)
			return
		}
		fmt.Printf("server call back:%s, cnt=%d\n", buf, cnt)
		//cpu阻塞,让出cpu时间片,避免无限for循环导致其他程序无法获取cpu时间片
		time.Sleep(time.Second * 1)
	}
}

②Test results

insert image description here

It can be seen that the server receives data from the client and echoes it every 1 second

2 Package connection conn, business binding [V2.0]

In version V0.1, we have implemented a basic server framework. Now we need to make another layer of interface encapsulation for client links and different businesses handled by different client links. Of course, we first Build the structure.
Now create an interface file iconnection.go under ziface. Of course, we put its implementation file in connection.go under znet.

Required method:

  1. start connection
  2. stop connection
  3. Get the connected conn object
  4. get the id of the connection
  5. Get the address and port of the client connection
  6. method of sending data
  7. The function of processing business bound by the connection

2.1 Package Conn

  • Define the iconnection interface
  • Create a connection structure and implement iconnection
  1. Create /zinx/ziface/iconnection.go:
package ziface

import "net"

type IConnection interface {
    
    
	//启动连接
	Start()
	//停止连接
	Stop()
	//获取当前连接的Conn对象
	GetTCPConnection() *net.TCPConn
	//获取当前连接模块的id
	GetConnectionID() uint32
	//获取远程客户端的TCP状态 IP:Port
	RemoteAddr() net.Addr
	//发送数据
	Send()
}

//定义一个处理连接业务的方法
type HandleFunc func(*net.TCPConn, []byte, int) error
  1. Create /zinx/znet/connection.go
package znet

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

type Connection struct {
    
    
	Conn      *net.TCPConn
	ConnID    uint32
	isClosed  bool
	handleAPI ziface.HandleFunc
	//告知当前的连接已经退出
	ExitChan chan bool
}

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

func (c *Connection) StartReader() {
    
    
	fmt.Println("reader goroutine is running...")
	defer fmt.Println("connID=", c.ConnID, "Reader is exit, remote addr is ", c.RemoteAddr().String())
	defer c.Stop()
	//读取数据
	for {
    
    
		buf := make([]byte, 512)
		cnt, err := c.Conn.Read(buf)
		if err != nil {
    
    
			fmt.Printf("connID %d receive buf err %s\n", c.ConnID, err)
			continue
		}
		//调用当前所绑定的处理业务的方法HandleAPI
		if err := c.handleAPI(c.Conn, buf, cnt); err != nil {
    
    
			fmt.Println("ConnID", c.ConnID, " handle is err ", err)
			break
		}
	}
}

//启动连接
func (c *Connection) Start() {
    
    
	fmt.Printf("ConnID %d is Start...", c.ConnID)
	go c.StartReader()
}

//停止连接
func (c *Connection) Stop() {
    
    
	fmt.Println("Connection Stop()...ConnectionID = ", c.ConnID)
	if c.isClosed {
    
    
		return
	}
	c.isClosed = true
	c.Conn.Close()
	close(c.ExitChan)
}

//获取当前连接的Conn对象
func (c *Connection) GetTCPConnection() *net.TCPConn {
    
    
	return c.Conn
}

//获取当前连接模块的id
func (c *Connection) GetConnectionID() uint32 {
    
    
	return c.ConnID
}

//获取远程客户端的TCP状态 IP:Port
func (c *Connection) RemoteAddr() net.Addr {
    
    
	return c.Conn.RemoteAddr()
}

//发送数据
func (c *Connection) Send() {
    
    

}

2.2 Modify server.go (realize business processing through the encapsulated conn)

Will modify server.go and add CallBackToClient method to realize specific business
insert image description here

Replace the processing business logic part of server.go in ZinxV1.0 version with the encapsulated Conn to call
insert image description here
all codes:
/zinx/znet/server.go:

package znet

import (
	"fmt"
	"github.com/kataras/iris/v12/x/errors"
	"net"
)

type Server struct {
    
    
	Name      string
	IPVersion string
	IP        string
	Port      int
}

func NewServer(name string) *Server {
    
    
	s := &Server{
    
    
		Name:      name,
		IPVersion: "tcp4",
		IP:        "0.0.0.0",
		Port:      8090,
	}
	return s
}

//定义当前客户端连接所绑定的handleAPI(暂时写死处理业务逻辑:数据回显)
func CallBackToClient(conn *net.TCPConn, data []byte, cnt int) error {
    
    
	fmt.Println("[Conn handle] CallBackToClient....")
	if _, err := conn.Write(data[:cnt]); err != nil {
    
    
		fmt.Println("write buf err ", err)
		return errors.New("CallBackToClient error")
	}
	return nil
}

func (s *Server) Start() {
    
    
	//启动服务监听端口
	fmt.Printf("[start] Server listener at IP:%s, Port %d is starting\n", s.IP, s.Port)

	go func() {
    
    
		addr, err := net.ResolveTCPAddr(s.IPVersion, fmt.Sprintf("%s:%d", s.IP, s.Port))
		if err != nil {
    
    
			fmt.Printf("resolve tcp addr error %v\n", err)
			return
		}
		listener, err := net.ListenTCP(s.IPVersion, addr)
		if err != nil {
    
    
			fmt.Println("listen ", s.IPVersion, " err ", err)
			return
		}
		fmt.Println("[start] Zinx server success ", s.Name, "Listening...")
		//阻塞连接,处理业务
		for {
    
    
			conn, err := listener.AcceptTCP()
			if err != nil {
    
    
				fmt.Println("Accept err ", err)
				continue
			}
			var cid uint32 = 0
			dealConn := NewConnection(conn, cid, CallBackToClient)
			cid++
			//开启goroutine处理启动当前conn
			go dealConn.Start()
			处理业务:回显消息
			//go func() {
    
    
			//	for {
    
    
			//		buf := make([]byte, 512)
			//		cnt, err := conn.Read(buf)
			//		if err != nil {
    
    
			//			fmt.Println("read buf err ", err)
			//			continue
			//		}
			//		fmt.Printf("receive client buf %s, cnt %d \n", buf, cnt)
			//		//回显读取到的字节数
			//		if _, err := conn.Write(buf[:cnt]); err != nil {
    
    
			//			fmt.Println("write buf err ", err)
			//			continue
			//		}
			//	}
			//
			//}()
		}
	}()
}

func (s *Server) Stop() {
    
    

}

func (s *Server) Serve() {
    
    
	s.Start()
	//阻塞,一直读取客户端所发送过来的消息
	select {
    
    }
}

2.3 Test ZinxV2.0 function

① Modify the log printing of Server.go and Client.go

Create /myDemo/ZinxV2.0/Client.go and /myDemo/ZinxV2.0/Server.go, this part of the test code is the same as V1.0, just replace the print log with Zinx2.0

  • Client.go
package main

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

/*
模拟客户端
*/
func main() {
    
    
	fmt.Println("client start...")
	time.Sleep(time.Second * 1)
	//1 创建服务器连接
	conn, err := net.Dial("tcp", "127.0.0.1:8090")
	if err != nil {
    
    
		fmt.Println("client start err ", err)
		return
	}
	for {
    
    
		//2 调用连接向服务器发数据
		_, err := conn.Write([]byte("Hello Zinx v0.2"))
		if err != nil {
    
    
			fmt.Println("write conn err ", err)
			return
		}
		// 3 读取服务器返回的数据
		buf := make([]byte, 512)
		cnt, err := conn.Read(buf)
		if err != nil {
    
    
			fmt.Println("client read buf err ", err)
			return
		}
		fmt.Printf("server call back:%s, cnt=%d\n", buf, cnt)
		//cpu阻塞,让出cpu时间片,避免无限for循环导致其他程序无法获取cpu时间片
		time.Sleep(time.Second * 1)
	}
}
  • Server.go
package main

import "myTest/zinx/znet"

func main() {
    
    
	s := znet.NewServer("[Zinx v2.0]")
	s.Serve()
}

②Test results

insert image description here

3 Realize the basic Router[V3.0]

3.1 Request encapsulation

Bind connections and data together

zinx/ziface/irequest.go:

package ziface

import "net"

type IRequest interface {
    
    
	GetConnection() *net.TCPConn
	GetData() []byte
}

zinx/znet/request.go:

package znet

import "net"

type Request struct {
    
    
	conn *net.TCPConn
	data []byte
}

func (r *Request) GetConnection() *net.TCPConn {
    
    
	return r.conn
}

func (r *Request) GetData() []byte {
    
    
	return r.data
}

3.2 Router module

zinx/ziface/irouter.go

package ziface

type IRouter interface {
    
    
	//处理请求之前的方法
	PreHandle(request IRequest)
	Handler(request IRequest)
	//处理请求之后的方法
	PostHandler(request IRequest)
}

zinx/znet/router.go

package znet

import "myTest/zinx/ziface"

type BaseRouter struct {
    
    
}

//这里做了空实现,直接让后续Router继承BaseRouter,然后根据需要重写对应方法即可
func (br *BaseRouter) PreHandle(request ziface.IRequest) {
    
    }
func (br *BaseRouter) Handler(request ziface.IRequest)   {
    
    }

func (br *BaseRouter) PostHandler(request ziface.IRequest) {
    
    }

3.3 Framework integrated router module

  • Cancel the HandlerFunc module in znet/server.go and change it to Router. Add Router property in server.go
    insert image description here
    insert image description here
  • Change the callback_api ziface.HandleFunc parameter in znet/connection.go to Router
    insert image description here

zinx/znet/connection.go

package znet

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

type Connection struct {
    
    
	Conn     *net.TCPConn
	ConnID   uint32
	isClosed bool
	//告知当前的连接已经退出
	ExitChan chan bool
	Router   ziface.IRouter
}

func NewConnection(conn *net.TCPConn, connID uint32, router ziface.IRouter) *Connection {
    
    
	c := &Connection{
    
    
		Conn:   conn,
		ConnID: connID,
		Router: router,
		isClosed: false,
		ExitChan: make(chan bool, 1),
	}
	return c
}

func (c *Connection) StartReader() {
    
    
	fmt.Println("reader goroutine is running...")
	defer fmt.Println("connID=", c.ConnID, "Reader is exit, remote addr is ", c.RemoteAddr().String())
	defer c.Stop()
	//读取数据
	for {
    
    
		buf := make([]byte, 512)
		_, err := c.Conn.Read(buf)
		if err != nil {
    
    
			fmt.Printf("connID %d receive buf err %s\n", c.ConnID, err)
			continue
		}
		//封装请求,改为router处理
		r := Request{
    
    
			conn: c.Conn,
			data: buf,
		}
		go func(request ziface.IRequest) {
    
    
			c.Router.PreHandle(request)
			c.Router.Handler(request)
			c.Router.PostHandler(request)
		}(&r)
	}
}

//启动连接
func (c *Connection) Start() {
    
    
	fmt.Printf("ConnID %d is Start...", c.ConnID)
	go c.StartReader()
}

//停止连接
func (c *Connection) Stop() {
    
    
	fmt.Println("Connection Stop()...ConnectionID = ", c.ConnID)
	if c.isClosed {
    
    
		return
	}
	c.isClosed = true
	c.Conn.Close()
	close(c.ExitChan)
}

//获取当前连接的Conn对象
func (c *Connection) GetTCPConnection() *net.TCPConn {
    
    
	return c.Conn
}

//获取当前连接模块的id
func (c *Connection) GetConnectionID() uint32 {
    
    
	return c.ConnID
}

//获取远程客户端的TCP状态 IP:Port
func (c *Connection) RemoteAddr() net.Addr {
    
    
	return c.Conn.RemoteAddr()
}

//发送数据
func (c *Connection) Send() {
    
    

}

zinx/znet/server.go

package znet

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

type Server struct {
    
    
	Name      string
	IPVersion string
	IP        string
	Port      int
	Router    ziface.IRouter
}

func NewServer(name string) *Server {
    
    
	s := &Server{
    
    
		Name:      name,
		IPVersion: "tcp4",
		IP:        "0.0.0.0",
		Port:      8090,
		Router:    nil,
	}
	return s
}

func (s *Server) Start() {
    
    
	//启动服务监听端口
	fmt.Printf("[start] Server listener at IP:%s, Port %d is starting\n", s.IP, s.Port)

	go func() {
    
    
		addr, err := net.ResolveTCPAddr(s.IPVersion, fmt.Sprintf("%s:%d", s.IP, s.Port))
		if err != nil {
    
    
			fmt.Printf("resolve tcp addr error %v\n", err)
			return
		}
		listener, err := net.ListenTCP(s.IPVersion, addr)
		if err != nil {
    
    
			fmt.Println("listen ", s.IPVersion, " err ", err)
			return
		}
		fmt.Println("[start] Zinx server success ", s.Name, "Listening...")
		//阻塞连接,处理业务
		for {
    
    
			conn, err := listener.AcceptTCP()
			if err != nil {
    
    
				fmt.Println("Accept err ", err)
				continue
			}
			var cid uint32 = 0
			dealConn := NewConnection(conn, cid, s.Router)
			cid++
			//开启goroutine处理启动当前conn
			go dealConn.Start()
		}
	}()
}

func (s *Server) Stop() {
    
    

}

func (s *Server) Serve() {
    
    
	s.Start()
	//阻塞,一直读取客户端所发送过来的消息
	select {
    
    }
}

func (s *Server) AddRouter(router ziface.IRouter) {
    
    
	s.Router = router
}

Test framework integrated router effect

myDemo/ZinxV3.0/client.go
package main

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

/*
模拟客户端
*/
func main() {
    
    
	fmt.Println("client start...")
	time.Sleep(time.Second * 1)
	//1 创建服务器连接
	conn, err := net.Dial("tcp", "127.0.0.1:8090")
	if err != nil {
    
    
		fmt.Println("client start err ", err)
		return
	}
	for {
    
    
		//2 调用连接向服务器发数据
		_, err := conn.Write([]byte("Hello Zinx v0.3"))
		if err != nil {
    
    
			fmt.Println("write conn err ", err)
			return
		}
		// 3 读取服务器返回的数据
		buf := make([]byte, 512)
		cnt, err := conn.Read(buf)
		if err != nil {
    
    
			fmt.Println("client read buf err ", err)
			return
		}
		fmt.Printf("server call back:%s, cnt=%d\n", buf, cnt)
		//cpu阻塞,让出cpu时间片,避免无限for循环导致其他程序无法获取cpu时间片
		time.Sleep(time.Second * 1)
	}
}
myDemo/ZinxV3.0/server.go
package main

import (
	"fmt"
	"myTest/zinx/ziface"
	"myTest/zinx/znet"
)

//自定义一个Router,测试路由功能
type PingRouter struct {
    
    
	znet.BaseRouter
}

func (pr *PingRouter) PreHandle(request ziface.IRequest) {
    
    
	_, err := request.GetConnection().Write([]byte("pre handle success..."))
	if err != nil {
    
    
		fmt.Println("server call pre handle err ", err)
		return
	}
	fmt.Println("server call pre handle...")
}

func (pr *PingRouter) Handler(request ziface.IRequest) {
    
    
	_, err := request.GetConnection().Write([]byte("handle success..."))
	if err != nil {
    
    
		fmt.Println("server call handle err ", err)
		return
	}
	fmt.Println("server call handler....")
}

func (pr *PingRouter) PostHandler(request ziface.IRequest) {
    
    
	_, err := request.GetConnection().Write([]byte("post handle success..."))
	if err != nil {
    
    
		fmt.Println("server call post handle err ", err)
		return
	}
	fmt.Println("server call post handler...")
}

func main() {
    
    
	s := znet.NewServer("[Zinx v3.0]")
	//添加自定义路由
	router := &PingRouter{
    
    }
	s.AddRouter(router)
	s.Serve()
}

final effect:
insert image description here

According to the template method design pattern, the call is completed

4 Extract the global configuration file [V4.0]

4.1 Write /zinx/util/globalobj.go

Mainly used to read the information of the zinx configuration file

package util

import (
	"encoding/json"
	"io/ioutil"
	"myTest/zinx/ziface"
)

type GlobalObj struct {
    
    
	TCPServer ziface.IServer //当前全局Zinx的server对象
	Host      string         //当前服务器主机监听的ip
	TcpPort   int            //当前服务器主机监听的端口号
	Name      string         //当前服务器的名称

	Version        string //当前Zinx的版本号
	MaxConn        int    //当前服务器所允许的最大连接数
	MaxPackageSize uint32 //当前Zinx框架数据包的最大值
}

var GlobalObject *GlobalObj

//从配置文件中重新加载GlobalObject的信息
func (g *GlobalObj) Reload() {
    
    
	data, err := ioutil.ReadFile("conf/zinx.json")
	if err != nil {
    
    
		panic(err)
	}
	//将json文件数据解析到struct中
	err = json.Unmarshal(data, &GlobalObject)
	if err != nil {
    
    
		panic(err)
	}
}

//在其他文件导入该util包的时候会加载init
func init() {
    
    
	GlobalObject = &GlobalObj{
    
    
		Name:           "ZinxServerApp",
		Version:        "V0.4",
		TcpPort:        8090,
		Host:           "0.0.0.0",
		MaxConn:        120,
		MaxPackageSize: 4096,
	}
	//尝试从conf/zinx.json中去加载用户自定义的参数
	GlobalObject.Reload()
}

4.2 Replace the hard code in server.go before

Include /zinx/znet/server.go and /zinx/znet/connection.go sections

  • server:
    insert image description here
  • connection:insert image description here

4.3 Testing

Write myDemo/ZinxV4.0

  • And write the corresponding .json configuration file (Client.go and Server.go are the same as V3.0)

insert image description here
zinx.json

{
    
    
  "Name": "Zinx Server Application",
  "Version": "V0.4",
  "Host": "0.0.0.0",
  "TcpPort": 8091,
  "MaxConn": 30,
  "MaxPackageSize": 1024
}

Final effect:
insert image description here

Reference: https://www.yuque.com/aceld/npyr8s/bgftov

Guess you like

Origin blog.csdn.net/weixin_45565886/article/details/131973229