Golang Zinx 框架入门

简介

Zinx 是一个基于 Golang 的轻量级并发服务器框架

Golang 轻量级并发服务器框架 zinx 文档)

教程地址 :【zinx-Golang 轻量级 TCP 服务器框架(适合 Go 语言自学-深入浅出)】

我自己的学习笔记: Xenolies/ZinxStudy: Golan-Zinx 框架学习 (github.com)

Zinx 架构

import.png

Zinx v0.1 基础 Server

Zinx 框架有两个最基本的模块 Zifaceznet .

ziface  主要是存放一些 Zinx 框架的全部模块的抽象层接口类。

Zinx 框架的最基本的服务类接口  iserver,定义在ziface模块中。

znet  模块是 Zinx 框架中网络相关功能的实现,所有网络相关模块定义在这里。

定义 iserver 接口

服务接口有三个最基础功能,启动服务器,运行服务,还有停止服务.

需要注意的是启动服务器可以写到运行服务中,来使用更方便

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package ziface

// IServer Server 定义一个服务器接口
type IServer interface {
   // Start 启动服务器
   Start()

   // Stop 停止服务器
   Stop()

   // Serve 运行服务
   Serve()

}

实例化 iserver 接口

定义了接口需要实例化接口,所以在 znet 下建立一个 server.go文件,将接口实例化

这里编写了了个回显业务来返回客户端发送的数据.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package znet

import (
	"ZinxDemo01/Zinx/Ziface"
	"fmt"
	"net"
)

// Server 实现 IServer 接口 ,定义一个服务器模块
type Server struct {
	// 服务器名称
	ServerName string
	// 服务器IP版本
	IpVersion string
	// 服务器端口
	IP string
	// 服务器监听端口
	Port int
}

func (s *Server) Start() {
	fmt.Printf("[START] Server Listener at IP: %s , Port %d is Starting\n", s.IP, s.Port)

	go func() {
		// 获取一个TCP的addr
		addr, err := net.ResolveTCPAddr(s.IpVersion, fmt.Sprintf("%s:%d", s.IP, s.Port))
		if err != nil {
			fmt.Println("net.ResolveIPAddr Error : ", err)
			return
		}
		// 监听服务器地址
		tcpListener, err := net.ListenTCP(s.IpVersion, addr)
		if err != nil {
			fmt.Println("net.ListenTCP Error : ", err)
			return
		}
		fmt.Println("Start Zinx Server Success! ", s.ServerName, "is Listening")
		// 阻塞等待客户端链接和处理客户端链接业务(读写)
		for {
			tcpConn, err := tcpListener.AcceptTCP()
			if err != nil {
				fmt.Println("tcpListener.AcceptTCP Error : ", err)
				continue
			}
			// 客户端链接后的读写操作
			// 做一个最基本的512字节长度的回显业务
			go func() {
				for {
					buf := make([]byte, 512)
					read, err := tcpConn.Read(buf)
					if err != nil {
						fmt.Println("tcpConn.Read Error : ", err)
						continue
					}
					fmt.Printf("Zinx Server Read: %s\n", buf[:read])
					// 回显
					if _, err := tcpConn.Write(buf[:read]); err != nil {
						fmt.Println("tcpConn.Write Error: ", err)
						continue
					}
				}
			}()
		}
	}()
}

func (s *Server) Stop() {
	// 服务器终止
}

func (s *Server) Serve() {
	// 启动服务
	s.Start()
	// 建立阻塞状态
	select {}
}

// NewServer 初始化 Server 模块
func NewServer(name string) Ziface.Server {
	s := &Server{
		ServerName: name,
		IpVersion:  "tcp4",
		IP:         "0.0.0.0",
		Port:       8899,
	}
	return s
}

回显业务测试

这里编写一个返回客户端发送数据的业务.

然后创建一个目录名为TestDemo ,其下创建一个目录名为ClientDemo,

ClientDemo目录下创建一个名为 Client.go ,模拟客户端输入.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

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

/*
模拟客户端
*/
func main() {
   fmt.Println("Client Start...")

   // 创建TCP连接,得到Conn连接
   conn, err := net.Dial("tcp", "127.0.0.1:8899")
   if err != nil {
      fmt.Println("net.Dial Error : ", err)
      return
   }

   for {
      // 调用Write写数据
      _, err = conn.Write([]byte("Hello,Zinx Server!"))
      if err != nil {
         fmt.Println("conn.Write Error : ", err)
         return
      }

      buf := make([]byte, 512)
      read, err := conn.Read(buf)
      if err != nil {
         fmt.Println("conn.Read Error : ", err)
         return
      }
      fmt.Printf("Server Back: %s\n", buf[:read])

      time.Sleep(2 * time.Second)
   }

}

接着在 TestDemo 创建一个目录 ServerDemo ,在 ServerDemo 目录下创建一个名为 Server.go 来建立 Zinx 服务.

TestDemo/ServerDemo/Server.go 中 写下如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import "ZinxDemo01/Zinx/znet"

/**
基于 Zinx开发的服务端应用
*/

func main() {
	s := znet.NewServer("[Zinx0.1]")
	//启动服务
	s.Serve()
}

然后启动服务器.

此时服务器输出 :

1
2
[START] Server Listener at IP: 0.0.0.0 , Port 8899 is Starting
Start Zinx Server Success!  [Zinx0.1] is Listening

说明服务器启动成功

接着启动客户端服务,客户端向服务端发送 Hello,Zinx Server!

1
2
3
Client Start...
Server Back: Hello,Zinx Server!
Server Back: Hello,Zinx Server!

此时服务器回显:

1
2
3
4
5
6
7
[START] Server Listener at IP: 0.0.0.0 , Port 8899 is Starting
Start Zinx Server Success!  [Zinx0.1] is Listening
Zinx Server Read: Hello,Zinx Server!
Zinx Server Read: Hello,Zinx Server!
Zinx Server Read: Hello,Zinx Server!
Zinx Server Read: Hello,Zinx Server!
Zinx Server Read: Hello,Zinx Server!

Zinx 框架 v0.1 基础的 Server 模块搭建成功

Zinx v0.2 链接封装与业务绑定

链接接口封装

一个客户端就要使用一个匿名函数处理是完全不够用的 ,所以需要定义 Connection (链接) 模块来实现对于客户端链接的处理, 首先创建一个 IConnection.go 接着编写代码定义一个接口.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package Ziface

import "net"

// Connection 定义连接模块接口
type Connection interface {
   // Start 启动连接 让当前连接准备开始工作
   Start()

   // Stop 停止连接 结束当前连接的工作
   Stop()

   // GetTCPConnection GetTCPConnetion 获取当前链接绑定的 Socket Conn   GetTCPConnection() *net.TCPConn

   // GetConnID 获取当前连接模块的ID
   GetConnID() uint32

   // RemoteAddr 获取远程客户端连接的TCP状态
   RemoteAddr() net.Addr

   // Send 发送数据 将数据发送给远程的客户端
   Send(data []byte) error
}

// HandleFunc 定义一个处理业务的方法
type HandleFunc func(*net.TCPConn, []byte, int) error

接着在 znet 目录创建一个名为 Connection.go 的文件来实现接口.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package znet

import (
   "net"
)

// Connection 当前连接的模块
type Connection struct {
   // 当前连接的Socket TCP 套接字
   Conn *net.TCPConn

   // 当前连接的ID
   ConnID uint32

   // 当前连接的状态
   isClosed bool

   // 当前连接的绑定的处理业务的方法
   handleAPI Ziface.HandleFunc

   // 告知当前连接退出的Channel
   ExitChan chan bool
}

// NewConnection 初始化连接模块的方法
func NewConnection(conn *net.TCPConn, connID uint32, callbackAPI Ziface.HandleFunc) *Connection {
   c := &Connection{
      Conn:      conn,
      ConnID:    connID,
      handleAPI: callbackAPI,
      isClosed:  false,
      ExitChan:  make(chan bool, 1),
   }
   return c
}

业务绑定

光实现有个接口和简单的初始化是没用的, 所以需要编写相关业务

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
package znet

import (
   "ZinxDemo01/Zinx/Ziface"
   "fmt"   "net")

// Connection 当前连接的模块
type Connection struct {
   // 当前连接的Socket TCP 套接字
   Conn *net.TCPConn

   // 当前连接的ID
   ConnID uint32

   // 当前连接的状态
   isClosed bool

   // 当前连接的绑定的处理业务的方法
   handleAPI Ziface.HandleFunc

   // 告知当前连接退出的Channel
   ExitChan chan bool
}

// NewConnection 初始化连接模块的方法
func NewConnection(conn *net.TCPConn, connID uint32, callbackAPI Ziface.HandleFunc) *Connection {
   c := &Connection{
      Conn:      conn,
      ConnID:    connID,
      handleAPI: callbackAPI,
      isClosed:  false,
      ExitChan:  make(chan bool, 1),
   }
   return c
}

// StartReader 连接读的业务
func (c *Connection) StartReader() {
   fmt.Println("Reader Goroutine is Running....")
   defer fmt.Printf("ConnID: %d Reader is Exit, Remote Addr is : %s", c.ConnID, c.RemoteAddr().String())
   defer c.Stop()

   for {
      // 建立阻塞读取客户端数据到buf中
      buf := make([]byte, 512)

      read, err := c.Conn.Read(buf)
      if err != nil {
         fmt.Printf("c.Conn.Read Error: %s\n", err)
         continue
      }

      // 调用当前连接绑定的HandleAPI
      if err := c.handleAPI(c.Conn, buf, read); err != nil {
         fmt.Printf("c.ConnID: %d , Handle Error: %s\n", c.ConnID, err)
         break
      }

   }

}

// Start 启动连接 让当前连接准备开始工作
func (c *Connection) Start() {
   fmt.Println("Connection START...")

   // 启动当前连接读数据的业务
   go c.StartReader()
}

// Stop 停止连接 结束当前连接的工作
func (c *Connection) Stop() {
   fmt.Printf("Connection STOP.... , ConnID: %d\n", c.ConnID)

   //如果当前连接已经关闭
   if c.isClosed {
      return
   }
   c.isClosed = true

   c.Conn.Close()
   close(c.ExitChan)
}

// GetTCPConnection GetTCPConnetion 获取当前链接绑定的 Socket Connfunc (c *Connection) GetTCPConnection() *net.TCPConn {
   return c.Conn
}

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

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

// Send 发送数据 将数据发送给远程的客户端
func (c *Connection) Send(data []byte) error {

   return nil
}

由于有了 Connection 模块,对于客户端的链接可以交给专门的方法了,所以要对 Server 做一些改动. 删除原来用于处理链接回显的 匿名函数,改为链接接口实现里面的方法. 同时也要防止出现冲突,把写一个自增定义唯一一个链接 ID.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package znet

import (
   "ZinxDemo01/Zinx/Ziface"
   "errors"   "fmt"   "net")

// Server 实现 IServer 接口 ,定义一个服务器模块
type Server struct {
   // 服务器名称
   ServerName string
   // 服务器IP版本
   IpVersion string
   // 服务器端口
   IP string
   // 服务器监听端口
   Port int
}

// CallBackToClient 定义当前客户端连接的 Handle API 目前这个 Handle 写死,可以用户自己优化
func CallBackToClient(conn *net.TCPConn, buf []byte, read int) error {
   // 回显业务
   fmt.Println("[ConnHandle] CallBackToClient ...")

   _, err := conn.Write(buf[:read])
   if err != nil {
      fmt.Println("conn.Write CallBackToClient Error: ", err)
      return errors.New("CallBack")
   }
   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() {
      // 获取一个TCP的addr
      addr, err := net.ResolveTCPAddr(s.IpVersion, fmt.Sprintf("%s:%d", s.IP, s.Port))
      if err != nil {
         fmt.Println("net.ResolveIPAddr Error : ", err)
         return
      }
      // 监听服务器地址
      tcpListener, err := net.ListenTCP(s.IpVersion, addr)
      if err != nil {
         fmt.Println("net.ListenTCP Error : ", err)
         return
      }

      var conId uint32
      conId = 0

      fmt.Println("Start Zinx Server Success! ", s.ServerName, "is Listening")

      // 阻塞等待客户端链接和处理客户端链接业务(读写)
      for {
         tcpConn, err := tcpListener.AcceptTCP()
         if err != nil {
            fmt.Println("tcpListener.AcceptTCP Error : ", err)
            continue
         }

         // 客户端链接后的读写操作

         // 将处理新链接的任务方法和Conn绑定得到连接模块
         Conn := NewConnection(tcpConn, conId, CallBackToClient)
         conId++

         //启动连接任务处理
         go Conn.Start()
      }
   }()
}

func (s *Server) Stop() {
   // 服务器终止
}

func (s *Server) Serve() {
   // 启动服务
   s.Start()

   // 建立阻塞状态
   select {}
}

// NewServer 初始化 Server 模块
func NewServer(name string) Ziface.Server {
   s := &Server{
      ServerName: name,
      IpVersion:  "tcp4",
      IP:         "0.0.0.0",
      Port:       8899,
   }
   return s
}

运行测试

服务器端

1
2
3
4
5
6
7
8
9
[START] Server Listener at IP: 0.0.0.0 , Port 8899 is Starting
Start Zinx Server Success!  [Zinx] is Listening
Connection START...
Reader Goroutine is Running....
[ConnHandle] CallBackToClient ...
[ConnHandle] CallBackToClient ...
[ConnHandle] CallBackToClient ...
[ConnHandle] CallBackToClient ...
[ConnHandle] CallBackToClient ...

可以看到客户端的链接被 Connection 模块处理了

客户端

1
2
3
4
5
Client Start...
Server Back: Hello,Zinx Server!
Server Back: Hello,Zinx Server!
Server Back: Hello,Zinx Server!
Server Back: Hello,Zinx Server!

Zinx v0.2 链接封装与业务绑定完成

Zinx v0.3 框架路由模块

编写抽象接口

IRequest 消息请求抽象类:

1
2
3
4
5
6
7
8
9
package ziface

// IRequest 接口 将客户端请求的练级信息和请求数据封装到一个Request中
type IRequest interface {
   // GetConnection 得到当前链接
   GetConnection() IConnection
   // GetData 得到请求的消息
   GetData([]byte) []byte
}

IRouter 路由配置抽象类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package ziface

// IRouter 路由抽象接口  路由里面数据都是IRequest
type IRouter interface {
   // PreHandle 处理Conn业务之前的钩子方法 Hook
   PreHandle(request IRequest)
   // Handle 处理 Conn业务的主方法 Hook
   Handle(request IRequest)
   // PostHandle 处理Conn 业务之后的钩子方法 Hook
   PostHandle(request IRequest)
}

实例化接口

实例化 IRouter 这里定义一个 BaseRouter 是为了以后扩展路由,客户可以自定义路由.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package znet

import (
   "ZinxDemo01/Zinx/ziface"
)

// BaseRouter 实现 Router时,线嵌入 BaseRouter基类.然后根据需要对这个基类进行重写
// 实现接口隔离
type BaseRouter struct {
}

// PreHandle 处理Conn业务之前的钩子方法 Hook
func (br *BaseRouter) PreHandle(request ziface.IRequest) {

}

// Handle 处理 Conn业务的主方法 Hook
func (br *BaseRouter) Handle(request ziface.IRequest) {
}

// PostHandle 处理Conn 业务之后的钩子方法 Hook
func (br *BaseRouter) PostHandle(request ziface.IRequest) {
}

实例化 IRequest 接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package znet

import "ZinxDemo01/Zinx/ziface"

type Request struct {
   // 已经和客户端建立好的链接
   conn ziface.IConnection

   //客户端请求的数据
   data []byte
}

// GetConnection 获取客户端链接
func (r *Request) GetConnection() ziface.IConnection {
   return r.conn
}

// GetData 获取用户端请求的数据
func (r *Request) GetData(data []byte) []byte {
   return r.data
}

集成路由模块

在 Connection 模块中添加一个新字段 Router ziface.IRouter 来集成路由模块.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
package znet

import (
   "ZinxDemo01/Zinx/ziface"
   "fmt"   "net")

// Connection 当前连接的模块
type Connection struct {
   // 当前连接的Socket TCP 套接字
   Conn *net.TCPConn

   // 当前连接的ID
   ConnID uint32

   // 当前连接的状态
   isClosed bool
   // 告知当前连接退出的Channel
   ExitChan chan bool

   // 当前连接的Router处理
   Router ziface.IRouter
}

// NewConnection 初始化连接模块的方法
func NewConnection(conn *net.TCPConn, connID uint32, router ziface.IRouter) *Connection {
   c := &Connection{
      Conn:     conn,
      ConnID:   connID,
      isClosed: false,
      ExitChan: make(chan bool, 1),
      Router:   router,
   }
   return c
}

// StartReader 连接读的业务
func (c *Connection) StartReader() {
   fmt.Println("Reader Goroutine is Running....")
   defer fmt.Printf("ConnID: %d Reader is Exit, Remote Addr is : %s", c.ConnID, c.RemoteAddr().String())
   defer c.Stop()

   for {
      // 建立阻塞读取客户端数据到buf中
      buf := make([]byte, 512)

      _, err := c.Conn.Read(buf)
      if err != nil {
         fmt.Printf("c.Conn.Read Error: %s\n", err)
         continue
      }
      // 得到当前Conn数据的Request的请求数据
      req := Request{
         conn: c,
         data: buf,
      }

      // 预注册路由方法
      go func(request ziface.IRequest) {
         c.Router.PreHandle(request)
         c.Router.Handle(request)
         c.Router.PostHandle(request)
      }(&req)

      //在路由中找到注册绑定的Conn的Router调用

   }

}

// Start 启动连接 让当前连接准备开始工作
func (c *Connection) Start() {
   fmt.Println("Connection START...")

   // 启动当前连接读数据的业务
   go c.StartReader()
}

// Stop 停止连接 结束当前连接的工作
func (c *Connection) Stop() {
   fmt.Printf("Connection STOP.... , ConnID: %d\n", c.ConnID)

   //如果当前连接已经关闭
   if c.isClosed {
      return
   }
   c.isClosed = true

   c.Conn.Close()
   close(c.ExitChan)
}

// GetTCPConnection GetTCPConnection 获取当前链接绑定的 Socket Conn
func (c *Connection) GetTCPConnection() *net.TCPConn {
   return c.Conn
}

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

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

// Send 发送数据 将数据发送给远程的客户端
func (c *Connection) Send(data []byte) error {
   return nil
}

之后在 Server 中加入添加路由的方法 ,并且 将原来处理 Handle 的方法交给路由.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package znet

import (
   "ZinxDemo01/Zinx/ziface"
   "fmt"   "net")

// Server 实现 IServer 接口 ,定义一个服务器模块
type Server struct {
   // 服务器名称
   ServerName string
   // 服务器IP版本
   IpVersion string
   // 服务器端口
   IP string
   // 服务器监听端口
   Port int
   // 当前Sever添加一个Router,Server注册的链接处理业务
   Router ziface.IRouter
}

func (s *Server) Start() {
   fmt.Printf("[START] Server Listener at IP: %s , Port %d is Starting\n", s.IP, s.Port)

   go func() {
      // 获取一个TCP的addr
      addr, err := net.ResolveTCPAddr(s.IpVersion, fmt.Sprintf("%s:%d", s.IP, s.Port))
      if err != nil {
         fmt.Println("net.ResolveIPAddr Error : ", err)
         return
      }
      // 监听服务器地址
      tcpListener, err := net.ListenTCP(s.IpVersion, addr)
      if err != nil {
         fmt.Println("net.ListenTCP Error : ", err)
         return
      }

      var conId uint32
      conId = 0

      fmt.Println("Start Zinx Server Success! ", s.ServerName, "is Listening")

      // 阻塞等待客户端链接和处理客户端链接业务(读写)
      for {
         tcpConn, err := tcpListener.AcceptTCP()
         if err != nil {
            fmt.Println("tcpListener.AcceptTCP Error : ", err)
            continue
         }

         // 客户端链接后的读写操作

         // 将处理新链接的任务方法和Conn绑定得到连接模块
         dealConn := NewConnection(tcpConn, conId, s.Router)
         conId++

         //启动连接任务处理
         go dealConn.Start()
      }
   }()
}

func (s *Server) Stop() {
   // 服务器终止
}

func (s *Server) Serve() {
   // 启动服务
   s.Start()

   // 建立阻塞状态
   select {}
}

// NewServer 初始化 Server 模块
func NewServer(name string) ziface.IServer {
   s := &Server{
      ServerName: name,
      IpVersion:  "tcp4",
      IP:         "0.0.0.0",
      Port:       8899,
      Router:     nil,
   }
   return s
}

func (s *Server) AddRouter(router ziface.IRouter) {
   s.Router = router
   fmt.Println("Router Add Success!!")
}

接着在 IServer 接口中添加 AddRouter 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package ziface

// IServer Server 定义一个服务器接口
type IServer interface {
   // Start 启动服务器
   Start()

   // Stop 运行服务器
   Stop()

   // Serve 运行服务
   Serve()

   // AddRouter 路由功能 给当前服务注册一个 路由,来处理客户端链接
   AddRouter(router IRouter)
}

编写自定义路由

这次转到客户层 TestDemo/ServerDemo/Server.go 编写自定义路由.处理 Conn 业务之前,Conn 业务和之后.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package main

import (
   "ZinxDemo01/Zinx/ziface"
   "ZinxDemo01/Zinx/znet"   "fmt")

/**
基于 Zinx开发的服务端应用
*/

func main() {
   s := znet.NewServer("[Zinx]")

   // 当前 Zinx 框架添加 Router   s.AddRouter(&PingRouter{})

   s.Serve()

}

// PingRouter 自定义路由
type PingRouter struct {
   znet.BaseRouter
}

// PreHandle 测试路由
func (pr *PingRouter) PreHandle(request ziface.IRequest) {
   fmt.Println("Call Router PreHandle...")
   _, err := request.GetConnection().GetTCPConnection().Write([]byte("Before Ping....  |  "))
   if err != nil {
      fmt.Println("Router PreHandle Write Error: ", err)
   }

}

// Handle 测试路由
func (pr *PingRouter) Handle(request ziface.IRequest) {
   fmt.Println("Call Router Handle...")
   _, err := request.GetConnection().GetTCPConnection().Write([]byte("....Ping....Ping....Ping....  |  "))
   if err != nil {
      fmt.Println("Router Handle Write Error: ", err)
   }
}

// PostHandle 测试路由
func (pr *PingRouter) PostHandle(request ziface.IRequest) {
   fmt.Println("Call Router PostHandle...")
   _, err := request.GetConnection().GetTCPConnection().Write([]byte("After Ping...."))
   if err != nil {
      fmt.Println("Router PostHandle Write Error: ", err)
   }
}

客户端不变

启动 Zinx 服务器

服务端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Router Add Success!!
[START] Server Listener at IP: 0.0.0.0 , Port 8899 is Starting
Start Zinx Server Success!  [Zinx] is Listening
Connection START...
Reader Goroutine is Running....
Call Router PreHandle...
Call Router Handle...
Call Router PostHandle...
Call Router PreHandle...
Call Router Handle...
Call Router PostHandle...
Call Router PreHandle...
Call Router Handle...
Call Router PostHandle...

服务端

1
2
3
4
5
6
7
Client Start...
Server Back: Before Ping....  |
Server Back: ....Ping....Ping....Ping....  |  After Ping....
Server Back: Before Ping....  |  ....Ping....Ping....Ping....  |  After Ping....Before Ping....  |  ....Ping....Ping....Ping....  |  After Ping....
Server Back: Before Ping....  |
Server Back: ....Ping....Ping....Ping....  |  After Ping....
Server Back: Before Ping....  |  ....Ping....Ping....Ping....  |  After Ping....

这里看到返回的并不是连续的,其实这里遇到了 TCP 粘包的问题.

**TCP粘包出现的原因** :

主要原因就是 TCP 数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。

1.由 Nagle 算法造成的发送端的粘包:Nagle 算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给 TCP 发送时,TCP 并不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。

2.接收端接收不及时造成的接收端粘包:TCP 会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时的把 TCP 的数据取出来,就会造成 TCP 缓冲区中存放了几段数据。

Zinx v0.4 全局配置文件编写

编写全局配置模块

Zinx下创建一个组件目录 utils ,然后在 utils 目录下创建一个全局配置组件 globalObj.go ,编写全局配置模块.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package utils

import (
   "ZinxDemo01/Zinx/ziface"
   "encoding/json"   "os")

/*
全局配置模块
在 "服务器程序/conf/zinx.json"中写入配置
将框架中的硬代码.使用 globalObj进行替换
*/

type GlobalObj struct {
   /*   Server Info   */
	TcpServer ziface.IServer // 当前 Zinx Server 全局对选哪个
	Host      string         // 当前服务器监听IP
    TcpPort   int            // 当前服务器端口
    Name      string         // 按当前服务器名称

   /*  Zinx Info   */
    Version        string // 当前 Zinx 版本号
    MaxConn        int    // 最大链接数量
    MaxPackageSize uint32 // 当前 Zinx 数据包最大值
}

var GlobalObject *GlobalObj

// 提供一个 init 方法,提供一个初始的默认值
func init() {
   GlobalObject = &GlobalObj{
      TcpServer:      nil,
      Host:           "0.0.0.0",
      TcpPort:        8899,
      Name:           "Zinx Server",
      Version:        "v0.4",
      MaxConn:        10,
      MaxPackageSize: 512,
   }

   // 从 conf/zinx.json 加载用户自定义参数
   GlobalObject.Reload()

   //

}

// Reload 从 zinx.json中加载自定义参数
func (g *GlobalObj) Reload() {
   data, err := os.ReadFile("conf/zinx.json")
   if err != nil {
      panic(err)
   }

   // 将 JSON 文件解析到 GlobalObj
   err = json.Unmarshal(data, &GlobalObject)
   if err != nil {
      return
   }

}

全局参数硬代码替换

将之前写死的参数使用全局变量 GlobalObject 替换

Server.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package znet

import (
   "ZinxDemo01/Zinx/utils"
   "ZinxDemo01/Zinx/ziface"   "fmt"   "net")

// Server 实现 IServer 接口 ,定义一个服务器模块
type Server struct {
   // 服务器名称
   ServerName string
   // 服务器IP版本
   IpVersion string
   // 服务器端口
   IP string
   // 服务器监听端口
   Port int
   // 当前Sever添加一个Router,Server注册的链接处理业务
   Router ziface.IRouter
}

func (s *Server) Start() {

   fmt.Printf("[START GlobalObject] Server Listener at IP: %s , Port %d is Starting\n", utils.GlobalObject.Host, utils.GlobalObject.TcpPort)

   fmt.Printf("[START] Server Listener at IP: %s , Port %d is Starting\n", s.IP, s.Port)

   go func() {
      // 获取一个TCP的addr
      addr, err := net.ResolveTCPAddr(s.IpVersion, fmt.Sprintf("%s:%d", s.IP, s.Port))
      if err != nil {
         fmt.Println("net.ResolveIPAddr Error : ", err)
         return
      }
      // 监听服务器地址
      tcpListener, err := net.ListenTCP(s.IpVersion, addr)
      if err != nil {
         fmt.Println("net.ListenTCP Error : ", err)
         return
      }

      var conId uint32
      conId = 0

      fmt.Println("Start Zinx Server Success! [", utils.GlobalObject.Name, "] is Listening")

      fmt.Println("Start Zinx Server Success! ", s.ServerName, " is Listening")

      // 阻塞等待客户端链接和处理客户端链接业务(读写)
      for {
         tcpConn, err := tcpListener.AcceptTCP()
         if err != nil {
            fmt.Println("tcpListener.AcceptTCP Error : ", err)
            continue
         }

         // 客户端链接后的读写操作

         // 将处理新链接的任务方法和Conn绑定得到连接模块
         dealConn := NewConnection(tcpConn, conId, s.Router)
         conId++

         //启动连接任务处理
         go dealConn.Start()
      }
   }()
}

func (s *Server) Stop() {
   // 服务器终止
}

func (s *Server) Serve() {
   // 启动服务
   s.Start()

   // 建立阻塞状态
   select {}
}

// NewServer 初始化 Server 模块
func NewServer(name string) ziface.IServer {
   s := &Server{
      // 使用 utils.GlobalObject 替换
      ServerName: utils.GlobalObject.Name,
      IpVersion:  "tcp4",
      IP:         utils.GlobalObject.Host,
      Port:       utils.GlobalObject.TcpPort,
      Router:     nil,
   }
   return s
}

func (s *Server) AddRouter(router ziface.IRouter) {
   s.Router = router
   fmt.Println("Router Add Success!!")
}

Connection.go

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
package znet

import (
   "ZinxDemo01/Zinx/utils"
   "ZinxDemo01/Zinx/ziface"   "fmt"   "net")

// Connection 当前连接的模块
type Connection struct {
   // 当前连接的Socket TCP 套接字
   Conn *net.TCPConn

   // 当前连接的ID
   ConnID uint32

   // 当前连接的状态
   isClosed bool
   // 告知当前连接退出的Channel
   ExitChan chan bool

   // 当前连接的Router处理
   Router ziface.IRouter
}

// NewConnection 初始化连接模块的方法
func NewConnection(conn *net.TCPConn, connID uint32, router ziface.IRouter) *Connection {
   c := &Connection{
      Conn:     conn,
      ConnID:   connID,
      isClosed: false,
      ExitChan: make(chan bool, 1),
      Router:   router,
   }
   return c
}

// StartReader 连接读的业务
func (c *Connection) StartReader() {
   fmt.Println("Reader Goroutine is Running....")
   defer fmt.Printf("ConnID: %d Reader is Exit, Remote Addr is : %s", c.ConnID, c.RemoteAddr().String())

   defer c.Stop()

   for {
      // 建立阻塞读取客户端数据到buf中
      buf := make([]byte, utils.GlobalObject.MaxPackageSize)
      _, err := c.Conn.Read(buf)
      if err != nil {
         fmt.Printf("c.Conn.Read Error: %s\n", err)
         continue
      }
      // 得到当前Conn数据的Request的请求数据
      req := Request{
         conn: c,
         data: buf,
      }

      // 预注册路由方法
      go func(request ziface.IRequest) {
         c.Router.PreHandle(request)
         c.Router.Handle(request)
         c.Router.PostHandle(request)
      }(&req)

      //在路由中找到注册绑定的Conn的Router调用

   }

}

// Start 启动连接 让当前连接准备开始工作
func (c *Connection) Start() {
   fmt.Println("Connection START...")

   // 启动当前连接读数据的业务
   go c.StartReader()
}

// Stop 停止连接 结束当前连接的工作
func (c *Connection) Stop() {
   fmt.Printf("Connection STOP.... , ConnID: %d\n", c.ConnID)

   //如果当前连接已经关闭
   if c.isClosed {
      return
   }
   c.isClosed = true

   c.Conn.Close()
   close(c.ExitChan)
}

// GetTCPConnection GetTCPConnection 获取当前链接绑定的 Socket Conn
func (c *Connection) GetTCPConnection() *net.TCPConn {
   return c.Conn
}

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

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

// Send 发送数据 将数据发送给远程的客户端
func (c *Connection) Send(data []byte) error {
   return nil
}

开发服务器应用

首先在TestDemo\ServerDemo 下建立 conf/zinx.json 的文件.写上Demo配置

1
2
3
4
5
6
7
{  
  "Name": "Zinx v0.4 Demo",  
  "Host": "127.0.0.1",  
  "TcpPort": "8899",  
  "MaxConn": "3",  
  "MaxPackageSize": "512"  
}

然后开启终端,使用 go run 命令启动服务器 ,可以看到服务端读取配置文件的结果

1
2
3
4
5
6
7
8
PS D:\Zinx-Study\ZinxStudy\TestDemo\ServerDemo> go run Server.go
Router Add Success!!
[START GlobalObject] Server Listener at IP: 127.0.0.1 , Port 8899 is Starting
[START] Server Listener at IP: 127.0.0.1 , Port 8899 is Starting
Start Zinx Server Success! [ Zinx v0.4 Demo ] is Listening
Start Zinx Server Success!  Zinx v0.4 Demo  is Listening
Connection START...
Reader Goroutine is Running....

Zinx v0.5 消息封装模块

0%