Zinx源代碼
github
https://github.com/aceld/zinx
gitee碼云
https://gitee.com/Aceld/zinx
在線開發(fā)教程
【B站】
zinx視頻教程-Golang輕量級TCP服務(wù)器框架-適合自學(xué)者
【YouTube】
zinx開發(fā)YouTube中國版
微信端文檔

【Zinx教程目錄】
完整教程電子版(在線高清)-下載
Zinx框架視頻教程(框架篇)(完整版下載)鏈接在下面正文
Zinx框架視頻教程(應(yīng)用篇)(完整版下載)鏈接在下面正文
Zinx開發(fā)API文檔
Zinx第一章-引言
Zinx第二章-初識Zinx框架
Zinx第三章-基礎(chǔ)路由模塊
Zinx第四章-全局配置
Zinx第五章-消息封裝
Zinx第六章-多路由模式
Zinx第七章-讀寫分離模型
Zinx第八章-消息隊列及多任務(wù)
Zinx第九章-鏈接管理
Zinx第十章-連接屬性設(shè)置
【Zinx應(yīng)用案例-MMO多人在線游戲】
(1)案例介紹
(2)AOI興趣點算法
(3)數(shù)據(jù)傳輸協(xié)議protocol buffer
(4)Proto3協(xié)議定義
(5)構(gòu)建項目及用戶上線
(6)世界聊天
(7)上線位置信息同步
(8)移動位置與AOI廣播
(9)玩家下線
(10)模擬客戶端AI模塊
三、Zinx框架基礎(chǔ)路由模塊
? 現(xiàn)在我們就給用戶提供一個自定義的conn處理業(yè)務(wù)的接口吧,很顯然,我們不能把業(yè)務(wù)處理業(yè)務(wù)的方法綁死在type HandFunc func(*net.TCPConn, []byte, int) error這種格式中,我們需要定一些interface{}來讓用戶填寫任意格式的連接處理業(yè)務(wù)方法。
? 那么,很顯然func是滿足不了我們需求的,我們需要再做幾個抽象的接口類。
?3.1 IRequest 消息請求抽象類
? 我們現(xiàn)在需要把客戶端請求的連接信息 和 請求的數(shù)據(jù),放在一個叫Request的請求類里,這樣的好處是我們可以從Request里得到全部客戶端的請求信息,也為我們之后拓展框架有一定的作用,一旦客戶端有額外的含義的數(shù)據(jù)信息,都可以放在這個Request里。可以理解為每次客戶端的全部請求數(shù)據(jù),Zinx都會把它們一起放到一個Request結(jié)構(gòu)體里。
A) 創(chuàng)建抽象IRequest層
? 在ziface下創(chuàng)建新文件irequest.go。
zinx/ziface/irequest.go
package ziface
/*
IRequest 接口:
實際上是把客戶端請求的鏈接信息 和 請求的數(shù)據(jù) 包裝到了 Request里
*/
type IRequest interface{
GetConnection() IConnection //獲取請求連接信息
GetData() []byte //獲取請求消息的數(shù)據(jù)
}
不難看出,當(dāng)前的抽象層只提供了兩個Getter方法,所以有個成員應(yīng)該是必須的,一個是客戶端連接,一個是客戶端傳遞進來的數(shù)據(jù),當(dāng)然隨著Zinx框架的功能豐富,這里面還應(yīng)該繼續(xù)添加新的成員。
B) 實現(xiàn)Request類
? 在znet下創(chuàng)建IRequest抽象接口的一個實例類文件request.go
zinx/znet/request.go
package znet
import "zinx/ziface"
type Request struct {
conn ziface.IConnection //已經(jīng)和客戶端建立好的 鏈接
data []byte //客戶端請求的數(shù)據(jù)
}
//獲取請求連接信息
func(r *Request) GetConnection() ziface.IConnection {
return r.conn
}
//獲取請求消息的數(shù)據(jù)
func(r *Request) GetData() []byte {
return r.data
}
? 好了現(xiàn)在我們Request類創(chuàng)建好了,稍后我們會用到它。
3.2 IRouter 路由配置抽象類
? 現(xiàn)在我們來給Zinx實現(xiàn)一個非常簡單基礎(chǔ)的路由功能,目的當(dāng)然就是為了快速的讓Zinx步入到路由的階段。后續(xù)我們會不斷的完善路由功能。
A) 創(chuàng)建抽象的IRouter層
? 在ziface下創(chuàng)建irouter.go文件
zinx/ziface/irouter.go
package ziface
/*
路由接口, 這里面路由是 使用框架者給該鏈接自定的 處理業(yè)務(wù)方法
路由里的IRequest 則包含用該鏈接的鏈接信息和該鏈接的請求數(shù)據(jù)信息
*/
type IRouter interface{
PreHandle(request IRequest) //在處理conn業(yè)務(wù)之前的鉤子方法
Handle(request IRequest) //處理conn業(yè)務(wù)的方法
PostHandle(request IRequest) //處理conn業(yè)務(wù)之后的鉤子方法
}
? 我們知道router實際上的作用就是,服務(wù)端應(yīng)用可以給Zinx框架配置當(dāng)前鏈接的處理業(yè)務(wù)方法,之前的Zinx-V0.2我們的Zinx框架處理鏈接請求的方法是固定的,現(xiàn)在是可以自定義,并且有3種接口可以重寫。
Handle:是處理當(dāng)前鏈接的主業(yè)務(wù)函數(shù)
PreHandle:如果需要在主業(yè)務(wù)函數(shù)之前有前置業(yè)務(wù),可以重寫這個方法
PostHandle:如果需要在主業(yè)務(wù)函數(shù)之后又后置業(yè)務(wù),可以重寫這個方法
? 當(dāng)然每個方法都有一個唯一的形參IRequest對象,也就是客戶端請求過來的連接和請求數(shù)據(jù),作為我們業(yè)務(wù)方法的輸入數(shù)據(jù)。
B) 實現(xiàn)Router類
? 在znet下創(chuàng)建router.go文件
package znet
import "zinx/ziface"
//實現(xiàn)router時,先嵌入這個基類,然后根據(jù)需要對這個基類的方法進行重寫
type BaseRouter struct {}
//這里之所以BaseRouter的方法都為空,
// 是因為有的Router不希望有PreHandle或PostHandle
// 所以Router全部繼承BaseRouter的好處是,不需要實現(xiàn)PreHandle和PostHandle也可以實例化
func (br *BaseRouter)PreHandle(req ziface.IRequest){}
func (br *BaseRouter)Handle(req ziface.IRequest){}
func (br *BaseRouter)PostHandle(req ziface.IRequest){}
我們當(dāng)前的Zinx目錄結(jié)構(gòu)應(yīng)該如下:
.
├── 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處理業(yè)務(wù)方法。
zinx/ziface/irouter.go
package ziface
//定義服務(wù)器接口
type IServer interface{
//啟動服務(wù)器方法
Start()
//停止服務(wù)器方法
Stop()
//開啟業(yè)務(wù)服務(wù)方法
Serve()
//路由功能:給當(dāng)前服務(wù)注冊一個路由業(yè)務(wù)方法,供客戶端鏈接處理使用
AddRouter(router IRouter)
}
B) Server類增添Router成員
? 有了抽象的方法,自然Server就要實現(xiàn),并且還要添加一個Router成員.
zinx/znet/server.go
//iServer 接口實現(xiàn),定義一個Server服務(wù)類
type Server struct {
//服務(wù)器的名稱
Name string
//tcp4 or other
IPVersion string
//服務(wù)綁定的IP地址
IP string
//服務(wù)綁定的端口
Port int
//當(dāng)前Server由用戶綁定的回調(diào)router,也就是Server注冊的鏈接對應(yīng)的處理業(yè)務(wù)
Router ziface.IRouter
}
? 然后NewServer()方法, 初始化Server對象的方法也要加一個初始化成員
/*
創(chuàng)建一個服務(wù)器句柄
*/
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 {
//當(dāng)前連接的socket TCP套接字
Conn *net.TCPConn
//當(dāng)前連接的ID 也可以稱作為SessionID,ID全局唯一
ConnID uint32
//當(dāng)前連接的關(guān)閉狀態(tài)
isClosed bool
//該連接的處理方法router
Router ziface.IRouter
//告知該鏈接已經(jīng)退出/停止的channel
ExitBuffChan chan bool
}
D) 在Connection調(diào)用注冊的Router處理業(yè)務(wù)
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 {
//讀取我們最大的數(shù)據(jù)到buf中
buf := make([]byte, 512)
_, err := c.Conn.Read(buf)
if err != nil {
fmt.Println("recv buf err ", err)
c.ExitBuffChan <- true
continue
}
//得到當(dāng)前客戶端請求的Request數(shù)據(jù)
req := Request{
conn:c,
data:buf,
}
//從路由Routers 中找到注冊綁定Conn的對應(yīng)Handle
go func (request ziface.IRequest) {
//執(zhí)行注冊的路由方法
c.Router.PreHandle(request)
c.Router.Handle(request)
c.Router.PostHandle(request)
}(&req)
}
}
? 這里我們在conn讀取完客戶端數(shù)據(jù)之后,將數(shù)據(jù)和conn封裝到一個Request中,作為Router的輸入數(shù)據(jù)。
然后我們開啟一個goroutine去調(diào)用給Zinx框架注冊好的路由業(yè)務(wù)。
3.4 Zinx-V0.3代碼實現(xiàn)
zinx/znet/server.go
package znet
import (
"fmt"
"net"
"time"
"zinx/ziface"
)
//iServer 接口實現(xiàn),定義一個Server服務(wù)類
type Server struct {
//服務(wù)器的名稱
Name string
//tcp4 or other
IPVersion string
//服務(wù)綁定的IP地址
IP string
//服務(wù)綁定的端口
Port int
//當(dāng)前Server由用戶綁定的回調(diào)router,也就是Server注冊的鏈接對應(yīng)的處理業(yè)務(wù)
Router ziface.IRouter
}
/*
創(chuàng)建一個服務(wù)器句柄
*/
func NewServer (name string) ziface.IServer {
s:= &Server {
Name :name,
IPVersion:"tcp4",
IP:"0.0.0.0",
Port:7777,
Router: nil,
}
return s
}
//============== 實現(xiàn) ziface.IServer 里的全部接口方法 ========
//開啟網(wǎng)絡(luò)服務(wù)
func (s *Server) Start() {
fmt.Printf("[START] Server listenner at IP: %s, Port %d, is starting\n", s.IP, s.Port)
//開啟一個go去做服務(wù)端Linster業(yè)務(wù)
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 監(jiān)聽服務(wù)器地址
listenner, err:= net.ListenTCP(s.IPVersion, addr)
if err != nil {
fmt.Println("listen", s.IPVersion, "err", err)
return
}
//已經(jīng)監(jiān)聽成功
fmt.Println("start Zinx server ", s.Name, " succ, now listenning...")
//TODO server.go 應(yīng)該有一個自動生成ID的方法
var cid uint32
cid = 0
//3 啟動server網(wǎng)絡(luò)連接業(yè)務(wù)
for {
//3.1 阻塞等待客戶端建立連接請求
conn, err := listenner.AcceptTCP()
if err != nil {
fmt.Println("Accept err ", err)
continue
}
//3.2 TODO Server.Start() 設(shè)置服務(wù)器最大連接控制,如果超過最大連接,那么則關(guān)閉此新的連接
//3.3 處理該新連接請求的 業(yè)務(wù) 方法, 此時應(yīng)該有 handler 和 conn是綁定的
dealConn := NewConntion(conn, cid, s.Router)
cid ++
//3.4 啟動當(dāng)前鏈接的處理業(yè)務(wù)
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() 是否在啟動服務(wù)的時候 還要處理其他的事情呢 可以在這里添加
//阻塞,否則主Go退出, listenner的go將會退出
for {
time.Sleep(10*time.Second)
}
}
//路由功能:給當(dāng)前服務(wù)注冊一個路由業(yè)務(wù)方法,供客戶端鏈接處理使用
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 {
//當(dāng)前連接的socket TCP套接字
Conn *net.TCPConn
//當(dāng)前連接的ID 也可以稱作為SessionID,ID全局唯一
ConnID uint32
//當(dāng)前連接的關(guān)閉狀態(tài)
isClosed bool
//該連接的處理方法router
Router ziface.IRouter
//告知該鏈接已經(jīng)退出/停止的channel
ExitBuffChan chan bool
}
//創(chuàng)建連接的方法
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 {
//讀取我們最大的數(shù)據(jù)到buf中
buf := make([]byte, 512)
_, err := c.Conn.Read(buf)
if err != nil {
fmt.Println("recv buf err ", err)
c.ExitBuffChan <- true
continue
}
//得到當(dāng)前客戶端請求的Request數(shù)據(jù)
req := Request{
conn:c,
data:buf,
}
//從路由Routers 中找到注冊綁定Conn的對應(yīng)Handle
go func (request ziface.IRequest) {
//執(zhí)行注冊的路由方法
c.Router.PreHandle(request)
c.Router.Handle(request)
c.Router.PostHandle(request)
}(&req)
}
}
//啟動連接,讓當(dāng)前連接開始工作
func (c *Connection) Start() {
//開啟處理該鏈接讀取到客戶端數(shù)據(jù)之后的請求業(yè)務(wù)
go c.StartReader()
for {
select {
case <- c.ExitBuffChan:
//得到退出消息,不再阻塞
return
}
}
}
//停止連接,結(jié)束當(dāng)前連接狀態(tài)M
func (c *Connection) Stop() {
//1. 如果當(dāng)前鏈接已經(jīng)關(guān)閉
if c.isClosed == true {
return
}
c.isClosed = true
//TODO Connection Stop() 如果用戶注冊了該鏈接的關(guān)閉回調(diào)業(yè)務(wù),那么在此刻應(yīng)該顯示調(diào)用
// 關(guān)閉socket鏈接
c.Conn.Close()
//通知從緩沖隊列讀數(shù)據(jù)的業(yè)務(wù),該鏈接已經(jīng)關(guān)閉
c.ExitBuffChan <- true
//關(guān)閉該鏈接全部管道
close(c.ExitBuffChan)
}
//從當(dāng)前連接獲取原始的socket TCPConn
func (c *Connection) GetTCPConnection() *net.TCPConn {
return c.Conn
}
//獲取當(dāng)前連接ID
func (c *Connection) GetConnID() uint32{
return c.ConnID
}
//獲取遠(yuǎn)程客戶端地址信息
func (c *Connection) RemoteAddr() net.Addr {
return c.Conn.RemoteAddr()
}
3.5 使用Zinx-V0.3完成應(yīng)用程序
? 接下來我們在基于Zinx寫服務(wù)器,就可以配置一個簡單的路由功能了。
A) 測試基于Zinx完成的服務(wù)端應(yīng)用
Server.go
package main
import (
"fmt"
"zinx/ziface"
"zinx/znet"
)
//ping test 自定義路由
type PingRouter struct {
znet.BaseRouter //一定要先基礎(chǔ)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(){
//創(chuàng)建一個server句柄
s := znet.NewServer("[zinx V0.3]")
s.AddRouter(&PingRouter{})
//2 開啟服務(wù)
s.Serve()
}
我們這里自定義了一個類似Ping操作的路由,就是當(dāng)客戶端發(fā)送數(shù)據(jù),我們的處理業(yè)務(wù)就是返回給客戶端"ping...ping..ping..", 為了測試,當(dāng)前路由也同時實現(xiàn)了PreHandle和PostHandle兩個方法。實際上Zinx會利用模板的設(shè)計模式,依次在框架中調(diào)用PreHandle、Handle、PostHandle三個方法。
B) 啟動Server.go
go run Server.go
C) 客戶端應(yīng)用測試程序
和之前的Client.go一樣 沒有改變
package main
import (
"fmt"
"net"
"time"
)
/*
模擬客戶端
*/
func main() {
fmt.Println("Client Test ... start")
//3秒之后發(fā)起測試請求,給服務(wù)端開啟服務(wù)的機會
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
運行結(jié)果如下:
服務(wù)端:
$ 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
...
現(xiàn)在Zinx框架已經(jīng)有路由功能了,雖然說目前只能配置一個,不過不要著急,很快我們會增加配置多路由的能力。
關(guān)于作者:
作者:Aceld(劉丹冰)
簡書號:IT無崖子
mail: danbing.at@gmail.com
github: https://github.com/aceld
原創(chuàng)書籍gitbook: http://legacy.gitbook.com/@aceld
原創(chuàng)聲明:未經(jīng)作者允許請勿轉(zhuǎn)載,或者轉(zhuǎn)載請注明出處!