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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/223707.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【坑备注】自定义@Repeatable注解

在字段上定义可重复使用的注解: Target(value {ElementType.FIELD}) Retention(value RetentionPolicy.RUNTIME) Repeatable(value Indexes.class) public interface TableIndex {IndexTypeEnum type();String name();int order(); } 这里指定了Repeatable注解…

最大子数组和java实现【动态规划基础练习】

12.15 最大子数组和 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 子数组 是数组中的一个连续部分。 示例 1: 输入:nums [-2,1,-3,4,-1,2,1,-5,4]…

深入了解空号检测API:提升通信效率的关键

引言 随着通信技术的不断发展,人们对于通信效率的要求也越来越高。在通信过程中,空号检测是一个非常重要的环节,它可以帮助我们避免无效的通信,提高通信效率。而空号检测API则是实现空号检测功能的重要工具。 空号检测API 空号…

git的分支的使用,创建分支,合并分支,删除分支,合并冲突,分支管理策略,bug分支,强制删除分支

GIT | 分支 文章目录 GIT | 分支创建分支合并分支删除分支合并冲突分支管理策略bug分支强制删除分支 创建分支 查看当前本地仓库中有哪些分支 git branchHEAD所指向的分支就是当前正在工作的分支 cat .git/HEAD创建一个分支 git branch dev创建好了,但是目前还是…

MapReduce模拟统计每日车流量-解决方案

MapReduce模拟统计每日车流量-解决方案 1.Map阶段:将原始数据分割成若干个小块,每个小块由一个Map任务处理。Map任务将小块中的每个数据项映射成为一个键值对,其中键为时间戳,值为车流量。2.Shuffle阶段:将Map任务输出…

数字人克隆系统源码无限克隆数字人!

随着人工智能技术的不断发展,数字人的应用越来越广泛。数字人可以用于虚拟演员、虚拟客服、虚拟主持人等领域,为企业和个人带来更多的商业价值和娱乐体验。然而,数字人的制作过程需要大量的人力和时间,成本较高,限制了…

记一次clickhouse启动报错

clickhouse一次排错 clickhouse启动报错 报错: Application: Code: 210. DB::Exception: Listen [::]:8123 failed: Poco::Exception. Code: 1000, e.code() 0, DNS error: EAI: Address family for hostname not supported (version 23.3.1.2823 (official bui…

QT6.3下载及安装步骤详解

QT 6.3 是 QT 的最新版本,它带来了许多新的功能和改进。下面介绍 QT 6.3 的下载和安装步骤。 1. 下载 QT 6.3 首先,需要从 QT 官方网站下载 QT 6.3 的安装包。打开浏览器,进入 QT 官方网站(https://www.qt.io/)&am…

23年Q2.Q3书单更新

0x00 如题 记录Q2.Q3阅读过的有价值的文章。 0x01 文章 Nginx正向代理配置-嗨客网 OU/Group/Group Policy组织单元、组和组策略_回关看不曾走远的技术博客_51CTO博客 https://www.cnblogs.com/-qing-/p/14979735.html ----域渗透-LDAP中域内组、OU笔记 Linux chmod 命令…

破局:国内市场确实存在“消费升级”和“消费降级”,3.0全新新零售商业模式

国内市场确实存在“消费升级”和“消费降级”两个趋势,这是由于不同消费者群体的需求和购买力存在差异。消费升级主要发生在高端市场,消费者愿意为高品质、高价值、高价格的商品和服务付出更多。而消费降级则主要发生在中低端市场,消费者更加…

vue2项目vue-qrcode-reader 扫一扫二维码插件

vue2项目 vue-qrcode-reader 扫一扫二维码插件 问题所在解决办法成功展示 问题所在 今天在引导师弟做扫二维码功能,发现通过npm install --save vue-qrcode-reade安装死活就是报错TypeError: Object...) is not a function 解决办法 百度了很多大牛的博客&#…

serializable和parcelable的区别(GPT回答)

在 Android 中,Parcelable 和 Serializable 是两种用于实现对象序列化和反序列化的接口,但它们有一些重要的区别: 性能: Parcelable 比 Serializable 更高效。Parcelable 的设计目标是为了在 Android 中传递对象数据,尤…

Leetcode—2415.反转二叉树的奇数层【中等】

2023每日刷题(六十) Leetcode—2415.反转二叉树的奇数层 BFS的C实现代码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(n…

【docker 】Dockerfile指令学习

学习文档地址 上篇文章:【docker 】基于Dockerfile创建镜像 Dockerfile指令文档地址 .dockerignore 文件 Dockerfile指令 常见的指令 Dockerfile 指令说明FROM指定基础镜像,用于后续的指令构建。MAINTAINER指定Dockerfile的作者/维护者。&#xff…

RK3568平台(网络篇) 有线网络基本概念及测试手法

一.什么是交换机? 交换机是一种用于电(光)信号转发的网络设备。它可以为接入交换机的任意两个网络节点提供独享的电信号通路。最常见的交换机是以太网交换机。交换机工作于OSI参考模型的第二层,即数据链路层。交换机拥有一条高带宽的背部总线和内部交换…

c++原子变量

原子变量 概述 ​ C11提供了一个原子类型std::atomic<T>&#xff0c;通过这个原子类型管理的内部变量就可以称之为原子变量&#xff0c;我们可以给原子类型指定bool、char、int、long、指针等类型作为模板参数&#xff08;不支持浮点类型和复合类型&#xff09;。 ​ …

51单片机应用从零开始(十一)·数组函数、指针函数

51单片机应用从零开始&#xff08;九&#xff09;数组-CSDN博客 51单片机应用从零开始&#xff08;十&#xff09;指针-CSDN博客 目录 1. 用数组作函数参数控制流水花样 2. 用指针作函数参数控制 P0 口 8 位 LED 流水点亮 1. 用数组作函数参数控制流水花样 要在51单片机中…

回溯算法第一篇(子集树问题【三种思路】、0-1背包问题、最小重量机器设计问题)

目录 1. 子集树问题 解法一 解法二 解法三 2. 0-1背包问题&#xff08;使用子集树解决&#xff09; 3. 最小重量机器设计问题 1. 子集树问题 子集力扣链接 题目描述&#xff1a;给你一个整数数组 nums &#xff0c;数组中的元素 互不相同 。返回该数组所有可能的子集&am…

NV040D语音芯片应用于取暖桌:智能语音提高用户体验

科技与生活的结合&#xff0c;是科技发展的展示。天气的降温&#xff0c;取暖桌越来越取得用户的心&#xff0c;时至今日传统的取暖桌已经没有办法满足用户的需求&#xff0c;智能语音取暖桌给用户的生活带来了不一样的体验。 NV040D语音芯片是一款性能稳定的芯片&#xff0c;拥…

XS9922B-国产cvi协议,满足国内车载视频传输领域国产化降本需求

XS9922B 是一款 4 通道模拟复合视频解码芯片&#xff0c;支持 HDCCTV 高清协议和 CVBS 标 清协议&#xff0c;视频制式支持 720P/1080P 高清制式和 960H/D1 标清制式。芯片将接收到的高清 模拟复合视频信号经过模数转化&#xff0c;视频解码以及 2D 图像处理之后&#xff0c;转…