当前位置: 首页 > news >正文

GoLang EASY 游戏框架 之 应用项目+教程 02

1 Program Examples Overview

用easy 实现的 服务端 和客户端样例。

simple 项目构建了比较完备的目录结构,可以作为空项目拿到项目中直接应用。

传送门:https://github.com/slclub/easy

位置:

  •         examples/simple
  •         examples/simple_client

        

2 Simple

比较简单的源码样例;

这是一个简单的服务端,你可以直接拿它做项目,扩展开发即可。

  • 最基本的easy框架使用
  • 简单的游戏架构,不包含数据层;
  • simple代码以极简化为主,项目扩展要 结构化一些
  • 目录多是空的

2.1 simple.Server

项目名:simple

目录结构:

-conf // 配置
--controller 控制器也就是解读消息的入口--callback 放置一些基本的回调函数,如链接创建,服务平滑关闭等--login 登陆模块--player 用户玩家--store 商铺--world 大世界相关
--initialize 初始化,工程启动执行一次;与运行时无关
--lservers 接入easy监听服务 l 是 ```listen``` 当让也可以接入其他的监听服务
--message 消息定义
--models 数据模型,尽量只有数据结构的定义,和基本验证
--services 游戏逻辑存放区域,主要的逻辑都可以放在这里
--vendors 您项目的一些必要基础功能性的包,或者接入第三方包(且这个包需要配置等);// 并非是替代 go mod注意:go mod 中也有一个verdor 且会产生vendor文件夹我们这里的vendors 仅仅是common 通用,基础,标准等的意思这里的包之间互相依赖也少,或者说机会是无比较大(功能性)的包引入后,总需要配置一些东西,甚至和自己的配置参数相关,那么放在这里改造一下(符合工程写法,结构要求等)就比较合适了

运行命令:go build && ./simple

2.2 simple.Client

项目名:simple_client

运行命令:go build && ./simple_client

测试simple服务端对应的客户端样例工程

3 项目代码

3.1 main

在main中Start() 中easy.Serv 可以传入多个服务组件。lservers.Server1()监听的是websocket,lservers.Server2()监听的是TCP。一个应用服务程序,可以容易的监听多个端口服务,且任意多个,多个同一类型的协议也是支持的。

我们在easy.Serv统一阻断主goroutine,其他可以直接使用内置的go 方法直接调用,没有任何高级花哨的调用方式。


import ("github.com/slclub/easy""simple/initialize""simple/lservers"
)func main() {initialize.Init()Start()
}func Start() {easy.Serv(lservers.Server1(), // websocket 监听服务 可以有多个lservers.Server2(), // tcp 服务)
}

3.2 lservers(listening service)

监听端口的服务,代码也很少,这里样例代码就没有按服务分文件。直接上源码:

func Server1() servers.ListenServer {return server1
}func Server2() servers.ListenServer {return server2
}func InitListenServer() {server1 = servers.NewWSServer()server1.Init(option.OptionWith(&agent.Gate{Addr:            ":18080",Protocol:        typehandle.ENCRIPT_DATA_JSON,PendingWriteNum: 2000,LittleEndian:    true,MaxConnNum:      2000,}).Default(option.DEFAULT_IGNORE_ZERO))server2 = servers.NewTCPServer()server2.Init(option.OptionWith(&agent.Gate{Addr:            ":18081",Protocol:        typehandle.ENCRIPT_DATA_JSON,PendingWriteNum: 2000,LittleEndian:    true,MaxConnNum:      2000,}).Default(option.DEFAULT_IGNORE_ZERO))
}
  • servers 是easy的监听服务基础包package
  • server1 :=servers.NewWSServer() 是new一个websocket 服务
  • server1.Init()初始化
  • option.OptionWith 是我们的一个开放配置选择包,为了易用和方便,配置方式多样,默认值等而开发。

参数:

        Addr:监听地址

        Protocol:选用编码组件(快捷换编码的方式,换自定义编码组件在后续章节会说明)

        PendingWriteNum:排队消息长度

        LittleEndian:true=小端,false=大端

        MaxConnNum:最大链接数

        option.DEFAULT_IGNORE_ZERO: 如果赋值0值,或者没有给相应字段赋值,则使用默认值。其中Default方法等于use,类似中间件

3.3 controller

handle 

控制器层面,MVC中的C,接收消息的下一步就是它了。用controller/login模块举例

import ("github.com/slclub/easy/nets/agent""reflect""simple/vendors/log8q"
)func HandleLogin(agent1 agent.Agent, arg any) {log8q.Log().Info("WS controller.Handle.Login info: ", reflect.TypeOf(arg).Elem().Name())
}func HandleLoginTcp(agent2 agent.Agent, arg any) {log8q.Log().Info("TCP controller.Handle.Login info: ", reflect.TypeOf(arg).Elem().Name())
}

分别是websocket 和Tcp 的login handle,它们做的事情是一样。写handle接收消息就是这样简单。

  • agent.Agent : 理解成连接,可以绑定到你的对象上,业务逻辑所用的handle,你也可以再次封装下,函数签名类似:HandleXXX(player *Player, msg Any) 。
  • arg any :是接收客户端的消息,我们直接粗暴的用reflect,查出它的结构体名字,以做测试验证。

binding

做完handle需要将它与消息以及监听的服务绑定,绑定方法也很简单。直接上代码


import ("github.com/slclub/easy/typehandle""simple/controller/login""simple/lservers""simple/message/ID""simple/message/json"
)func InitBindingRoute() {r1 := lservers.Server1().Router()r1.Register(ID.LOGIN_REQ, &json.LoginReq{}, typehandle.HandleMessage(login.HandleLogin))
}func InitBindingRouteServer2() {r2 := lservers.Server2().Router()r2.Register(ID.LOGIN_REQ, &json.LoginReq{}, typehandle.HandleMessage(login.HandleLoginTcp))
}

直接使用监听服务的Router() 获取路由,使用路由Register()绑定 消息ID,消息体,和消息handle,其中handle是可选的(response返回给客户端的消息是不需要handle的)。
这样哪个监听服务对应哪个handle也是一目了然。

callback

这仅仅是笔者自己起的模块名字,目的是为了给连接Open和Close做监听回调的handle。与业务handle有点不同,少了个消息参数。


import ("github.com/slclub/easy/nets/agent""github.com/slclub/easy/servers""simple/lservers""simple/vendors/log8q"
)func RegisterCallerToLservers() {lservers.Server1().Hook().Append(servers.CONST_AGENT_NEW, handleOnConnNew)lservers.Server1().Hook().Append(servers.CONST_AGENT_CLOSE, handleOnConnClose)lservers.Server1().Hook().Append(servers.CONST_SERVER_CLOSE, handleOnServerClose)lservers.Server2().Hook().Append(servers.CONST_AGENT_NEW, handleOnConnNew)lservers.Server2().Hook().Append(servers.CONST_AGENT_CLOSE, handleOnConnClose)
}func handleOnConnNew(ag agent.Agent) {log8q.Log().Info("[CONNECTION.NEW] server create an new connection")
}func handleOnConnClose(ag agent.Agent) {log8q.Log().Info("[CONNECTION.CLOSE] server closed an old connection")
}// the current listening server is closing
// smoothly shutdown the server
func handleOnServerClose(ag agent.Agent) {// ag == nil// 执行一些 平滑停服务的逻辑
}

同样需要我们用具体的监听服务,去调用钩子对象去Append添加链接的handle。需要注意我们使用的是Append,一个钩子可以添加多个handle。笔者相信为了性能大多数人仅仅会用一个,来完善自己链接在线逻辑。

长链接的监听服务,可以共用此handle。

其他的controller

就是我们按照逻辑流程划分的业务模块,不再赘述了。

4 vendors

笔者把一些引用第三方,需要我们简单封装,或配置等的,或者自己实现单独功能依赖少的包,可以放在vendors 下面作为third package存放之地。这个目录名不要用vendor,它是go默认使用vendor,这以前是一套项目部署方案,有了go mod 它就不香了。后来的很多人甚至没见过它。记得不要混淆vendors 和vendor。

4.1 log8q

笔者使用了自己写的log8q这个日志库,zip虽然性能高且强大,总有些记不住,特别依赖goland等IDE才好用一些。

笔者按照log4j的思想撸了个日志的轮子,可配置级别,支持官方的log接口,也有自己简单的使用Info,Debug,Warn,Error,Fatal等级别,每一个传参数的方式都与fmt.Println类似。可以设置日志保留时间(30 * 86400)=30天。

主打一个简单,易用,组件化,性能够用就好。

5 Message 消息

由于go的import特性等,建议将消息定义为单独一个package,减少loop,import的概率。

在message/register.go 中也是注册消息,是不是熟悉,在controller中我们也有消息注册绑定,其实全放在controller里注册也是可以的,在这里我们主要就是注册哪些没有handle的消息


import ("simple/lservers""simple/message/ID""simple/message/json"
)// 将不需要handle 处理的消息 尽量放在这里注册
// 可以将所有注册消息都放在这里也可以
func Init() {InitJson()InitProtobuf()
}func InitJson() {r1 := lservers.Server1().Router()r1.Register(ID.LOGIN_RES, &json.LoginRes{}, nil)r2 := lservers.Server2().Router()r2.Register(ID.LOGIN_RES, &json.LoginRes{}, nil)
}func InitProtobuf() {//r2 := lservers.SimpleServ1.Router()
}

6 总结

致于其它的目录结构也没什么内容,介绍目录的结构的tree中说明就足够了。你有自己习惯可以改吗,不是硬性要求。整体看下来代码量很少的吧。

一个完整的单机游戏工程,就构建完毕了。数据库缓存等就用gorm等即可。

致于说单线程开发,在golang中使用 go 和channel 可以轻易的实现,安全稳定的goroutine。并不需要我们过多的给予定式封装,反而难用且性能低下。不同的需求用不同的方式去控制线程就好了。

go是一个高并发语言,开携程像吃饭喝水一样简单,控制好携程数量可能稍有难度。所以我们不要被单线程思想限制住,并发控制好共有资源,代码也不见的就一定复杂,一定多。性能强力,资源占用少,开发方便,稳定性高就好了,服务器也就这点追求了。当然你能让你的代码变现金,那是比较实在的最求出发点不太一样是吧。

后期我会发布Aoi的package,单线程的,且与handle多线程互通有无。位置 EASY.vendors/aoi。

相关文章:

  • 【计算机视觉】SIFT
  • Java网络编程,使用UDP实现TCP(二), 实现数据传输过程
  • C语言—每日选择题—Day46
  • Redux Toolkit(RTK)在React tsx中的使用
  • UDP群聊
  • Java网络编程,对使用UDP实现TCP(一)三次握手实现的补充
  • 华为OD机试 - 数据单元的变化替换(Java JS Python C)
  • 在idea中使用maven创建dynamic web project
  • 4-Docker命令之docker export
  • Redis KEY*模糊查询导致速度慢、阻塞其他 Redis 操作
  • 破晓6G新时代:迈向新一代星地融合的高速测试解决方案
  • Mysql的多表联合查询
  • OpenAI 承认 ChatGPT 最近确实变懒,承诺修复问题
  • 基于Maven构建OSGI应用(Maven和OSGI结合)
  • 为什么现在是学习 Rust 的最佳时机
  • 【JavaScript】通过闭包创建具有私有属性的实例对象
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • Android优雅地处理按钮重复点击
  • gf框架之分页模块(五) - 自定义分页
  • java小心机(3)| 浅析finalize()
  • PHP的类修饰符与访问修饰符
  • Spring Cloud中负载均衡器概览
  • SQLServer之创建显式事务
  • Zsh 开发指南(第十四篇 文件读写)
  • 阿里云爬虫风险管理产品商业化,为云端流量保驾护航
  • 读懂package.json -- 依赖管理
  • 如何使用 JavaScript 解析 URL
  • 设计模式走一遍---观察者模式
  • 深入 Nginx 之配置篇
  • 手机端车牌号码键盘的vue组件
  • 腾讯优测优分享 | 你是否体验过Android手机插入耳机后仍外放的尴尬?
  • 由插件封装引出的一丢丢思考
  • 《TCP IP 详解卷1:协议》阅读笔记 - 第六章
  • const的用法,特别是用在函数前面与后面的区别
  • 格斗健身潮牌24KiCK获近千万Pre-A轮融资,用户留存高达9个月 ...
  • #LLM入门|Prompt#1.7_文本拓展_Expanding
  • (32位汇编 五)mov/add/sub/and/or/xor/not
  • (附源码)python旅游推荐系统 毕业设计 250623
  • (含react-draggable库以及相关BUG如何解决)固定在左上方某盒子内(如按钮)添加可拖动功能,使用react hook语法实现
  • (一)UDP基本编程步骤
  • (转)项目管理杂谈-我所期望的新人
  • ****** 二十三 ******、软设笔记【数据库】-数据操作-常用关系操作、关系运算
  • .[hudsonL@cock.li].mkp勒索病毒数据怎么处理|数据解密恢复
  • .NET Core 项目指定SDK版本
  • .net core开源商城系统源码,支持可视化布局小程序
  • .NET 反射的使用
  • .NET3.5下用Lambda简化跨线程访问窗体控件,避免繁复的delegate,Invoke(转)
  • .NET多线程执行函数
  • @DependsOn:解析 Spring 中的依赖关系之艺术
  • [14]内置对象
  • [Android]使用Android打包Unity工程
  • [C++]Leetcode17电话号码的字母组合
  • [codevs 2822] 爱在心中 【tarjan 算法】
  • [elastic 8.x]java客户端连接elasticsearch与操作索引与文档
  • [Excel]如何找到非固定空白格數列的條件數據? 以月份報價表單為例