SkillAgentSearch skills...

Origin

A game server framework in Go (golang).

Install / Use

/learn @duanhf2012/Origin
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

origin 游戏服务器引擎简介

origin 是一个由 Go 语言(golang)编写的分布式开源游戏服务器引擎。origin适用于各类游戏服务器的开发,包括 H5(HTML5)游戏服务器。

origin 解决的问题:

  • origin总体设计如go语言设计一样,总是尽可能的提供简洁和易用的模式,快速开发。
  • 能够根据业务需求快速并灵活的制定服务器架构。
  • 利用多核优势,将不同的service配置到不同的node,并能高效的协同工作。
  • 将整个引擎抽象三大对象,node,service,module。通过统一的组合模型管理游戏中各功能模块的关系。
  • 有丰富并健壮的工具库。

Hello world!

下面我们来一步步的建立origin服务器,建议使用origin v2版本,内容如下:

package main

import (
    "github.com/duanhf2012/origin/v2/node"
)

func main() {
    node.Start()
}

以上只是基础代码,具体运行参数和配置请参照第一章节。

一个origin进程需要创建一个node对象,Start开始运行。您也可以直接下载origin引擎示例:

git clone https://github.com/duanhf2012/originserver_v2.git

本文所有的说明都是基于该示例为主。

我们也提供了示例项目,包含网络模块(支持tcp,kcp,websocket)、角色加载登录流程,存档,配置读取等功能。请参照:

git clone https://github.com/duanhf2012/origingame.git

origin引擎三大对象关系

  • Node: 可以认为每一个Node代表着一个origin进程
  • Service:一个独立的服务可以认为是一个大的功能模块,他是Node的子集,创建完成并安装Node对象中。服务可以支持对外部RPC等功能。
  • Module: 这是origin最小对象单元,强烈建议所有的业务模块都划分成各个小的Module组合,origin引擎将监控所有服务与Module运行状态,例如可以监控它们的慢处理和死循环函数。Module可以建立树状关系。Service本身也是Module的类型。

origin配置说明

origin的配置文件以json格式,主要包含Discovery、RpcMode、NodeList、Service部分,具体格式如下:

{
    "Discovery":{},
    "RpcMode":{},
    "NodeList":[],
    "Service":{},
    "Global": {}
}

Discovery部分

origin目前支持etcd与origin自带的服务发现类型。

Etcd方式示例:

  "Discovery": {
    "Etcd": {
      "TTLSecond": 10,
      "DialTimeoutMillisecond": 3000,
      "EtcdList": [
        {
          "LocalNetworkName": "network_Area1",
          "Endpoints": ["http://127.0.0.1:12379"],
          "UserName": "",
          "Password": "",
          "Cert": "",
          "CertKey": "",
          "Ca": ""
        },
        {
          "NeighborNetworkName": ["network_Area2"],
          "Endpoints": ["http://127.0.0.1:12379"],
          "UserName": "",
          "Password": "",
          "Cert": "",
          "CertKey": "",
          "Ca": ""
        }
      ]
    }
  }

TTLSecond:表示健康检查TTL失效时间10秒

DialTimeoutMillisecond: 与etcd连接超时时间

EtcdList:Etcd列表,可以多个Etcd服务器连接(注意:列表中必需有一个LocalNetworkName项,表示当前所有的Node归属当前网络名为network_Area1)Node下所有的服务会往network_Area1中注册。监听该网络的结点可以发现该网络中的Service。本地网络会默认监听本地网络中所有的服务。

NeighborNetworkName:表示监听的邻居网络名,可以发现该网络中所有Service

Endpoints:Etcd服务器地址

Origin方式示例:

{
  "Discovery": {
    "Origin":{
      "TTLSecond": 10,
     "LocalMasterNodeId": "bot",
      "MasterNodeList": [
        {
          "NodeId": "bot",
          "ListenAddr": "127.0.0.1:11001"
        },
        {
          "NodeId": "mp1server",
          "ListenAddr": "127.0.0.1:11000"
        }
      ]
    }
  }
}

TTLSecond:表示健康检查TTL失效时间10秒

LocalMasterNodeId:本地所有的Node归属当前Master NodeId。归属当前的所有的Node会往该Master Node中注册服务。MasterNode会自动同步给所有的监听结点。本地Node会默认监听本地Master Node中所有的服务。注意LocalMasterNodeId配置的NodeId要在MasterNodeList列表中。

MasterNodeList:指定哪些Node为服务发现Master结点,需要配置NodeId与ListenAddr,注意它们要与实际的Node配置一致。

RpcMode部分

默认模式

{
  "RpcMode":{
      "Type": "Default"
  }
}

默认模式下,origin的node之前通过tcp连接组网。

Nats模式

{
  "RpcMode":{
      "Type": "Nats",
      "remark": "support Default or Nats",
      "Nats": {
          "NatsUrl":"127.0.0.1:4222",
          "NoRandomize": true
      }
  }
}

NatsUrl:Nats连接url串

NoRandomize:在多连接集群模式下,连接nats节点是否顺序策略。false表示随机连接,true表示顺序连接。

NodeList部分

{
    "NodeList":[
        {
          "NodeId": "node_1",
          "Private": false,
          "ListenAddr":"127.0.0.1:8001",
          "MaxRpcParamLen": 409600,
          "CompressBytesLen": 20480,
          "remark":"//以_打头的,表示只在本机进程,不对整个子网公开",
          "ServiceList": ["TestService1","TestService2","TestServiceCall","GateService","_TcpService","HttpService","WSService"]
        },
        {
          "NodeId": "node_2",
          "Private": false,
          "ListenAddr":"127.0.0.1:8002",
          "MaxRpcParamLen": 409600,
          "CompressBytesLen": 20480,
          "remark":"//以_打头的,表示只在本机进程,不对整个子网公开",
          "ServiceList": ["TestService1","TestService2","TestServiceCall","GateService","TcpService","HttpService","WSService"]
        }
    ]

以上配置了两个结点服务器程序:

  • NodeId: 表示origin程序的结点Id标识,同一个服务发现网络中不允许重复。
  • Private: 是否私有结点,如果为true,表示其他结点不会发现它,但可以自我运行。
  • ListenAddr:Rpc通信服务的监听地址
  • MaxRpcParamLen:Rpc参数数据包最大长度,该参数可以缺省,默认一次Rpc调用支持最大4294967295byte长度数据。
  • CompressBytesLen:Rpc网络数据压缩,当数据>=20480byte时将被压缩。该参数可以缺省或者填0时不进行压缩。
  • remark:备注,可选项
  • ServiceList:该Node拥有的服务列表,注意:origin按配置的顺序进行安装初始化。但停止服务的顺序是相反。

在启动程序命令originserver -start nodeid="node_1"中nodeid就是根据该配置装载服务。 更多参数使用,请使用originserver -help查看。

Service 部分

service.json如下:

{
  "Service":{
      "HttpService":{
        "ListenAddr":"0.0.0.0:9402",
        "ReadTimeout":10000,
        "WriteTimeout":10000,
        "ProcessTimeout":10000,
        "ManualStart": false,
        "CAFile":[
        {
          "Certfile":"",
            "Keyfile":""
        }]
  
      },
      "TcpService":{
        "ListenAddr":"0.0.0.0:9030",
        "MaxConnNum":3000,
        "PendingWriteNum":10000,
        "LittleEndian":false,
        "MinMsgLen":4,
        "MaxMsgLen":65535
      },
      "WSService":{
        "ListenAddr":"0.0.0.0:9031",
        "MaxConnNum":3000,
        "PendingWriteNum":10000,
        "MaxMsgLen":65535
      }  
  },
  "NodeService":[
   {
      "NodeId":1,
      "TcpService":{
        "ListenAddr":"0.0.0.0:9830",
        "MaxConnNum":3000,
        "PendingWriteNum":10000,
        "LittleEndian":false,
        "MinMsgLen":4,
        "MaxMsgLen":65535
      },
      "WSService":{
        "ListenAddr":"0.0.0.0:9031",
        "MaxConnNum":3000,
        "PendingWriteNum":10000,
        "MaxMsgLen":65535
      }  
   },
   {
      "NodeId":2,
      "TcpService":{
        "ListenAddr":"0.0.0.0:9030",
        "MaxConnNum":3000,
        "PendingWriteNum":10000,
        "LittleEndian":false,
        "MinMsgLen":4,
        "MaxMsgLen":65535
      },
      "WSService":{
        "ListenAddr":"0.0.0.0:9031",
        "MaxConnNum":3000,
        "PendingWriteNum":10000,
        "MaxMsgLen":65535
      }  
   }
  ]
 
}

以上配置分为两个部分:Global,Service与NodeService。Global是全局配置,在任何服务中都可以通过cluster.GetCluster().GetGlobalCfg()获取,NodeService中配置的对应结点中服务的配置,如果启动程序中根据nodeid查找该域的对应的服务,如果找不到时,从Service公共部分查找。

HttpService配置

  • ListenAddr:Http监听地址
  • ReadTimeout:读网络超时毫秒
  • WriteTimeout:写网络超时毫秒
  • ProcessTimeout: 处理超时毫秒
  • ManualStart: 是否手动控制开始监听,如果true,需要手动调用StartListen()函数
  • CAFile: 证书文件,如果您的服务器通过web服务器代理配置https可以忽略该配置

TcpService配置

  • ListenAddr: 监听地址
  • MaxConnNum: 允许最大连接数
  • PendingWriteNum:发送网络队列最大数量
  • LittleEndian:是否小端
  • MinMsgLen:包最小长度
  • MaxMsgLen:包最大长度

WSService配置

  • ListenAddr: 监听地址
  • MaxConnNum: 允许最大连接数
  • PendingWriteNum:发送网络队列最大数量
  • MaxMsgLen:包最大长度

Global部分

{
  "Global": {
    "AreaId": 1
  }
}

这部分,在所有的服务中都可以类似以下代码获取:

globalCfg := cluster.GetCluster().GetGlobalCfg()
mapGlobal, ok := globalCfg.(map[string]interface{})
if ok == false {
    return fmt.Errorf("Canot find Global from config.")
}

areaId, ok := mapGlobal["AreaId"]

第一章:origin基础:

查看github.com/duanhf2012/originserver_v2中的simple_service中新建两个服务,分别是TestService1.go与CTestService2.go。

simple_service/TestService1.go如下:

package simple_service

import (
    "github.com/duanhf2012/origin/v2/node"
    "github.com/duanhf2012/origin/v2/service"
)

//模块加载时自动安装TestService1服务
func init(){
    node.Setup(&TestService1{})
}

//新建自定义服务TestService1
type TestService1 struct {
    //所有的自定义服务必需加入service.Service基服务
    //那么该自定义服务将有各种功能特性
    //例如: Rpc,事件驱动,定时器等
    service.Service
}

//服务初始化函数,在安装服务时,服务将自动调用OnInit函数
func (slf *TestService1) OnInit() error {
    return nil
}


simple_service/TestService2.go如下:

import (
    "github.com/duanhf2012/origin/v2/node"
    "github.com/duanhf2012/origin/v2/service"
)

func init(){
    node.Setup(&TestService2{})
}

type TestService2 struct {
    service.Service
}

func (slf *TestService2) OnInit() error {
    return nil
}


  • main.go运行代码
package main

import (
    "github.com/duanhf2012/origin/v2/node"
    //导入simple_service模块
    _"orginserver/simple_service"
)

func main(){
    node.Start()
}

  • config/cluster/cluster.json如下:
{
    "NodeList":[
        {
          "NodeId": "nodeid_1",
          "Private": false,
          "ListenAddr":"127.0.0.1:8001",
          "remark":"//以_打头的,表示只在本机进程,不对整个子网开发",
          "ServiceList": ["TestService1","TestService2"]
        }
    ]
}

编译后运行结果如下:

#originserver -start nodeid="nodeid_1"
TestService1 OnInit.
TestService2 OnInit.

第二章:Service中常用功能:

定时器:

在开发中最常用的功能有定时任务,origin提供两种定时方式:

一种AfterFunc函数,可以间隔一定时间触发回调,参照simple_service/TestService2.go,实现如下:

func (slf *TestService2) OnInit() error {
    fmt.Printf("TestService2 OnInit.\n")
    slf.AfterFunc(time.Second*1,slf.OnSecondTick)
    return nil
}

func (slf *TestService2) OnSecondTick(){
    fmt.Printf("tick.\n")
    slf.AfterFunc(time.Second*1,slf.OnSecondTick)
}

此时日志可以看到每隔1秒钟会print一次"tick.",如果下次还需要触发,需要重新设置定时器

另一种方式是类似Linux系统的crontab命令,使用如下:


func (slf *TestService2) OnInit() error {
    fmt.Printf("TestService2 OnInit.\n")

    //crontab模式定时触发
    //NewCronExpr的参数分别代表:Seconds Minutes Hours DayOfMonth Month DayOfWeek
    //以下为每换分钟时触发
    cron,_:=timer.NewCronExpr("0 * * * * *")
    slf.CronFunc(cron,slf.OnCron)
    return nil
}


func (slf *TestService2) OnCron(cron *timer.Cron){
    fmt.Printf(":A minute passed!\n")
}

以上运行结果每换分钟时打印:A minute passed!

打开多协程模式:

在origin引擎设计中,所有的服务是单协程模式,这样在编写逻辑代码时,不用考虑线程安全问题。极大的减少开发难度,但某些开发场景下不用考虑这个问题,而且需要并发执行的情况,比如,某服务只处理数据库操作控制,而数据库处理中发生阻塞等待的问题,因为一个协程,该服务接受的数据库操作只能是一个 一个的排队处理,效率过低。于是可以打开此模式指定处理协程数,代码如下:

func (slf *TestService1) OnInit() error {
    fmt.Printf("TestService1 OnInit.\n")

    //打开多线程处理模式,10个协程并发处理
    slf.SetGoRoutineNum(10)
    return nil
}

性能监控功能:

我们在开发一个大型的系统时,经常由于一些代码质量的原因,产生处理过慢或者死循环的产生,该功能可以被监测到。使用方法如下:

func (slf *TestService1) OnInit() error {
    fmt.Pr
View on GitHub
GitHub Stars1.7k
CategoryDevelopment
Updated2d ago
Forks198

Languages

Go

Security Score

95/100

Audited on Apr 3, 2026

No findings