【Zinx教程目录】
Zinx源代码
https://github.com/aceld/zinx (请拷贝网址,在浏览器打开[简书不让自动跳转])
完整教程电子版(在线高清)-下载
Zinx框架视频教程(框架篇)(完整版下载)连接在下面正文
Zinx框架视频教程(应用篇)(完整版下载)连接在下面正文
Zinx开发API文档
Zinx第一章-引言
Zinx第二章-初识Zinx框架
Zinx第三章-基础路由模块
Zinx第四章-全局配置
Zinx第五章-消息封装
Zinx第六章-多路由模式
Zinx第七章-读写分离模型
Zinx第八章-消息队列及多任务
Zinx第九章-连接管理
Zinx第十章-链接属性设置git
【Zinx应用案例-MMO多人在线游戏】
(1)案例介绍
(2)AOI兴趣点算法
(3)数据传输协议protocol buffer
(4)Proto3协议定义
(5)构建项目及用户上线
(6)世界聊天
(7)上线位置信息同步
(8)移动位置与AOI广播
(9)玩家下线
(10)模拟客户端AI模块github
这里先看一下Zinx最简单的Server雏形。算法
为了更好的看到Zinx框架,首先Zinx构建Zinx的最基本的两个模块ziface
和znet
。api
ziface
主要是存放一些Zinx框架的所有模块的抽象层接口类,Zinx框架的最基本的是服务类接口iserver
,定义在ziface模块中。浏览器
znet
模块是zinx框架中网络相关功能的实现,全部网络相关模块都会定义在znet
模块中。bash
在$GOPATH/src下建立zinx
文件夹服务器
在zinx/下 建立ziface、znet文件夹, 使当前的文件路径以下:网络
└── zinx ├── ziface │ └── └── znet ├──
zinx/ziface/iserver.go架构
package ziface //定义服务器接口 type IServer interface{ //启动服务器方法 Start() //中止服务器方法 Stop() //开启业务服务方法 Serve() }
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 } //============== 实现 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...") //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 TODO Server.Start() 处理该新链接请求的 业务 方法, 此时应该有 handler 和 conn是绑定的 //咱们这里暂时作一个最大512字节的回显服务 go func () { //不断的循环从客户端获取数据 for { buf := make([]byte, 512) cnt, err := conn.Read(buf) if err != nil { fmt.Println("recv buf err ", err) continue } //回显 if _, err := conn.Write(buf[:cnt]); err !=nil { fmt.Println("write back buf err ", err) continue } } }() } }() } 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 NewServer (name string) ziface.IServer { s:= &Server { Name :name, IPVersion:"tcp4", IP:"0.0.0.0", Port:7777, } return s }
好了,以上咱们已经完成了Zinx-V0.1的基本雏形了,虽然只是一个基本的回写客户端数据(咱们以后会自定义处理客户端业务方法),那么接下来咱们就应该测试咱们当前的zinx-V0.1是否可使用了。框架
理论上咱们应该能够如今导入zinx框架,而后写一个服务端程序,再写一个客户端程序进行测试,可是咱们能够经过Go的单元Test功能,进行单元测试
建立zinx/znet/server_test.go
package znet import ( "fmt" "net" "testing" "time" ) /* 模拟客户端 */ func ClientTest() { 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("hello ZINX")) 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) } } //Server 模块的测试函数 func TestServer(t *testing.T) { /* 服务端测试 */ //1 建立一个server 句柄 s s := NewServer("[zinx V0.1]") /* 客户端测试 */ go ClientTest() //2 开启服务 s.Serve() }
在zinx/znet下执行
$ go test
执行结果,以下:
[START] Server listenner at IP: 0.0.0.0, Port 7777, is starting Client Test ... start listen tcp4 err listen tcp4 0.0.0.0:7777: bind: address already in use server call back : hello ZINX, cnt = 6 server call back : hello ZINX, cnt = 6 server call back : hello ZINX, cnt = 6 server call back : hello ZINX, cnt = 6
说明咱们的zinx框架已经可使用了。
固然,若是感受go test 好麻烦,那么咱们能够彻底基于zinx写两个应用程序,Server.go , Client.go
Server.go
package main import ( "zinx/znet" ) //Server 模块的测试函数 func main() { //1 建立一个server 句柄 s s := znet.NewServer("[zinx V0.1]") //2 开启服务 s.Serve() }
启动Server.go
go run Server.go
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("hahaha")) 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.go进行测试
go run Client.go
V0.1版本咱们已经实现了一个基础的Server框架,如今咱们须要对客户端连接和不一样的客户端连接所处理的不一样业务再作一层接口封装,固然咱们先是把架构搭建起来。
如今在ziface
下建立一个属于连接的接口文件iconnection.go
,固然他的实现文件咱们放在znet
下的connection.go
中。
zinx/ziface/iconnection.go
package ziface import "net" //定义链接接口 type IConnection interface { //启动链接,让当前链接开始工做 Start() //中止链接,结束当前链接状态M Stop() //从当前链接获取原始的socket TCPConn GetTCPConnection() *net.TCPConn //获取当前链接ID GetConnID() uint32 //获取远程客户端地址信息 RemoteAddr() net.Addr } //定义一个统一处理连接业务的接口 type HandFunc func(*net.TCPConn, []byte, int) error
该接口的一些基础方法,代码注释已经介绍的很清楚,这里先简单说明一个HandFunc这个函数类型,这个是全部conn连接在处理业务的函数接口,第一参数是socket原生连接,第二个参数是客户端请求的数据,第三个参数是客户端请求的数据长度。这样,若是咱们想要指定一个conn的处理业务,只要定义一个HandFunc类型的函数,而后和该连接绑定就能够了。
zinx/znet/connection.go
package znet import ( "fmt" "net" "zinx/ziface" ) type Connection struct { //当前链接的socket TCP套接字 Conn *net.TCPConn //当前链接的ID 也能够称做为SessionID,ID全局惟一 ConnID uint32 //当前链接的关闭状态 isClosed bool //该链接的处理方法api handleAPI ziface.HandFunc //告知该连接已经退出/中止的channel ExitBuffChan chan bool } //建立链接的方法 func NewConntion(conn *net.TCPConn, connID uint32, callback_api ziface.HandFunc) *Connection{ c := &Connection{ Conn: conn, ConnID: connID, isClosed: false, handleAPI: callback_api, ExitBuffChan: make(chan bool, 1), } return c } /* 处理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中 buf := make([]byte, 512) cnt, err := c.Conn.Read(buf) if err != nil { fmt.Println("recv buf err ", err) c.ExitBuffChan <- true continue } //调用当前连接业务(这里执行的是当前conn的绑定的handle方法) if err := c.handleAPI(c.Conn, buf, cnt); err !=nil { fmt.Println("connID ", c.ConnID, " handle is error") c.ExitBuffChan <- true return } } } //启动链接,让当前链接开始工做 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() }
zinx/znet/server.go
package znet import ( "errors" "fmt" "net" "time" "zinx/ziface" ) //iServer 接口实现,定义一个Server服务类 type Server struct { //服务器的名称 Name string //tcp4 or other IPVersion string //服务绑定的IP地址 IP string //服务绑定的端口 Port int } //============== 定义当前客户端连接的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 里的所有接口方法 ======== //开启网络服务 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, CallBackToClient) 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 NewServer (name string) ziface.IServer { s:= &Server { Name :name, IPVersion:"tcp4", IP:"0.0.0.0", Port:7777, } return s }
CallBackToClient
是咱们给当前客户端conn对象绑定的handle方法,固然目前是server端强制绑定的回显业务,咱们以后会丰富框架,让这个用户可让用户自定义指定handle。
在 start()
方法中,咱们主要作了以下的修改:
//3.3 处理该新链接请求的 业务 方法, 此时应该有 handler 和 conn是绑定的 dealConn := NewConntion(conn, cid, CallBackToClient) cid ++ //3.4 启动当前连接的处理业务 go dealConn.Start()
好了,如今咱们已经将connection的链接和handle绑定了,下面咱们在测试一下Zinx-V0.2的框架是否可使用吧。
实际上,目前Zinx框架的对外接口并未改变,因此V0.1的测试依然有效。
Server.go
package main import ( "zinx/znet" ) //Server 模块的测试函数 func main() { //1 建立一个server 句柄 s s := znet.NewServer("[zinx V0.1]") //2 开启服务 s.Serve() }
启动Server.go
go run Server.go
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("hahaha")) 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.go进行测试
go run Client.go
如今咱们已经简单初始了Zinx的雏形,可是目前离咱们真正的框架还很远,接下来咱们来改进zinx框架。
做者:Aceld(刘丹冰)
简书号:IT无崖子
mail: danbing.at@gmail.com
github: https://github.com/aceld
原创书籍gitbook: http://legacy.gitbook.com/@aceld
原创声明:未经做者容许请勿转载,或者转载请注明出处!