MCPcopy Index your code
hub / github.com/duanhf2012/origin

github.com/duanhf2012/origin @v.19.11

Chat with this repo
repository ↗ · DeepWiki ↗ · release v.19.11 ↗ · + Follow
2,118 symbols 4,951 edges 111 files 266 documented · 13%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

origin 游戏服务器引擎简介

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

origin 解决的问题:

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

Hello world!

下面我们来一步步的建立origin服务器,先下载origin引擎,或者使用如下命令:

go get -v -u  github.com/duanhf2012/origin

于是下载到GOPATH环境目录中,在src中加入main.go,内容如下:

package main

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

func main() {
    node.Start()
}

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

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

go get -v -u github.com/duanhf2012/originserver

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

origin引擎三大对象关系

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

origin集群核心配置文件在config的cluster目录下,如github.com/duanhf2012/originserver的config/cluster目录下有cluster.json与service.json配置:

cluster.json如下:

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

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

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

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


{
"Global": {
        "AreaId": 1
    },
  "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:包最大长度

第一章:origin基础:

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

simple_service/TestService1.go如下:

package simple_service

import (
    "github.com/duanhf2012/origin/node"
    "github.com/duanhf2012/origin/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/node"
    "github.com/duanhf2012/origin/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/node"
    //导入simple_service模块
    _"orginserver/simple_service"
)

func main(){
    node.Start()
}

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

编译后运行结果如下:

#originserver -start 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.Printf("TestService1 OnInit.\n")
    //打开性能分析工具
    slf.OpenProfiler()
    //监控超过1秒的慢处理
    slf.GetProfiler().SetOverTime(time.Second*1)
    //监控超过10秒的超慢处理,您可以用它来定位是否存在死循环
    //比如以下设置10秒,我的应用中是不会发生超过10秒的一次函数调用
    //所以设置为10秒。
    slf.GetProfiler().SetMaxOverTime(time.Second*10)

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

func (slf *TestService1) Loop(){
    for {
        time.Sleep(time.Second*1)
    }
}


func main(){
    //打开性能分析报告功能,并设置10秒汇报一次
    node.OpenProfilerReport(time.Second*10)
    node.Start()
}

上面通过GetProfiler().SetOverTime与slf.GetProfiler().SetMaxOverTimer设置监控时间 并在main.go中,打开了性能报告器,以每10秒汇报一次,因为上面的例子中,定时器是有死循环,所以可以得到以下报告:

2020/04/22 17:53:30 profiler.go:179: [release] Profiler report tag TestService1: process count 0,take time 0 Milliseconds,average 0 Milliseconds/per. too slow process:Timer_orginserver/simple_service.(*TestService1).Loop-fm is take 38003 Milliseconds 直接帮助找到TestService1服务中的Loop函数

结点连接和断开事件监听:

在有些业务中需要关注某结点是否断开连接,可以注册回调如下:

func (ts *TestService) OnInit() error{
    ts.RegRpcListener(ts)

    return nil
}

func (ts *TestService) OnNodeConnected(nodeId int){
}

func (ts *TestService) OnNodeDisconnect(nodeId int){
}

第三章:Module使用:

Module创建与销毁:

可以认为Service就是一种Module,它有Module所有的功能。在示例代码中可以参考originserver/simple_module/TestService3.go。

package simple_module

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

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

type TestService3 struct {
    service.Service
}

type Module1 struct {
    service.Module
}

type Module2 struct {
    service.Module
}

func (slf *Module1) OnInit()error{
    fmt.Printf("Module1 OnInit.\n")
    return nil
}

func (slf *Module1) OnRelease(){
    fmt.Printf("Module1 Release.\n")
}

func (slf *Module2) OnInit()error{
    fmt.Printf("Module2 OnInit.\n")
    return nil
}

func (slf *Module2) OnRelease(){
    fmt.Printf("Module2 Release.\n")
}


func (slf *TestService3) OnInit() error {
    //新建两个Module对象
    module1 := &Module1{}
    module2 := &Module2{}
    //将module1添加到服务中
    module1Id,_ := slf.AddModule(module1)
    //在module1中添加module2模块
    module1.AddModule(module2)
    fmt.Printf("module1 id is %d, module2 id is %d",module1Id,module2.GetModuleId())

    //释放模块module1
    slf.ReleaseModule(module1Id)
    fmt.Printf("xxxxxxxxxxx")
    return nil
}

在OnInit中创建了一条线型的模块关系TestService3->module1->module2,调用AddModule后会返回Module的Id,自动生成的Id从10e17开始,内部的id,您可以自己设置Id。当调用ReleaseModule释放时module1时,同样会将module2释放。会自动调用OnRelease函数,日志顺序如下:

Module1 OnInit.
Module2 OnInit.
module1 id is 100000000000000001, module2 id is 100000000000000002
Module2 Release.
Module1 Release.

在Module中同样可以使用定时器功能,请参照第二章节的定时器部分。

第四章:事件使用

事件是origin中一个重要的组成部分,可以在同一个node中的service与service或者与module之间进行事件通知。系统内置的几个服务,如:TcpService/HttpService等都是通过事件功能实现。他也是一个典型的观察者设计模型。在event中有两个类型的interface,一个是event.IEventProcessor它提供注册与卸载功能,另一个是event.IEventHandler提供消息广播等功能。

在目录simple_event/TestService4.go中

package simple_event

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

const (
    //自定义事件类型,必需从event.Sys_Event_User_Define开始
    //event.Sys_Event_User_Define以内给系统预留
    EVENT1 event.EventType =event.Sys_Event_User_Define+1
)

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

type TestService4 struct {
    service.Service
}

func (slf *TestService4) OnInit() error {
    //10秒后触发广播事件
    slf.AfterFunc(time.Second*10,slf.TriggerEvent)
    return nil
}

func (slf *TestService4) TriggerEvent(){
    //广播事件,传入event.Event对象,类型为EVENT1,Data可以自定义任何数据
    //这样,所有监听者都可以收到该事件
    slf.GetEventHandler().NotifyEvent(&event.Event{
        Type: EVENT1,
        Data: "event data.",
    })
}


在目录simple_event/TestService5.go中

package simple_event

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

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

type TestService5 struct {
    service.Service
}

type TestModule struct {
    service.Module
}

func (slf *TestModule) OnInit() error{
    //在当前node中查找TestService4
    pService := node.GetService("TestService4")

    //在TestModule中,往TestService4中注册EVENT1类型事件监听
    pService.(*TestService4).GetEventProcessor().RegEventReciverFunc(EVENT1,slf.GetEventHandler(),slf.OnModuleEvent)
    return nil
}

func (slf *TestModule) OnModuleEvent(ev event.IEvent){
    event := ev.(*event.Event)
    fmt.Printf("OnModuleEvent type :%d data:%+v\n",event.GetEventType(),event.Data)
}


//服务初始化函数,在安装服务时,服务将自动调用OnInit函数
func (slf *TestService5) OnInit() error {
    //通过服务名获取服务对象
    pService := node.GetService("TestService4")

    ////在TestModule中,往TestService4中注册EVENT1类型事件监听
    pService.(*TestService4).GetEventProcessor().RegEventReciverFunc(EVENT1,slf.GetEventHandler(),slf.OnServiceEvent)
    slf.AddModule(&TestModule{})
    return nil
}

func (slf *TestService5) OnServiceEvent(ev event.IEvent){
    event := ev.(*event.Event)
    fmt.Printf("OnServiceEvent type :%d data:%+v\n",event.Type,event.Data)
}


程序运行10秒后,调用slf.TriggerEvent函数广播事件,于是在TestService5中会收到

OnServiceEvent type :1001 data:event data.
OnModuleEvent type :1001 data:event data.

在上面的TestModule中监听的事情,当这个Module被Release时监听会自动卸载。

第五章:RPC使用

RPC是service与service间通信的重要方式,它允许跨进程node互相访问,当然也可以指定nodeid进行调用。如下示例:

simple_rpc/TestService6.go文件如下:

package simple_rpc

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

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

type TestService6 struct {
    service.Service
}

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

type InputData struct {
    A int
    B int
}

// 注意RPC函数名的格式必需为RPC_FunctionName或者是RPCFunctionName,如下的RPC_Sum也可以写成RPCSum
func (slf *TestService6) RPC_Sum(input *InputData,output *int) error{
    *output = input.A+input.B
    return nil
}

simple_rpc/TestService7.go文件如下:

``` package simple_rpc

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

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

type TestService7 struct { service.Service }

func (slf TestService7) OnInit() error { slf.AfterFunc(time.Second2,slf.CallTest) slf.AfterFunc(time.Second2,slf.AsyncCallTest) slf.AfterFunc(time.Second2,slf.GoTest) return nil }

func (slf *TestService7) CallTest(){ var input InputData input.A = 300 input.B = 600 var output int

//同步调用其他服务的rpc,input为传入的rpc,output为输出参数
err := slf.Call("TestService6.RPC_Sum",&input,&output)
if err != nil {
    fmt.Printf("Call error :%+v\n",err)
}else{
    fmt.Printf("Call output %d\n",output)
}


//自定义超时,默认rpc超时时间为15s
err = slf.CallWithTimeout(time.Second*1, "TestService6.RPC_Sum", &input, &output

Extension points exported contracts — how you extend this code

IPoolData (Interface)
(no doc) [8 implementers]
util/sync/MemPool.go
Agent (Interface)
(no doc) [5 implementers]
network/agent.go
Comparator (Interface)
Comparator is a generic interface that represents items that can be compared. [2 implementers]
util/algorithms/skip/interface.go
IProcessor (Interface)
(no doc) [3 implementers]
network/processor/processor.go
ITimer (Interface)
ITimer [1 implementers]
util/timer/timer.go
IEvent (Interface)
(no doc) [3 implementers]
event/event.go
INodeListener (Interface)
(no doc) [2 implementers]
rpc/rpchandler.go
IRpcRequestData (Interface)
(no doc) [2 implementers]
rpc/rpc.go

Core symbols most depended-on inside this repo

SError
called by 114
log/log.go
Unlock
called by 114
rpc/lclient.go
Error
called by 106
log/log.go
Lock
called by 99
rpc/lclient.go
Close
called by 79
network/conn.go
Error
called by 54
log/log.go
AppendByte
called by 52
log/buffer.go
Marshal
called by 52
rpc/processor.go

Shape

Method 1,606
Function 248
Struct 171
Interface 41
FuncType 29
TypeAlias 23

Languages

Go100%

Modules by API surface

rpc/rank.pb.go332 symbols
rpc/messagequeue.pb.go118 symbols
rpc/dynamicdiscover.pb.go76 symbols
rpc/rpchandler.go73 symbols
sysmodule/redismodule/redismodule.go66 symbols
sysservice/httpservice/httpservice.go57 symbols
service/service.go55 symbols
util/timer/timer.go52 symbols
service/module.go48 symbols
event/event.go47 symbols
rpc/gogorpc.pb.go43 symbols
cluster/cluster.go40 symbols

Datastores touched

testdbCollection · 1 repos
(mongodb)Database · 1 repos

For agents

$ claude mcp add origin \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact