【Zinx第三章-基礎路由模組】Golang輕量級併發伺服器框架
【Zinx 教程目錄】 Zinx 原始碼
Zinx 框架視訊教程 (框架篇)(完整版下載) 連結在下面正文
Zinx 框架視訊教程 (應用篇)(完整版下載) 連結在下面正文
Zinx 第十章-連線屬性設定
【Zinx 應用案例-MMO 多人線上遊戲】 (1) 案例介紹
(9) 玩家下線
三、Zinx 框架基礎路由模組
現在我們就給使用者提供一個自定義的 conn 處理業務的介面吧,很顯然,我們不能把業務處理業務的方法綁死在type HandFunc func(*net.TCPConn, []byte, int) error
這種格式中,我們需要定一些interface{}
來讓使用者填寫任意格式的連線處理業務方法。
那麼,很顯然 func 是滿足不了我們需求的,我們需要再做幾個抽象的介面類。
3.1 IRequest 訊息請求抽象類
我們現在需要把客戶端請求的連線資訊 和 請求的資料,放在一個叫 Request 的請求類裡,這樣的好處是我們可以從 Request 裡得到全部客戶端的請求資訊,也為我們之後擴充框架有一定的作用,一旦客戶端有額外的含義的資料資訊,都可以放在這個 Request 裡。可以理解為每次客戶端的全部請求資料,Zinx 都會把它們一起放到一個 Request 結構體裡。
A) 建立抽象 IRequest 層
在ziface
下建立新檔案irequest.go
。
zinx/ziface/irequest.go
package ziface
/*
IRequest 介面:
實際上是把客戶端請求的連結資訊 和 請求的資料 包裝到了 Request裡
*/
type IRequest interface{
GetConnection() IConnection //獲取請求連線資訊
GetData() []byte //獲取請求訊息的資料
}
不難看出,當前的抽象層只提供了兩個 Getter 方法,所以有個成員應該是必須的,一個是客戶端連線,一個是客戶端傳遞進來的資料,當然隨著 Zinx 框架的功能豐富,這裡面還應該繼續新增新的成員。
B) 實現 Request 類
在 znet 下建立 IRequest 抽象介面的一個例項類檔案request.go
zinx/znet/request.go
package znet
import "zinx/ziface"
type Request struct {
conn ziface.IConnection //已經和客戶端建立好的 連結
data []byte //客戶端請求的資料
}
//獲取請求連線資訊
func(r *Request) GetConnection() ziface.IConnection {
return r.conn
}
//獲取請求訊息的資料
func(r *Request) GetData() []byte {
return r.data
}
好了現在我們 Request 類建立好了,稍後我們會用到它。
3.2 IRouter 路由配置抽象類
現在我們來給 Zinx 實現一個非常簡單基礎的路由功能,目的當然就是為了快速的讓 Zinx 步入到路由的階段。後續我們會不斷的完善路由功能。
A) 建立抽象的 IRouter 層
在ziface
下建立irouter.go
檔案
zinx/ziface/irouter.go
package ziface
/*
路由介面, 這裡面路由是 使用框架者給該連結自定的 處理業務方法
路由裡的IRequest 則包含用該連結的連結資訊和該連結的請求資料資訊
*/
type IRouter interface{
PreHandle(request IRequest) //在處理conn業務之前的鉤子方法
Handle(request IRequest) //處理conn業務的方法
PostHandle(request IRequest) //處理conn業務之後的鉤子方法
}
我們知道 router 實際上的作用就是,服務端應用可以給 Zinx 框架配置當前連結的處理業務方法,之前的 Zinx-V0.2 我們的 Zinx 框架處理連結請求的方法是固定的,現在是可以自定義,並且有 3 種介面可以重寫。
Handle
:是處理當前連結的主業務函式
PreHandle
:如果需要在主業務函式之前有前置業務,可以重寫這個方法
PostHandle
:如果需要在主業務函式之後又後置業務,可以重寫這個方法
當然每個方法都有一個唯一的形參IRequest
物件,也就是客戶端請求過來的連線和請求資料,作為我們業務方法的輸入資料。
B) 實現 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 目錄結構應該如下:
.
├── README.md
├── ziface
│ ├── iconnnection.go
│ ├── irequest.go
│ ├── irouter.go
│ └── iserver.go
└── znet
├── connection.go
├── request.go
├── router.go
├── server.go
└── server_test.go
3.3 Zinx-V0.3-整合簡單路由功能
A) IServer 增添路由新增功能
我們需要給 IServer 類,增加一個抽象方法AddRouter
,目的也是讓 Zinx 框架使用者,可以自定一個 Router 處理業務方法。
zinx/ziface/irouter.go
package ziface
//定義伺服器介面
type IServer interface{
//啟動伺服器方法
Start()
//停止伺服器方法
Stop()
//開啟業務服務方法
Serve()
//路由功能:給當前服務註冊一個路由業務方法,供客戶端連結處理使用
AddRouter(router IRouter)
}
B) Server 類增添 Router 成員
有了抽象的方法,自然 Server 就要實現,並且還要新增一個 Router 成員.
zinx/znet/server.go
//iServer 介面實現,定義一個Server服務類
type Server struct {
//伺服器的名稱
Name string
//tcp4 or other
IPVersion string
//服務繫結的IP地址
IP string
//服務繫結的埠
Port int
//當前Server由使用者繫結的回撥router,也就是Server註冊的連結對應的處理業務
Router ziface.IRouter
}
然後NewServer()
方法, 初始化 Server 物件的方法也要加一個初始化成員
/*
建立一個伺服器控制程式碼
*/
func NewServer (name string) ziface.IServer {
s:= &Server {
Name :name,
IPVersion:"tcp4",
IP:"0.0.0.0",
Port:7777,
Router: nil,
}
return s
}
C) Connection 類繫結一個 Router 成員
zinx/znet/connection.go
type Connection struct {
//當前連線的socket TCP套接字
Conn *net.TCPConn
//當前連線的ID 也可以稱作為SessionID,ID全域性唯一
ConnID uint32
//當前連線的關閉狀態
isClosed bool
//該連線的處理方法router
Router ziface.IRouter
//告知該連結已經退出/停止的channel
ExitBuffChan chan bool
}
D) 在 Connection 呼叫註冊的 Router 處理業務
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 {
//讀取我們最大的資料到buf中
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)
}
}
這裡我們在 conn 讀取完客戶端資料之後,將資料和 conn 封裝到一個 Request 中,作為 Router 的輸入資料。
然後我們開啟一個 goroutine 去呼叫給 Zinx 框架註冊好的路由業務。
3.4 Zinx-V0.3 程式碼實現
zinx/znet/server.go
package znet
import (
"fmt"
"net"
"time"
"zinx/ziface"
)
//iServer 介面實現,定義一個Server服務類
type Server struct {
//伺服器的名稱
Name string
//tcp4 or other
IPVersion string
//服務繫結的IP地址
IP string
//服務繫結的埠
Port int
//當前Server由使用者繫結的回撥router,也就是Server註冊的連結對應的處理業務
Router ziface.IRouter
}
/*
建立一個伺服器控制程式碼
*/
func NewServer (name string) ziface.IServer {
s:= &Server {
Name :name,
IPVersion:"tcp4",
IP:"0.0.0.0",
Port:7777,
Router: nil,
}
return s
}
//============== 實現 ziface.IServer 裡的全部介面方法 ========
//開啟網路服務
func (s *Server) Start() {
fmt.Printf("[START] Server listenner at IP: %s, Port %d, is starting\n", s.IP, s.Port)
//開啟一個go去做服務端Linster業務
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 監聽伺服器地址
listenner, 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的方法
var cid uint32
cid = 0
//3 啟動server網路連線業務
for {
//3.1 阻塞等待客戶端建立連線請求
conn, err := listenner.AcceptTCP()
if err != nil {
fmt.Println("Accept err ", err)
continue
}
//3.2 TODO Server.Start() 設定伺服器最大連線控制,如果超過最大連線,那麼則關閉此新的連線
//3.3 處理該新連線請求的 業務 方法, 此時應該有 handler 和 conn是繫結的
dealConn := NewConntion(conn, cid, s.Router)
cid ++
//3.4 啟動當前連結的處理業務
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() 是否在啟動服務的時候 還要處理其他的事情呢 可以在這裡新增
//阻塞,否則主Go退出, listenner的go將會退出
for {
time.Sleep(10*time.Second)
}
}
//路由功能:給當前服務註冊一個路由業務方法,供客戶端連結處理使用
func (s *Server)AddRouter(router ziface.IRouter) {
s.Router = router
fmt.Println("Add Router succ! " )
}
zinx/znet/conneciont.go
package znet
import (
"fmt"
"net"
"zinx/ziface"
)
type Connection struct {
//當前連線的socket TCP套接字
Conn *net.TCPConn
//當前連線的ID 也可以稱作為SessionID,ID全域性唯一
ConnID uint32
//當前連線的關閉狀態
isClosed bool
//該連線的處理方法router
Router ziface.IRouter
//告知該連結已經退出/停止的channel
ExitBuffChan chan bool
}
//建立連線的方法
func NewConntion(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
}
func (c *Connection) StartReader() {
fmt.Println("Reader Goroutine is running")
defer fmt.Println(c.RemoteAddr().String(), " conn reader exit!")
defer c.Stop()
for {
//讀取我們最大的資料到buf中
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)
}
}
//啟動連線,讓當前連線開始工作
func (c *Connection) Start() {
//開啟處理該連結讀取到客戶端資料之後的請求業務
go c.StartReader()
for {
select {
case <- c.ExitBuffChan:
//得到退出訊息,不再阻塞
return
}
}
}
//停止連線,結束當前連線狀態M
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)
}
//從當前連線獲取原始的socket TCPConn
func (c *Connection) GetTCPConnection() *net.TCPConn {
return c.Conn
}
//獲取當前連線ID
func (c *Connection) GetConnID() uint32{
return c.ConnID
}
//獲取遠端客戶端地址資訊
func (c *Connection) RemoteAddr() net.Addr {
return c.Conn.RemoteAddr()
}
3.5 使用 Zinx-V0.3 完成應用程式
接下來我們在基於 Zinx 寫伺服器,就可以配置一個簡單的路由功能了。
A) 測試基於 Zinx 完成的服務端應用
Server.go
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")
}
}
func main(){
//建立一個server控制程式碼
s := znet.NewServer("[zinx V0.3]")
s.AddRouter(&PingRouter{})
//2 開啟服務
s.Serve()
}
我們這裡自定義了一個類似 Ping 操作的路由,就是當客戶端傳送資料,我們的處理業務就是返回給客戶端"ping...ping..ping..", 為了測試,當前路由也同時實現了 PreHandle 和 PostHandle 兩個方法。實際上 Zinx 會利用模板的設計模式,依次在框架中呼叫PreHandle
、Handle
、PostHandle
三個方法。
B) 啟動 Server.go
go run Server.go
C) 客戶端應用測試程式
和之前的 Client.go 一樣 沒有改變
package main
import (
"fmt"
"net"
"time"
)
/*
模擬客戶端
*/
func main() {
fmt.Println("Client Test ... start")
//3秒之後發起測試請求,給服務端開啟服務的機會
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)
}
}
D) 啟動 Client.go
go run Client.go
執行結果如下:
服務端:
$ go run Server.go
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 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
...
客戶端:
$ go run Client.go
Client Test ... start
server call back : before ping ....
, cnt = 17
server call back : ping...ping...ping
After ping .....
, cnt = 36
server call back : before ping ....
ping...ping...ping
After ping .....
, cnt = 53
server call back : before ping ....
ping...ping...ping
After ping .....
, cnt = 53
server call back : before ping ....
ping...ping...ping
After ping .....
, cnt = 53
...
現在 Zinx 框架已經有路由功能了,雖然說目前只能配置一個,不過不要著急,很快我們會增加配置多路由的能力。
### 關於作者:
作者:Aceld(劉丹冰)
簡書號:IT無崖子
mail: danbing.at@gmail.com github: https://github.com/aceld 原創書籍 gitbook: http://legacy.gitbook.com/@aceld
>原創宣告:未經作者允許請勿轉載,或者轉載請註明出處!
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- 【Zinx第一章-引言】Golang輕量級併發伺服器框架Golang伺服器框架
- 【Zinx第四章-全域性配置】Golang輕量級併發伺服器框架Golang伺服器框架
- Flutter路由輕量級框架FRouterFlutter路由框架
- Fastflow——基於golang的輕量級工作流框架ASTGolang框架
- Solon & Solon Cloud 1.5.62 釋出,輕量級 Java 基礎開發框架CloudJava框架
- 輕量級 Java 基礎開發框架,Solon & Solon Cloud 1.5.48 釋出Java框架Cloud
- 輕量級 Java 基礎開發框架,Solon & Solon Cloud 1.5.40 釋出Java框架Cloud
- 輕量級模組化開發框架 Hasor 核心模組 v0.0.2 釋出框架
- [譯文]greenlet:輕量級併發程式
- Golang併發程式設計基礎Golang程式設計
- Go Web輕量級框架Gin學習系列:路由分組GoWeb框架路由
- Golang 基礎之併發知識 (三)Golang
- 輕量級IOC框架:Ninject框架
- Nancy .Net 輕量級mvc框架使用(5)Routing路由方式整理NaNMVC框架路由
- Golang web filter 輕量級實現GolangWebFilter
- 微服務架構基礎之輕量級部署微服務架構
- 輕量級orm框架——gzero指南ORM框架
- 輕量級Web框架Flask(二)Web框架Flask
- 超輕量級PHP框架BroPHPPHP框架
- 一個輕量級WebFramework開發框架介紹WebFramework框架
- 搭建基於springboot輕量級讀寫分離開發框架Spring Boot框架
- ColyseusJS 輕量級多人遊戲伺服器開發框架 - 中文手冊(上)JS遊戲伺服器框架
- ColyseusJS 輕量級多人遊戲伺服器開發框架 - 中文手冊(中)JS遊戲伺服器框架
- ColyseusJS 輕量級多人遊戲伺服器開發框架 - 中文手冊(下)JS遊戲伺服器框架
- PhalApi(π框架) - PHP輕量級開源介面框架API框架PHP
- core_framework —— 基於libev的輕量級lua網路開發框架Framework框架
- bbossgroupsRPC基於aop的輕量級rpc框架RPC框架
- Solon 1.6.25 釋出,輕量級應用開發框架框架
- Lite Actor:方舟Actor併發模型的輕量級優化模型優化
- looter——超輕量級爬蟲框架爬蟲框架
- Fd.Service 輕量級WebApi框架WebAPI框架
- 如何編寫輕量級 CSS 框架CSS框架
- 基於協程的高效能高併發伺服器框架—協程模組伺服器框架
- Uvicorn 初體驗-一個基於 asyncio 開發的一個輕量級高效的 Web 伺服器框架Web伺服器框架
- Solon 1.6.18 釋出,輕量級應用開發框架框架
- 輕量級 Web 框架 Gin 結構分析Web框架
- 輕量級DI框架Guice使用詳解框架GUI
- 輕量級Android快取框架ASimpleCacheAndroid快取框架