<1.Building Basic Services with Zinx Framework>
<2. Zinx-V0.2 Simple Connection Encapsulation and Binding with Business>
<3.Design and Implementation of the Zinx Framework's Routing Module>
<4.Zinx Global Configuration>
在zinx中,有必要为用户提供自定义的连接处理接口。显然,将业务处理方法绑定到格式type HandFunc func(*net.TCPConn, []byte, int)
错误是不够的。在这里,我们需要定义一些interface{}
,以允许用户提供任何格式的连接处理方法。
仅使用func
类型显然无法满足开发要求。现在,我们需要创建几个抽象接口类。
3.1 IREQUEST-消息请求的抽象类
在本节中,我们将将客户请求的连接信息和请求数据组合到称为请求的请求类中。这种方法的好处是,我们可以从请求中获取所有客户的请求信息,这也是将来框架扩展的基础。如果客户端具有其他有意义的数据,则可以将其存储在此请求类中。可以理解的是,每当客户发送完整的请求时,Zinx都会将它们放在请求结构中。
3.1.1创建抽象的iRequest层
在Ziface目录中创建一个新文件IREQUEST.GO。该文件代表IreQuest接口,位于Zinx(Ziface)的抽象层目录中。接口定义如下:
//zinx/ziface/irequest.go
package ziface
/*
IRequest interface:
This interface encapsulates the client's connection
information and request data into a Request.
*/
type IRequest interface {
GetConnection() IConnection // Get the connection information of the request
GetData() []byte // Get the data of the request message
}
很明显,当前的抽象层仅提供两种getter方法,表明必须存在两个成员。一个是客户的连接,另一个是客户传递的数据。当然,随着Zinx框架变得更加丰富,应该添加新成员。
请注意IreQuest中GetConnection()的接口设计。它返回iConnection而不是连接。尽管考虑到设计模式和架构可扩展性,它在返回后者时对程序实现没有影响,但建议抽象层中的接口仍然取决于抽象层。它主要是关于针对抽象层的编程,因此返回的混凝土连接对象只需要是iConnection的子类。这也证明了面向对象的编程的多态性特征的应用。因此,该界面的设计应符合“ Liskov替代原则”。
3.1.2请求类实施
在ZNET目录中创建一个名为request.go
的新文件,该文件将主要实现Zinx的请求类。代码如下:
// zinx/znet/request.go
package znet
import "zinx/ziface"
type Request struct {
conn ziface.IConnection // The established connection with the client
data []byte // Data requested by the client
}
// GetConnection retrieves the connection information of the request
func (r *Request) GetConnection() ziface.IConnection {
return r.conn
}
// GetData retrieves the data of the request message
func (r *Request) GetData() []byte {
return r.data
}
现在创建了请求类,并在配置路由器时将在以后使用。
3.2 irouter-抽象路由器配置
在本节中,我们将在Zinx中实现一个非常基本的路由功能,旨在快速将路由引入框架。
3.2.1创建抽象的Irouter层
在ziface
目录中创建一个名为irouter.go
的文件。该文件将定义路由功能的接口。代码实现如下:
// zinx/ziface/irouter.go
package ziface
/*
Router interface.
Routers are used by framework users to configure custom business methods for a connection.
The IRequest in the router contains the connection information and the request data of that connection.
*/
type IRouter interface {
PreHandle(request IRequest) // Hook method executed before handling the conn business
Handle(request IRequest) // Method to handle the conn business
PostHandle(request IRequest) // Hook method executed after handling the conn business
}
路由器的作用是允许服务器应用程序为特定连接配置处理业务方法。在Zinx的先前版本(V0.2)中,修复了处理连接请求的方法。现在,可以自定义,并且可以覆盖三个接口:
-
Handle
:这是处理当前连接的主要业务功能。 -
PreHandle
:如果需要在主要业务功能之前进行预处理,则可以覆盖此方法。 -
PostHandle
:如果需要在主要业务功能之后进行后处理,则可以覆盖此方法。
每个方法都有一个唯一的参数,IRequest
对象,它代表来自客户端的连接和请求数据,并用作业务方法的输入数据。
3.2.2实施路由器类
在znet
目录中创建一个名为router.go
的文件。该文件包含Router
类的实现。代码如下:
// zinx/znet/router.go
package znet
import "zinx/ziface"
// When implementing a router, embed this base class and override its methods as needed
type BaseRouter struct{}
func (br *BaseRouter) PreHandle(req ziface.IRequest) {}
func (br *BaseRouter) Handle(req ziface.IRequest) {}
func (br *BaseRouter) PostHandle(req ziface.IRequest) {}
BaseRouter
类用作实现Router
的所有子类的父类。它实现了IRouter
的三个接口,但是BaseRouter
中的方法实现是空的。这是因为某些实现层路由器可能不需要PreHandle
或PostHandle
方法。通过继承BaseRouter
,无需实现这些方法以实例化路由器。
此时,Zinx的目录结构应如下:
.
├── README.md
├── ziface
│ ├── iconnection.go
│ ├── irequest.go
│ ├── irouter.go
│ └── iserver.go
└── znet
├── connection.go
├── request.go
├── router.go
├── server.go
└── server_test.go
3.3将基本路由功能集成到zinx-v0.3
现在已经定义了IRequest
和IRouter
,下一步是将它们集成到zinx框架中。
3.3.1将路由器注册功能添加到Iserver
IServer
接口需要添加一个抽象方法AddRouter()
。目的是允许Zinx框架的用户自定义用于处理业务方法的路由器。代码如下:
// zinx/ziface/iserver.go
package ziface
// Definition of the server interface
type IServer interface {
// Start the server
Start()
// Stop the server
Stop()
// Serve the business services
Serve()
// Router function: Register a router business method for the current server to handle client connections
AddRouter(router IRouter)
}
AddRouter()
方法以IRouter
为参数,这是一个抽象层,而不是具体的实现。
3.3.2将路由器成员添加到服务器类
使用抽象方法,Server
类需要实现并添加路由器成员。修改后的Server
数据结构如下:
// zinx/znet/server.go
// Implementation of the iServer interface, defining a Server service class
type Server struct {
// Server name
Name string
// IP version (tcp4 or other)
IPVersion string
// IP address that the service binds to
IP string
// Port that the service binds to
Port int
// Callback router bound by the user for the current Server, which is responsible for handling business for registered connections
Router ziface.IRouter
}
相应地,NewServer()
方法应包括Router
的默认初始化和分配:
// zinx/znet/server.go
/*
Create a server handle
*/
func NewServer(name string) ziface.IServer {
s := &Server{
Name: name,
IPVersion: "tcp4",
IP: "0.0.0.0",
Port: 7777,
Router: nil, // Default not specified
}
return s
}
Server
类需要实现AddRouter()
方法来添加路由器。在这里,它只需要在s.Router
成员中注册即可。开发人员将AddRouter()
方法用于业务功能。代码如下:
// zinx/znet/server.go
// Router function: Register a router business method for the current server to handle client connections
func (s *Server) AddRouter(router ziface.IRouter) {
s.Router = router
fmt.Println("Add Router success!")
}
3.3.3将路由器成员绑定到连接类
由于Server
已经集成了路由器功能,因此Connection
类也需要与路由器关联。修改连接实现类的数据结构,然后添加Router
成员。代码修改如下:
// zinx/znet/connection.go
type Connection struct {
// TCP socket for the current connection
Conn *net.TCPConn
// Connection ID, also known as SessionID, globally unique
ConnID uint32
// Connection close status
isClosed bool
// Router for handling the connection
Router ziface.IRouter
// Channel to notify that the connection has exited/stopped
ExitBuffChan chan bool
}
在NewConnection()
中创建新连接时,需要传递Router
参数。修改代码如下:
// zinx/znet/connection.go
// Method to create a connection
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
}
3.3.4致电注册路由器以处理连接的业务
将路由器成员集成到Connection
类中后,可以在处理数据读取的StartReader()
逻辑中调用注册的路由器。阅读数据后,以下代码添加了路由器的调用:
// zinx/znet/connection.go
func (c *Connection) StartReader() {
fmt.Println("Reader Goroutine is running")
defer fmt.Println(c.RemoteAddr().String(), " conn reader exit!")
defer c.Stop()
for {
// Read the maximum data into the buffer
buf := make([]byte, 512)
_, err := c.Conn.Read(buf)
if err != nil {
fmt.Println("recv buf err ", err)
c.ExitBuffChan <- true
continue
}
// Get the Request data of the current client request
req := Request{
conn: c,
data: buf,
}
// Find the corresponding Handle registered with the bound Conn from the Routers
go func(request ziface.IRequest) {
// Execute the registered router methods
c.Router.PreHandle(request)
c.Router.Handle(request)
c.Router.PostHandle(request)
}(&req)
}
}
在连接的StartReader()
方法中读取客户端数据后,将数据和连接封装在Request
中作为路由器的输入数据:
// Get the Request data of the current client request
req := Request{
conn: c,
data: buf,
}
然后,开始在Zinx框架中调用注册的路由器业务逻辑:
// Find the corresponding Handle registered with the bound Conn from the Routers
go func(request ziface.IRequest) {
// Execute the registered router methods
c.Router.PreHandle(request)
c.Router.Handle(request)
c.Router.PostHandle(request)
}(&req)
如果路由器已覆盖PreHandle()
,则将被称为;否则,将调用空的BaseRouter()
的空方法。相同的逻辑适用于Handle()
和PostHandle()
。
3.4将路由器参数传递到服务器中的连接
服务器成功建立新连接后,需要创建一个新连接,并且应将当前路由器参数传递给连接。主要修改是在NewConnection()
方法中。相关的关键代码更改如下:
// zinx/znet/server.go
package znet
import (
"fmt"
"net"
"time"
"zinx/ziface"
)
// Start the network service
func (s *Server) Start() {
// ... (partial code omitted)
// Start a goroutine to handle server listener business
go func() {
// 1. Get a TCP Addr
// ... (partial code omitted)
// 2. Listen to the server address
// ... (partial code omitted)
// 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: Set the server's maximum connection control in Server.Start()
// If the maximum connection is exceeded, close this new connection
// 3.3 Handle the business method of the new connection request
// The handler and conn should be bound at this point
dealConn := NewConnection(conn, cid, s.Router)
cid++
// 3.4 Start handling the business of the current connection
go dealConn.Start()
}
}()
}
3.5用zinx-v0.3完成应用程序
现在,使用Zinx,可以配置一个简单的路由功能。
3.5.1测试用Zinx构建的服务器应用程序
现在让我们继续开发服务器。开发人员需要定义自定义路由器类,并实现PreHandle(),handle()和postthandle()方法,以处理ZINX中数据请求的特定业务逻辑。代码如下:
// Server.go
package main
import (
"fmt"
"zinx/ziface"
"zinx/znet"
)
// Ping test custom router
type PingRouter struct {
znet.BaseRouter // Must embed BaseRouter first
}
// 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("callback 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("callback 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("callback ping ping ping error")
}
}
func main() {
// Create a server instance
s := znet.NewServer("[zinx V0.3]")
s.AddRouter(&PingRouter{})
// Start the server
s.Serve()
}
以上代码定义了类似于ping操作的自定义路由器。当客户端发送数据时,服务器的业务逻辑是将“ ping ... ping ... ping”返回给客户端。为了测试它,当前路由器还实现了PreHandle()和postthle()方法。实际上,Zinx将使用模板方法设计模式来调用prehandle(),handle()和portthandle()方法。
3.5.2启动服务器和客户端
1个启动服务器
执行以下命令使用注册路由器启动服务器程序:
go run Server.go
2启动客户端
客户端中的客户端代码与上一个版本保持不变。执行以下命令运行程序:
go run Client.go
3服务器输出
$ go run Server.go
Add Router succ!
[START] Server listener at IP: 0.0.0.0, Port 7777, is starting
start Zinx server [zinx V0.3] succ, now listening...
Reader Goroutine is running
Call Router PreHandle
Call PingRouter Handle
Call Router PostHandle
Call Router PreHandle
Call PingRouter Handle
Call Router PostHandle
Call Router PreHandle
Call PingRouter Handle
Call Router PostHandle
Call Router PreHandle
Call PingRouter Handle
Call Router PostHandle
Call Router PreHandle
Call PingRouter Handle
Call Router PostHandle
...
4个客户端输出
$ go run Client.go
Client Test ... start
Server callback: before ping ...., cnt = 17
Server callback: ping...ping...ping
After ping ....., cnt = 36
Server callback: before ping ....
ping...ping...ping
After ping ....., cnt = 53
Server callback: before ping ....
ping...ping...ping
After ping ....., cnt = 53
Server callback: before ping ....
ping...ping...ping
After ping ....., cnt = 53
...
3.6结论
现在,Zinx Framework具有路由功能,尽管目前只能配置一个路由器。但是,Zinx现在允许开发人员自由配置业务逻辑。在接下来的步骤中,Zinx将添加配置多个路由器的功能。
<1.Building Basic Services with Zinx Framework>
<2. Zinx-V0.2 Simple Connection Encapsulation and Binding with Business>
<3.Design and Implementation of the Zinx Framework's Routing Module>
<4.Zinx Global Configuration>
未完待续...
作者:
不和谐:https://discord.gg/xQ8Xxfyfcz
zinx:https://github.com/aceld/zinx
github:https://github.com/aceld
Aceld的家:https://yuque.com/aceld