Zinx源代码
github
https://github.com/aceld/zinx
gitee码云
https://gitee.com/Aceld/zinx
在线开发教程
【B站】
zinx视频教程-Golang轻量级TCP服务器框架-适合自学者
【YouTube】
zinx开发YouTube中国版
【Zinx教程目录】
完整教程电子版(在线高清)-下载
Zinx框架视频教程(框架篇)(完整版下载)链接在下面正文
Zinx框架视频教程(应用篇)(完整版下载)链接在下面正文
Zinx开发API文档
Zinx第一章-引言
Zinx第二章-初识Zinx框架
Zinx第三章-基础路由模块
Zinx第四章-全局配置
Zinx第五章-消息封装
Zinx第六章-多路由模式
Zinx第七章-读写分离模型
Zinx第八章-消息队列及多任务
Zinx第九章-链接管理
Zinx第十章-连接属性设置
【Zinx应用案例-MMO多人在线游戏】
(1)案例介绍
(2)AOI兴趣点算法
(3)数据传输协议protocol buffer
(4)Proto3协议定义
(5)构建项目及用户上线
(6)世界聊天
(7)上线位置信息同步
(8)移动位置与AOI广播
(9)玩家下线
(10)模拟客户端AI模块
为了我们方便测试服务器的调试,这里提供了一个,模拟客户端的代码。
现在我们不用在windows上打开client.exe
也能够访问并且测试我们的服务器.
完整代码如下:
mmo_game/client_AI_robot.go
package main
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/golang/protobuf/proto"
"io"
"math/rand"
"net"
"os"
"os/signal"
"time"
"zinx/zinx_app_demo/mmo_game/pb"
)
type Message struct {
Len uint32
MsgId uint32
Data []byte
}
type TcpClient struct {
conn net.Conn
X float32
Y float32
Z float32
V float32
Pid int32
isOnline chan bool
}
func (this *TcpClient) Unpack(headdata []byte) (head *Message, err error) {
headbuf := bytes.NewReader(headdata)
head = &Message{}
// 读取Len
if err = binary.Read(headbuf, binary.LittleEndian, &head.Len); err != nil {
return nil, err
}
// 读取MsgId
if err = binary.Read(headbuf, binary.LittleEndian, &head.MsgId); err != nil {
return nil, err
}
// 封包太大
//if head.Len > MaxPacketSize {
// return nil, packageTooBig
//}
return head, nil
}
func (this *TcpClient) Pack(msgId uint32, dataBytes []byte) (out []byte, err error) {
outbuff := bytes.NewBuffer([]byte{})
// 写Len
if err = binary.Write(outbuff, binary.LittleEndian, uint32(len(dataBytes))); err != nil {
return
}
// 写MsgId
if err = binary.Write(outbuff, binary.LittleEndian, msgId); err != nil {
return
}
//all pkg data
if err = binary.Write(outbuff, binary.LittleEndian, dataBytes); err != nil {
return
}
out = outbuff.Bytes()
return
}
func (this *TcpClient) SendMsg(msgID uint32, data proto.Message) {
// 进行编码
binary_data, err := proto.Marshal(data)
if err != nil {
fmt.Println(fmt.Sprintf("marshaling error: %s", err))
return
}
sendData, err := this.Pack(msgID, binary_data)
if err == nil {
this.conn.Write(sendData)
} else {
fmt.Println(err)
}
return
}
func (this *TcpClient) AIRobotAction() {
//聊天或者移动
//随机获得动作
tp := rand.Intn(2)
if tp == 0 {
content := fmt.Sprintf("hello 我是player %d, 你是谁?", this.Pid)
msg := &pb.Talk{
Content: content,
}
this.SendMsg(2, msg)
} else {
//移动
x := this.X
z := this.Z
randpos := rand.Intn(2)
if randpos == 0 {
x -= float32(rand.Intn(10))
z -= float32(rand.Intn(10))
} else {
x += float32(rand.Intn(10))
z += float32(rand.Intn(10))
}
//纠正坐标位置
if x > 410 {
x = 410
} else if x < 85 {
x = 85
}
if z > 400 {
z = 400
} else if z < 75 {
z = 75
}
//移动方向角度
randv := rand.Intn(2)
v := this.V
if randv == 0 {
v = 25
} else {
v = 335
}
//封装Postsition消息
msg := &pb.Position{
X: x,
Y: this.Y,
Z: z,
V: v,
}
fmt.Println(fmt.Sprintf("player ID: %d. Walking...", this.Pid))
//发送移动MsgID:3的指令
this.SendMsg(3, msg)
}
}
/*
处理一个回执业务
*/
func (this *TcpClient) DoMsg(msg *Message) {
//处理消息
//fmt.Println(fmt.Sprintf("msg id :%d, data len: %d", msg.MsgId, msg.Len))
if msg.MsgId == 1 {
//服务器回执给客户端 分配ID
//解析proto
syncpid := &pb.SyncPid{}
proto.Unmarshal(msg.Data, syncpid)
//给当前客户端ID进行赋值
this.Pid = syncpid.Pid
} else if msg.MsgId == 200 {
//服务器回执客户端广播数据
//解析proto
bdata := &pb.BroadCast{}
proto.Unmarshal(msg.Data, bdata)
//初次玩家上线 广播位置消息
if bdata.Tp == 2 && bdata.Pid == this.Pid {
//本人
//更新客户端坐标
this.X = bdata.GetP().X
this.Y = bdata.GetP().Y
this.Z = bdata.GetP().Z
this.V = bdata.GetP().V
fmt.Println(fmt.Sprintf("player ID: %d online.. at(%f,%f,%f,%f)", bdata.Pid, this.X, this.Y, this.Z, this.V))
//玩家已经成功上线
this.isOnline <- true
} else if bdata.Tp == 1 {
fmt.Println(fmt.Sprintf("世界聊天,玩家%d说的话是: %s", bdata.Pid, bdata.GetContent()))
}
}
}
func (this *TcpClient) Start() {
go func() {
for {
//read per head data
headdata := make([]byte, 8)
if _, err := io.ReadFull(this.conn, headdata); err != nil {
fmt.Println(err)
return
}
pkgHead, err := this.Unpack(headdata)
if err != nil {
return
}
//data
if pkgHead.Len > 0 {
pkgHead.Data = make([]byte, pkgHead.Len)
if _, err := io.ReadFull(this.conn, pkgHead.Data); err != nil {
return
}
}
//处理服务器回执业务
this.DoMsg(pkgHead)
}
}()
select {
case <-this.isOnline:
//自动AI业务
go func() {
for {
this.AIRobotAction()
time.Sleep(3 * time.Second)
}
}()
}
}
func NewTcpClient(ip string, port int) *TcpClient {
addrStr := fmt.Sprintf("%s:%d", ip, port)
conn, err := net.Dial("tcp", addrStr)
if err == nil {
client := &TcpClient{
conn: conn,
Pid: 0,
X: 0,
Y: 0,
Z: 0,
V: 0,
isOnline: make(chan bool),
}
return client
} else {
panic(err)
}
}
func main() {
for i := 0; i < 1000; i++ {
client := NewTcpClient("127.0.0.1", 8999)
client.Start()
time.Sleep(1 * time.Second)
}
// close
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill)
sig := <-c
fmt.Println("=======", sig)
}
这里实际上一个TcpClient
就是一个模拟的客户端,包含与服务器建立的链接conn
、还有模拟玩家的基本信息(ID和坐标等)
TcpClient
提供了Start()
的启动方法,Start()
中,其中读写的业务用一个单独的goroutine去装载。
AIRobotAction()
是一个简单的AI自动化方法,他会随机的选择移动
或者聊天
。这样一个模拟客户端上线之后,就不会傻傻的站立在那里了。
我们也可以通过在main()
中,更改循环变量i
来进行循环建立多个客户端进行压力测试
func main() {
for i := 0; i < 1000; i++ {
client := NewTcpClient("127.0.0.1", 8999)
client.Start()
time.Sleep(1 * time.Second)
}
// close
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill)
sig := <-c
fmt.Println("=======", sig)
}
关于作者:
作者:Aceld(刘丹冰)
号:IT无崖子
mail: [email protected]
github: https://github.com/aceld
原创书籍gitbook: http://legacy.gitbook.com/@aceld
原创声明:未经作者允许请勿转载, 如果转载请注明出处