Awesome Open Source
Awesome Open Source

RockGO(下一波重要更新,正在测试中,现有版本不建议直接食用)

重构 RockGO ECS engine: https://github.com/zllangct/ecs

  基于ECS(Entity component System)构建的分布式游戏服务端框架,同时提供Actor模型,目标是致力于快速搭建轻量、高性能、高可用的 分布式游戏后端,以及其他分布式后端应用。

  组件化的构架使得开发者不需要任何改动就能轻松实现一站式开发,分布式部署,规避分布式调试的困难,提升开发效率。框架提供udp、 tcp、websocket常用网络协议,同时提供优雅的协议接口,可让开发者轻松实现其他如kcp等网络协议的定制。

Installation:

$ go get -u github.com/zllangct/RockGO

Quick start:

当然用最简单的hello world告诉你一切都是如此的简单, 完整代码参见example: SingleNode ,更多的用法,参见example目录。

Config:

    {
        "MasterAddress": "127.0.0.1:6666",        //中心服务节点地址
        "LocalAddress": "127.0.0.1:6666",         //本节点服务地址
        "AppName": "defaultApp",                  //本节点服务的App名称
        "Role": [                                 //本节点的服务角色,角色可理解为按服务内容分服
            "master"                              //此处,默认为中心服务节点
        ],
        "NodeDefine": {                           //节点定义
            "node_gate": {                        //程序启动参数中,附加节点名参数可覆盖上面的默认
                "LocalAddress": "0.0.0.0:6601",   //默认参数,eg:go run main.go -node node_gate
                "Role": [                         //此时启动的便是node_gate对应的服务内容
                    "gate"                        //节点服务内容可自由搭配,单服或者分布式的选择只
                ]                                 //需要修改配置文件,不需要修改任何一行代码,做到
            },                                    //单站式开发,随心部署
            "node_login": {                        
                "LocalAddress": "0.0.0.0:6602",
                "Role": [                         //分布式部署:
                    "login"                       //物理机一:go run main.go -node node_master
                ]                                 //物理机二:go run main.go -node node_gate
            },                                    //物理机三:go run main.go -node node_room
            "node_login_gate": {
                "LocalAddress": "0.0.0.0:6603",   //go run main.go -node node_gate_gate 这样启动的
                "Role": [                         //便是一个节点同时具备 login 和 gate 两个角色的节点
                    "login",
                    "gate" 
                ]
            }
            "node_single": {
                "LocalAddress": "0.0.0.0:6604",   //go run main.go -node single 这样启动的
                "Role": [                         //便是所有服务在同一节点,即单服模式,小负载
                    "login",                      //或者开发阶段使用,方便调试
                    "gate" 
                    "master",
                    "room",
                    "location"
                ]
            }
        },        
        "NetConnTimeout": 9000,                 //外网连接心跳超时间隔,单位毫秒
        "NetListenAddress": "0.0.0.0:5555",     //外网服务端口
    }

Server:

    package main
    
    import (
    	"flag"
    	"fmt"
    	"github.com/zllangct/RockGO"
    	"github.com/zllangct/RockGO/component"
    	"github.com/zllangct/RockGO/gate"
    	"github.com/zllangct/RockGO/logger"
    )

    var Server *RockGO.Server
    
    func main()  {  
        //初始化服务节点
    	Server = RockGO.DefaultServer()  
    	
    	/*
    	    添加组件组
    	    添加网关组件(DefaultGateComponent)后,此服务节点拥有网关的服务能力。
    	    同理,添加其他组件,如登录组件(LoginComponent)后,拥有登录的服务内容。
    	*/
    	Server.AddComponentGroup("gate",[]Component.IComponent{&gate.DefaultGateComponent{}})
	
    	//开始服务
    	Server.Serve()
    }  

Client:

//默认网关采用websocket协议,可以使用任意websocket客户点连接测试。example中提供一个laya客户端供测试使用。

Feature:

1. ECS(Entity Component System)构架

  ECS全称Entity-Component-System(实体-组件-系统),是基于组合优于继承,即将不变的部分使用继承以方便复用, 将多变的部分用组合来方便拓展,是按照这种原则的一种设计模式。当然这种设计模式带来了许多比OOP更容易容实现 的特性,当然ECS并不是抛弃OOP,这是一个度的问题。ECS的特点和优势,暴雪在分享《守望先锋》的ECS服务构架的文 章中已经阐述比较详细(中文译文戳这里),也可以看看云风博客中的 讨论 (继续戳),此处都不赘述了。在服务端程序中 的优势,这里简要概括几点:

1.1 自由组合
    Component尽量按照原子性原则设计,只要有合适的功能拆分粒度,服务端可以随性所欲的组合,这在分布式构架
中至关重要,关系到服务端节点间功能的拆分组合。比如中心服务节点担任了游戏大厅和注册登录两项服务,随着用户
规模的扩大,中心节点需要把注册登录拆分出来单独成为节点。在ECS构架中,这将是非常容易的一件事,很少或者
甚至不需要任何修改一行功能代码就能实现,因为节点本身就是功能组件的组合,分服不过是根据配置文件重新组合一
次。这也微服务的思想走在了一起,微服务往往在服务间的调用走的是网络调用,但ECS构架下可以轻松的实现网络本地
调用的自主切换。单服就是真正的单服,本地调用,减少不必要的网络消耗,而不是多个服务部署在了同一台物理机上。
1.2 热插拔
    ECS构架下,实体和组件都能运行时添加删除,框架提供了Initialize、Awake、Start、Update、Destroy 内置系统,
这些内置系统能保证每一个功能组件,或者组件组合所需的完整生命周期。
1.3 易拓展
    功能的拓展,大多情况是新组件,新系统的设计,耦合性极低。
1.4 更优雅的停机
    所以对象都有完整的生命周期,Destroy系统可以保证每个对象在销毁时,得到正确的处理,实现更优雅的停机处理。
1.5 序列化
    所有组件,实现持久化接口之后,都具备序列化的能力。配合优雅的停机,很容易实现停机后的恢复。
1.7 高性能
    每个系统都不会遍历所有的对象,只会过滤出感兴趣的组件,专人专事,效率集中,减少调用,例如不需要Update处理的组件,便
不用实现Update接口,Update系统将不会遍历此组件。 
1.8 方便调试
    分布式调试的麻烦,这里并不存在,单机开发,随心大胆的断点,最后仅仅是一个配置参数完成分布式部署。
1.9 想到了再添加 ... ...

2. Actor 模式

  有空再详细写,反正知道对方的ActorID,无论他在哪儿,无论活在那个节点,Tell() 都能告诉他。框架内自主实现本地调用和RPC 调用的分流。Actor模式的特性与优势,看官自行G或者B。千万别问为什么有了ECS还能有Actor,并不冲突,ECS也是基于OOP实现的,所以Actor基于 ECS实现,而且比OOP实现Actor更简单。

3. RPC

  RPC的性能与可靠性是分布式系统中最重要的一环,游戏要的是效率,服务治理方面够用就行,所以并未选择市面上流行的服务治理型 的RPC调用框架,并且游戏构架本身就具备了服务治理的能力,但RPC框架自带的治理能力又不足以支撑游戏需求,所以略显多余,大体量的框架 复杂的环境配置大大的增加了维护、定制、学习、部署、迁移的难度,尤其对中小型开发者不友好。既要满足中小型项目需要的简易性, 又具有大型项目的扩展性,框架选用了go自带的rpc框架,其性能有目共睹,测评参照这里(戳我就行), 基于net/rpc作了轻量化定制,添加了必要的功能特性:

   (1). 超时机制  (2). 心跳检测 (3). 断线重连 (4). 单向调用 (5). 客户端回调

4. 网络协议

4.1 框架支持的网络协议

框架支持的网络协议有:TCP、UDP、Websocket、http,封包格式如下:

协议名称 协议格式 长度 数据类型
TCP Length-[Type-Data] 4 - [ 4 - n ] 二进制
UDP Length-[Session-Type-Data] 4 - [ 4 - 4 - n ] 二进制
Websocket Type-Data 4 - n 二进制

  http建议直接在网关组件中使用gin、fasthttp等http处理框架对应路由处理函数,http使用途中极有可能与页面有关,虽然 集成到上述方式路由中十分简单,但不建议这样处理,这样僵化了http的灵活性和丰富的功能特性。

4.2 自定义网络协议

  框架内可以很轻松的实现自定义协议的扩展,对原有协议都是非侵入的依赖,只需要简单封装,比如各种可靠UDP协议,KCP、UDT、 ENET等,只需要实现以下述接口:

    //网络协议
    type ServerHandler interface {
        Listen() error                      //监听
        Handle() error                      //处理
    }
    //链接对象
    type Conn interface {
    	WriteMessage(messageType uint32, data []byte) error   //消息发送
    	Addr() string                                         //目标地址
    	Close() error                                         //关闭
    }
    //解包协议
    type Protocol interface {
        ParsePackage( []byte) (int, int)                            //包处理
    	ParseMessage( context.Context,  []byte)([]uint32,[]byte)    //消息处理
    }
   

  网络协议的实现可参照TCP、UDP和Websocket。各种网络协议所提供链接对象可不相同, 需要实现连接对象接口进行统一,供Session使用。网络协议和链接对象接口的实现相对简单,不容 易产生歧义,其中解包协议参照TCP和Websocket用法:

/* TCP LTD protocol 
	Length—(Type—Data) ,数据长度—(消息类型—消息体) 大小:  4 — (4 — n)
*/
type LtdProtocol struct{}

//完整包中解析出消息ID和数据部分
func (s *LtdProtocol) ParseMessage(ctx context.Context,data []byte)([]uint32,[]byte){
    mt := binary.BigEndian.Uint32(data[:4])
    return []uint32{mt}, data[4:]
}

//检查包是否接受完整
func (s *LtdProtocol) ParsePackage(buff []byte) (pkgLen, status int) {
    if len(buff) < 4 {
        return 0, PACKAGE_LESS
    }
    length := binary.BigEndian.Uint32(buff[:4])
    
    if length > 1048576000 || len(buff) > 1048576000 { // 1000MB
        return 0, PACKAGE_ERROR
    }
    if len(buff) < int(length) {
        return 0, PACKAGE_LESS
    }
    return int(length), PACKAGE_FULL
}


/* Websocket TD protocol
	Type—Data ,消息类型—消息体 大小:  4 — n
*/
type TdProtocol struct{}

//解析消息ID和消息数据
func (s *TdProtocol) ParseMessage(ctx context.Context,data []byte)([]uint32,[]byte){
    mt := binary.BigEndian.Uint32(data[:4])
    return []uint32{mt}, data[4:]
}

//websocket 自带粘包处理,此处无需手动处理
func (s *TdProtocol) ParsePackage(buff []byte) (pkgLen, status int) {
    return 0,0
}

5. 消息序列化协议

5.1 支持的序列化协议
   (1). Json  (2). ProtoBuf
5.2 自定义序列化协议

  自定义序列化协议需要实现以下接口:

//消息解析协议
type MessageProtocol interface {
    Marshal( interface{})([]byte,error)         //序列化
    Unmarshal( []byte, interface{})error        //反序列化
}

7. 业务接口

7.1 路由规则

  框架提供消息的路由,无需用户手动对应消息的处理方法,框架根据函数自动判断是否为消息处理函数。需要满足以下条件 :

  (1). 结构体继承 ApiBase
  (2). 函数必须为结构体导出函数
  (3). 函数必须为以下结构:func (this *XXX) FunctionName(sess *network.Session,message *MessageStruct)
   第一参数为 会话Session,第二参数为消息对应的结构体,框架会更加第二参数去判断处理对应的消息。

  参见:

//协议对应字典,推荐使用工具生成该文件,以便前后端对应准确
//稍后会提供相应工具,目前完成了protobuf 导出c# 和 golang 的协议对应
//完善之后会更新至本仓库,由于过于简单,客官可自行完成
// 原理:1)读取proto文件 2)提取消息名 3)按照同一序号生成c#、golang或者其他语言文件(字符串拼接)

var Testid2mt = map[reflect.Type]uint32{
    reflect.TypeOf(&TestMessage{}):1,
    reflect.TypeOf(&TestLogin{}):2,
    reflect.TypeOf(&PlayerInfo{}):3,
}

//消息定义
type TestMessage struct {
    Name string
}
type TestReply struct {
    Result bool
}

//接口组定义
type TestApi struct {
    network.ApiBase         //继承ApiBase
}

/*
    使用协议接口时,需先初始化,初始化时需传入定义的消息号对应字典
    以及所需的消息序列化组件,可轻易切换为protobuf,msgpack等其他序列化工具
*/
func NewTestApi() *TestApi  {
    r:=&TestApi{}
    r.Instance(r).SetMT2ID(Testid2mt).SetProtocol(&MessageProtocol.JsonProtocol{})
    return r
}

//协议接口1  Hello,框架会自动判断TestMessage类型消息,自动路由至此函数处理
func (this *TestApi)Hello(sess *network.Session,message *TestMessage) {
    //打印消息
    println(fmt.Sprintf("Hello,%s", message.Name))

    //回复消息
    res:=&TestReply{
        Result:true,
    }
    this.Reply(sess,res)
}

//协议接口2  other,同理,该函数处理 Other 类型消息
func (this *TestApi) Other(sess *network.Session,message *Other) {
	......
}
7.1 自定义路由

  当然用户可以不用使用框架自带的消息路由方法,可以实现NetAPI接口自定义消息路由规则:

type NetAPI interface {
        Init()                                                                        //初始化
	Route(*Session, uint32, []byte)	                                              //反序列化并路由到api处理函数
	Reply(session *Session,message interface{})error                              //序列化消息并发送至客户端
}

8. Example列表

  组件定义范例
  Laya测试客户端
  Golang Websocket测试客户端
  分布式部署配置范例
  ECS单节点范例
  ECS+Actor单节点范例
  TCPUDP服务端客户端范例
  Websocket服务端范例

8. 计划任务

  (1). 完善现有example
  (2). 提供房间类游戏大厅、房间模型(就是你想的棋牌)
  (3). 增加Laya 客户端范例
  (4). 增加Unity 帧同步客户端范例
  (5). 增加KCP协议支持
  (6). 后台管理页面、数据统计页面
  (7). 节点平滑升级
  (8). 提供protobuf 前后端协议自动化对应工具
  。。。。。。

9. 写在后面

   致谢: gin — gin-gonicwebsocket—gorillago-component—shadowmintTarsGo—TarsCloud
   喜欢的朋友给个星星~

  【RockGO交流群①】(已满,请加②群)


Get A Weekly Email With Trending Projects For These Topics
No Spam. Unsubscribe easily at any time.
go (14,165
golang (3,613
websockets (297
rpc (261
distributed (221
concurrency (189
lightweight (186
ecs (120
websocket-server (57
actor-model (56
entity-component-system (54
tcp-server (42
extensible (24
gameserver (20

Find Open Source By Browsing 7,000 Topics Across 59 Categories