【Zinx】Day2-Part1:Zinx框架基础路由模块

目录

  • Day2-Part1:Zinx 框架基础路由模块
    • IRequest 消息请求抽象类
      • 创建抽象 IRequest 层
      • 实现 Request 类
    • IRouter 路由配置抽象类
      • 创建抽象的 IRouter 层
      • 实现 Router 类
    • 在 Zinx v0.3 当中集成简单的路由功能
      • IServer 增添路由添加功能
      • 为 Server 添加 Router 成员
      • Connection 结构绑定一个 Router 成员
      • 在 Connection 的方法中调用注册的 Router 进行业务处理
    • Zinx v0.3 完整的 Server 与 Connection 实现
      • zinx/znet/server.go
      • zinx/znet/connection.go
    • 基于 Server 和 Client 对 Zinx v0.3 进行测试

Day2-Part1:Zinx 框架基础路由模块

在昨天对 Zinx 框架的学习过程中,我们已经实现了最基础的 Server,并对 Connection 进行了封装,使其与数据回显业务相关联。最后,我们实现了使用客户端向服务端发送消息,服务端进行数据回显的功能。

现在我们想要更进一步,在数据回显业务的基础上,我们希望可以加入自定义的业务功能,并与连接相关联。显然,我们不能将业务处理的方法绑定在type HandFunc func(*net.TCPConn, []byte, int) error这种格式上,我们需要定义一些接口类,来让用户按照任意格式填写具体的处理业务。

IRequest 消息请求抽象类

现在我们把来自客户端连接信息请求数据放在一个叫 Request 的请求类当中,好处是我们可以从 Request 当中得到全部客户端的请求信息,有助于我们之后进一步对框架进行拓展。

可以理解为,每一次从客户端发送到服务端的请求数据,都将会被封装并保存到一个 Request 结构体当中。

创建抽象 IRequest 层

在定义 Request 结构之前,先在 ziface 下的 irequest.go 中为其定义一个 IRequest 接口:

package ziface

// IRequest 为接口, Request 打包客户端请求的连接信息以及请求数据
type IRequest interface {
	GetConnection() IConnection // 获取请求连接信息
	GetData() []byte            // 获取请求消息的数据
}

IRequest 接口提供了两个 Getter 方法。

实现 Request 类

现在我们在 znet 下创建 IRequest 抽象接口的实例类文件 request.go

package znet

import "zinx/ziface"

type Request struct {
	conn ziface.IConnection // 已经和客户端建立好的连接
	data []byte             // 客户端请求的数据
}

// 确保 Request 实现了 ziface.IRequest 接口
var _ ziface.IRequest = (*Request)(nil)

func (r *Request) GetConnection() ziface.IConnection {
	return r.conn
}

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

现在我们定义好了 Request 类,稍后我们会用到它。

IRouter 路由配置抽象类

接下来我们要为 Zinx 实现一个非常基础的路由功能,目的是让 Zinx 快速步入到路由阶段。

创建抽象的 IRouter 层

首先我们在 ziface 下创建 irouter.go 文件:

package ziface

type IRouter interface {
	PreHandle(request IRequest)  // 处理 conn 业务之前的钩子方法
	Handle(request IRequest)     // 处理 conn 业务的方法
	PostHandle(request IRequest) // 处理 conn 业务之后的钩子方法
}

Router 实际上的作用就是描述服务端应用为客户端发送过来的请求数据提供的业务处理方法,在 Zinx v0.2 中业务方法是固定的(即数据处理),现在我们可以通过实现 IRouter 接口当中的 Handle 来对业务进行自定义。

  • Handle:是处理当前链接的主业务函数;
  • PreHandle:如果需要在主业务函数之前有前置业务,可以重写这个方法;
  • PostHandle:如果需要在主业务函数之后又后置业务,可以重写这个方法;

我们可以看到,PreHandle、Handle 和 PostHandle 都有一个 IRequest 对象作为唯一的形参,也就是将客户端发送过来的数据作为业务的输入。

实现 Router 类

接下来我们在 znet 下创建 router.go

package znet

import "zinx/ziface"

// 实现 router 时, 先嵌入这个基类, 然后根据需要对这个基类的方法进行重写
type BaseRouter struct{}

/*
	此处 BaseRouter 的方法都为空的原因是
	有一些 Router 不需要 PreHandle 和 PostHandle
	所以 Router 全部嵌入 BaseRouter 的好处是, 不需要实现 PreHandle 和 PostHandle 也可以实例化
*/

func (br *BaseRouter) PreHandle(req ziface.IRequest)  {}
func (br *BaseRouter) Handle(req ziface.IRequest)     {}
func (br *BaseRouter) PostHandle(req ziface.IRequest) {}

在 Zinx v0.3 当中集成简单的路由功能

现在我们需要对之前的 Server 和 Connection 的接口以及具体实现进行修改。首先回顾一下,之前 Server 当中没有添加路由的功能,而是我们显式地将一个实现了数据回显的业务函数硬编码到了 Server 当中处理 Connection 业务,而 Connection 结构当中只有一个简单的 handleAPI 成员,用来表示与连接绑定的业务函数。

现在我们来对 Server 和 Connection 进行修改,使得二者可以使用 Router 来实现业务添加和业务绑定与处理。

IServer 增添路由添加功能

首先我们对 Server 的接口 IServer 进行修改,为其添加一个抽象方法AddRouter

// /zinx/ziface/irouter.go
package ziface

// 定义服务器接口
type IServer interface {
	Start()                   // Start 启动服务器方法
	Stop()                    // Stop 停止服务器方法
	Serve()                   // Serve 开启服务器方法
	AddRouter(router IRouter) // 路由功能: 给当前服务注册一个路由业务方法
}

为 Server 添加 Router 成员

修改 Server 的结构,并实现接口新添加的 AddRouter 方法:

type Server struct {
	Name      string         // Name 为服务器的名称
	IPVersion string         // IPVersion: IPv4 or other
	IP        string         // IP: 服务器绑定的 IP 地址
	Port      int            // Port: 服务器绑定的端口
	Router    ziface.IRouter // 当前 Server 由用户绑定的回调 router, 即 Server 注册的链接对应的业务
}

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

工厂函数 NewServer() 也要进行相应的修改:

// NewServer 将创建一个服务器的 Handler
func NewServer(name string) ziface.IServer {
	s := &Server{
		Name:      name,
		IPVersion: "tcp4",
		IP:        "0.0.0.0",
		Port:      7777,
		Router:    nil,
	}

	return s
}

Connection 结构绑定一个 Router 成员

修改完 Server 后,还需要为 Connection 绑定一个 Router 成员,实际上是用 Router 替换了之前的 handleAPI:

type Connection struct {
	Conn         *net.TCPConn   // 当前连接的 socket TCP 套接字
	ConnID       uint32         // 当前连接的 ID, 也可称为 SessionID, 全局唯一
	isClosed     bool           // 当前连接的开启/关闭状态
	Router       ziface.IRouter // 该连接的业务处理方法 router
	ExitBuffChan chan bool      // 告知该连接一经推出/停止的 channel
}

同时修改 Connection 的工厂函数:

// NewConnection 创建新的连接
func NewConnection(conn *net.TCPConn, connID uint32, router ziface.IRouter) *Connection {
	c := &Connection{
		Conn:         conn,
		ConnID:       connID,
		isClosed:     false,
		Router:       router,
		ExitBuffChan: make(chan bool, 1),
	}

	return c
}

在 Connection 的方法中调用注册的 Router 进行业务处理

现在我们进一步对 Connection 的方法进行修改,由于我们使用 Router 替换了之前的 handleAPI 函数,因此我们应该使用 Router 的方法来替代之前 handleAPI 函数实现的功能,即具体的业务处理。

回忆一下,Router 有三个 Handle 方法,输入的参数是客户端请求的 Request 数据,因此我们需要传入对应的数据,并开启 goroutine 执行业务:

// StartReader 开启处理 conn 读数据的 goroutine
func (c *Connection) StartReader() {
	fmt.Println("Reader Goroutine is running")
	defer fmt.Println(c.RemoteAddr().String(), " conn reader exit !")
	defer c.Stop()

	for {
		buf := make([]byte, 512)
		_, err := c.Conn.Read(buf)
		if err != nil {
			fmt.Println("recv buf err ", err)
			c.ExitBuffChan <- true
			continue
		}
		// 得到当前客户端请求的 Request 数据, 也就是客户端想要发送给服务端的数据
		req := Request{
			conn: c,
			data: buf,
		}
		// 从路由 Routers 中找到注册绑定 Conn 的对应 Handle
		go func(request ziface.IRequest) {
			// 执行注册的路由方法
			c.Router.PreHandle(request)
			c.Router.Handle(request)
			c.Router.PostHandle(request)
		}(&req)
	}
}

Zinx v0.3 完整的 Server 与 Connection 实现

现在我们完成了 Server 和 Connection 的修改,并可以自定义路由来为服务端加入自定义的业务逻辑。为了同步进度,Server 和 Connection 的完整实现如下:

zinx/znet/server.go

// zinx/znet/server.go
package znet

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

type Server struct {
	Name      string         // Name 为服务器的名称
	IPVersion string         // IPVersion: IPv4 or other
	IP        string         // IP: 服务器绑定的 IP 地址
	Port      int            // Port: 服务器绑定的端口
	Router    ziface.IRouter // 当前 Server 由用户绑定的回调 router, 即 Server 注册的链接对应的业务
}

// 确保 Server 实现了 ziface.IServer 的所有方法
var _ ziface.IServer = (*Server)(nil)

/* =============== 定义当前客户端连接的 handle api =============== */
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 back buf err", err)
		return errors.New("CallBackToClient error")
	}
	return nil
}

/* =============== 实现 ziface.IServer 接口当中全部的方法 =============== */

// Start 开启 Server 的网络服务
func (s *Server) Start() {
	fmt.Printf("[START] Server listenner at IP: %s, Port %d, is starting\n", s.IP, s.Port)

	// 开启一个 goroutine 去做服务端的 Listener 业务
	go func() {
		// 1. 获取一个 TCP 的 Addr
		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. 监听服务器地址
		listener, err := net.ListenTCP(s.IPVersion, addr)
		if err != nil {
			fmt.Println("listen", s.IPVersion, "err", err)
			return
		}

		// 监听成功
		fmt.Println("start Zinx server  ", s.Name, " succ, now listenning...")

		// TODO: server.go 应该有一个自动生成 ID 的方法, 比如 snowflake
		var cid uint32
		cid = 0

		// 3. 启动 Server 网络连接服务
		for {
			// 3.1 阻塞等待客户端建立连接请求
			conn, err := listener.AcceptTCP()
			if err != nil {
				fmt.Println("Accept err", err)
				continue
			}

			// 3.2 TODO Server.Start() 设置服务器最大连接控制, 如果超过最大连接, 则关闭此新的连接

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

			go dealConn.Start()
		}
	}()
}

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

	// TODO Server.Stop() 将其它需要清理的连接信息或其他信息一并停止或清理
}

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

	// TODO Server.Serve() 是否在启动服务的时候, 还需要做其它事情呢? 比如自定义 Logger 或加入鉴权中间件等

	// 阻塞, 否则 main goroutine 退出, listenner 也将会随之退出
	for {
		time.Sleep(10 * time.Second)
	}
}

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

// NewServer 将创建一个服务器的 Handler
func NewServer(name string) ziface.IServer {
	s := &Server{
		Name:      name,
		IPVersion: "tcp4",
		IP:        "0.0.0.0",
		Port:      7777,
		Router:    nil,
	}

	return s
}

zinx/znet/connection.go

// zinx/znet/connection.go
package znet

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

type Connection struct {
	Conn         *net.TCPConn   // 当前连接的 socket TCP 套接字
	ConnID       uint32         // 当前连接的 ID, 也可称为 SessionID, 全局唯一
	isClosed     bool           // 当前连接的开启/关闭状态
	Router       ziface.IRouter // 该连接的业务处理方法 router
	ExitBuffChan chan bool      // 告知该连接一经推出/停止的 channel
}

// 确保 Connection 实现 ziface.IConenction 方法
var _ ziface.IConnection = (*Connection)(nil)

// NewConnection 创建新的连接
func NewConnection(conn *net.TCPConn, connID uint32, router ziface.IRouter) *Connection {
	c := &Connection{
		Conn:         conn,
		ConnID:       connID,
		isClosed:     false,
		Router:       router,
		ExitBuffChan: make(chan bool, 1),
	}

	return c
}

// StartReader 开启处理 conn 读数据的 goroutine
func (c *Connection) StartReader() {
	fmt.Println("Reader Goroutine is running")
	defer fmt.Println(c.RemoteAddr().String(), " conn reader exit !")
	defer c.Stop()

	for {
		buf := make([]byte, 512)
		_, err := c.Conn.Read(buf)
		if err != nil {
			fmt.Println("recv buf err ", err)
			c.ExitBuffChan <- true
			continue
		}
		// 得到当前客户端请求的 Request 数据, 也就是客户端想要发送给服务端的数据
		req := Request{
			conn: c,
			data: buf,
		}
		// 从路由 Routers 中找到注册绑定 Conn 的对应 Handle
		go func(request ziface.IRequest) {
			// 执行注册的路由方法
			c.Router.PreHandle(request)
			c.Router.Handle(request)
			c.Router.PostHandle(request)
		}(&req)
	}
}

// Start 实现 IConnection 中的方法, 它启动连接并让当前连接开始工作
func (c *Connection) Start() {
	// 开启处理该连接读取到客户端数据之后的业务请求
	go c.StartReader()

	for {
		select {
		case <-c.ExitBuffChan:
			// 得到退出消息则不再阻塞
			return
		}
	}
}

// Stop 停止连接, 结束当前连接状态
func (c *Connection) Stop() {
	// 1. 如果当前连接已经关闭
	if c.isClosed == true {
		return
	}
	c.isClosed = true

	// TODO: Connection Stop() 如果用户注册了该连接的关闭回调业务, 那么应该在此刻显式调用

	// 关闭 socket 连接
	c.Conn.Close()

	// 通知从缓冲队列读数据的业务, 该链接已经关闭
	c.ExitBuffChan <- true

	// 关闭该链接全部管道
	close(c.ExitBuffChan)
}

// GetTCPConnection 从当前连接获取原始的 socket TCPConn
func (c *Connection) GetTCPConnection() *net.TCPConn {
	return c.Conn
}

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

// RemoteAddr 获取远程客户端的地址信息
func (c *Connection) RemoteAddr() net.Addr {
	return c.Conn.RemoteAddr()
}

基于 Server 和 Client 对 Zinx v0.3 进行测试

现在我们分别新建一个模块(文件夹)实现 client 和 server,现阶段的目录组织如下:
【Zinx】Day2-Part1:Zinx框架基础路由模块_第1张图片
Server 的实现如下:

package main

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

// ping test 自定义路由
type PingRouter struct {
	znet.BaseRouter // 一定要先嵌入 BaseRouter
}

// Test PreHandle
func (this *PingRouter) PreHandle(request ziface.IRequest) {
	fmt.Println("Call Router PreHandle")
	_, err := request.GetConnection().GetTCPConnection().Write([]byte("before ping ...\n"))
	if err != nil {
		fmt.Println("call back ping ping ping error")
	}
}

// Test Handle
func (this *PingRouter) Handle(request ziface.IRequest) {
	fmt.Println("Call PingRouter Handle")
	_, err := request.GetConnection().GetTCPConnection().Write([]byte("ping...ping...ping\n"))
	if err != nil {
		fmt.Println("call back ping ping ping error")
	}
}

// Test PostHandle
func (this *PingRouter) PostHandle(request ziface.IRequest) {
	fmt.Println("Call Router PostHandle")
	_, err := request.GetConnection().GetTCPConnection().Write([]byte("After ping ...\n"))
	if err != nil {
		fmt.Println("call back ping ping ping error")
	}
}

// 自定义新的路由, 这次仅实现 Handle
type CustomizeRouter struct {
	znet.BaseRouter
}

// Customize Handle
func (this *CustomizeRouter) Handle(request ziface.IRequest) {
	fmt.Println("Call CustomizeRouter Handle")
	_, err := request.GetConnection().GetTCPConnection().Write([]byte("Hello from YGGP...\n"))
	if err != nil {
		fmt.Println("Hello From YGGP failed...")
	}
}

func main() {
	// 创建一个 Server 句柄
	s := znet.NewServer("[zinx v0.3]")

	//s.AddRouter(&PingRouter{})
	s.AddRouter(&CustomizeRouter{})	

	// 2. 开启服务
	s.Serve()
}

在官方文档的基础上,我新实现了一个 CustomizeRouter,同样是能够成功执行业务的。

Client 的实现如下:

package main

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

func main() {
	fmt.Println("Client Test ... start")

	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("Zinx V0.3"))
		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 端的 main 启动后,Terminal 显示:

 server call back: Hello from YGGP...
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             , cnt = 19
 server call back: Hello from YGGP...
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             , cnt = 19
 server call back: Hello from YGGP...
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             , cnt = 19
 server call back: Hello from YGGP...
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             , cnt = 19

而 Server 的 Terminal 显示:

Add Router succ! 
[START] Server listenner at IP: 0.0.0.0, Port 7777, is starting
start Zinx server   [zinx v0.3]  succ, now listenning...
Reader Goroutine is running
Call CustomizeRouter Handle
Call CustomizeRouter Handle
Call CustomizeRouter Handle
Call CustomizeRouter Handle
Call CustomizeRouter Handle
Call CustomizeRouter Handle
Call CustomizeRouter Handle
... ... ...

实验成功,我们已经实现了 Zinx 框架最简单的路由功能。目前 Zinx 框架当中只能配置一个路由,明天我们将会为 Zinx 实现配置多理由的能力。

你可能感兴趣的:(Golang,Project,网络,服务器,golang)