├── gate_server ├── gateserver.go └── test_gate_rpc.go ├── conf ├── xingo_cluster_架构.png ├── server.json ├── clusterconf_测试网关有root和http.json └── clusterconf.json ├── .gitignore ├── admin_server ├── test_admin_http.go └── test_admin_rpc.go ├── master.go ├── net_server ├── test_net_http.go ├── test_net_rpc.go ├── core │ ├── player.go │ └── playermgr.go ├── netserver.go ├── test_net_api.go └── test_net_api_测试网关有root和http.go ├── test.go ├── LICENSE ├── test_log.go ├── game_server └── test_game_rpc.go ├── server.go ├── README.md ├── client_test1.go └── pb └── msg.pb.go /gate_server/gateserver.go: -------------------------------------------------------------------------------- 1 | package gate_server 2 | -------------------------------------------------------------------------------- /conf/xingo_cluster_架构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viphxin/xingo_cluster/HEAD/conf/xingo_cluster_架构.png -------------------------------------------------------------------------------- /conf/server.json: -------------------------------------------------------------------------------- 1 | { 2 | "TcpPort": 8999, 3 | "StepPerMs": 2000, 4 | "PoolSize": 1, 5 | "LogLevel": 2, 6 | "MaxConn": 20000, 7 | "LogPath": "/Users/huangxin/workspace/src/xingo_cluster/log", 8 | "LogName": "cluster.log", 9 | "WriteList": ["127.0.0.1"] 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | .idea 26 | .log 27 | -------------------------------------------------------------------------------- /admin_server/test_admin_http.go: -------------------------------------------------------------------------------- 1 | package admin_server 2 | 3 | import ( 4 | "net/http" 5 | "github.com/viphxin/xingo/logger" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | type TestAdminHttp struct { 11 | 12 | } 13 | 14 | func (this *TestAdminHttp)Hello(w http.ResponseWriter, r *http.Request) { 15 | logger.Info(strings.Repeat("hello", 10)) 16 | time.Sleep(3*time.Second) 17 | w.Write([]byte("hudfasdkasfas")) 18 | } 19 | -------------------------------------------------------------------------------- /master.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "path/filepath" 5 | "github.com/viphxin/xingo" 6 | ) 7 | 8 | func main() { 9 | dir, err := filepath.Abs(filepath.Dir(".")) 10 | if err == nil{ 11 | if true{ 12 | s := xingo.NewXingoMaster(filepath.Join(dir, "conf", "clusterconf.json")) 13 | s.StartMaster() 14 | }else{ 15 | s := xingo.NewXingoMaster(filepath.Join(dir, "conf", "clusterconf_测试网关有root和http.json")) 16 | s.StartMaster() 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /net_server/test_net_http.go: -------------------------------------------------------------------------------- 1 | package net_server 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | "fmt" 7 | ) 8 | 9 | type TestNetHttp struct { 10 | 11 | } 12 | 13 | func (this *TestNetHttp)Hello(w http.ResponseWriter, r *http.Request) { 14 | queryForm, err := url.ParseQuery(r.URL.RawQuery) 15 | if err == nil && len(queryForm["name"]) > 0 { 16 | w.Write([]byte(fmt.Sprintf("hello %s", queryForm["name"][0]))) 17 | }else{ 18 | w.Write([]byte("hello 陌生人")) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /conf/clusterconf_测试网关有root和http.json: -------------------------------------------------------------------------------- 1 | { 2 | "master":{"host": "127.0.0.1","rootport":9999, "debugport":8881, "writelist": ["127.0.0.1"]}, 3 | "servers":{ 4 | "net1":{"host": "127.0.0.1", "netport":11009, "rootport":11109, "http": [11209, "/static"], "debugport":11007, "name":"net1", "module": "net", "log": "net.log"}, 5 | "net2":{"host": "127.0.0.1", "netport":11010, "rootport":11110, "http": [11210, "/static"], "name":"net2", "module": "net", "log": "net.log"}, 6 | "game1":{"host": "127.0.0.1", "remotes":["net1", "net2"], "name":"game1", "module": "game"} 7 | } 8 | } -------------------------------------------------------------------------------- /net_server/test_net_rpc.go: -------------------------------------------------------------------------------- 1 | package net_server 2 | 3 | import ( 4 | "github.com/viphxin/xingo/cluster" 5 | "xingo_cluster/pb" 6 | ) 7 | 8 | type TestNetRpc struct { 9 | 10 | } 11 | 12 | func (this *TestNetRpc)PushMsg2Client(request *cluster.RpcRequest){ 13 | pid := request.Rpcdata.Args[0].(int32) 14 | message := request.Rpcdata.Args[1].(string) 15 | p := GlobalPlayerMgr.GetPlayer(pid) 16 | if p != nil{ 17 | msg := &pb.BroadCast{ 18 | Pid : pid, 19 | Tp: 1, 20 | Data: &pb.BroadCast_Content{ 21 | Content: message, 22 | }, 23 | } 24 | p.SendMsg(1, msg) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /admin_server/test_admin_rpc.go: -------------------------------------------------------------------------------- 1 | package admin_server 2 | 3 | import ( 4 | "github.com/viphxin/xingo/clusterserver" 5 | "github.com/viphxin/xingo/cluster" 6 | "github.com/viphxin/xingo/logger" 7 | ) 8 | 9 | type TestAdminRpc struct { 10 | 11 | } 12 | 13 | func (this *TestAdminRpc)GetGSTime(request *cluster.RpcRequest){ 14 | pid := (request.Rpcdata.Args[0]).(int32) 15 | //转发到gate 16 | onegate := clusterserver.GlobalClusterServer.RemoteNodesMgr.GetRandomChild("gate") 17 | 18 | if onegate != nil{ 19 | logger.Debug("chose root: " + onegate.GetName()) 20 | onegate.CallChildNotForResult("Proxy2Game", pid, "2222.3333") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /net_server/core/player.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "github.com/golang/protobuf/proto" 5 | "github.com/viphxin/xingo/iface" 6 | "github.com/viphxin/xingo/logger" 7 | "github.com/viphxin/xingo/utils" 8 | ) 9 | 10 | type Player struct { 11 | Pid int32 12 | Fconn iface.Iconnection 13 | } 14 | 15 | func NewPlayer(pid int32, fconn iface.Iconnection) *Player { 16 | return &Player{ 17 | Pid: pid, 18 | Fconn: fconn, 19 | } 20 | } 21 | 22 | func (this *Player) SendMsg(msgId uint32, data proto.Message) { 23 | if this.Fconn != nil { 24 | packdata, err := utils.GlobalObject.Protoc.GetDataPack().Pack(msgId, data) 25 | if err == nil { 26 | this.Fconn.Send(packdata) 27 | } else { 28 | logger.Error("pack data error") 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /net_server/netserver.go: -------------------------------------------------------------------------------- 1 | package net_server 2 | 3 | import ( 4 | "github.com/viphxin/xingo/iface" 5 | "github.com/viphxin/xingo/logger" 6 | "github.com/viphxin/xingo/utils" 7 | "xingo_cluster/net_server/core" 8 | ) 9 | 10 | func DoConnectionMade(fconn iface.Iconnection) { 11 | logger.Debug("111111111111111111111111") 12 | p := GlobalPlayerMgr.Add(fconn) 13 | fconn.SetProperty("pid", p.Pid) 14 | } 15 | 16 | func DoConnectionLost(fconn iface.Iconnection) { 17 | logger.Debug("222222222222222222222222") 18 | pid, err := fconn.GetProperty("pid") 19 | if err == nil { 20 | GlobalPlayerMgr.Remove(pid.(int32)) 21 | } 22 | 23 | } 24 | 25 | var GlobalPlayerMgr *core.PlayerMgr = core.NewPlayerMgr() 26 | 27 | func init() { 28 | utils.GlobalObject.OnConnectioned = DoConnectionMade 29 | utils.GlobalObject.OnClosed = DoConnectionLost 30 | } 31 | -------------------------------------------------------------------------------- /net_server/core/playermgr.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "sync" 5 | "github.com/viphxin/xingo/iface" 6 | ) 7 | 8 | type PlayerMgr struct { 9 | sync.RWMutex 10 | players map[int32]*Player 11 | } 12 | 13 | func NewPlayerMgr() *PlayerMgr{ 14 | return &PlayerMgr{ 15 | players: make(map[int32]*Player, 0), 16 | } 17 | } 18 | 19 | func (this *PlayerMgr)Add(fconn iface.Iconnection) *Player{ 20 | this.Lock() 21 | defer this.Unlock() 22 | 23 | p := NewPlayer(int32(fconn.GetSessionId()), fconn) 24 | this.players[p.Pid] = p 25 | return p 26 | } 27 | 28 | func (this *PlayerMgr)Remove(pid int32){ 29 | this.Lock() 30 | defer this.Unlock() 31 | 32 | delete(this.players, pid) 33 | } 34 | 35 | func (this *PlayerMgr)GetPlayer(pid int32) *Player{ 36 | this.RLock() 37 | defer this.RUnlock() 38 | 39 | p, ok := this.players[pid] 40 | if ok{ 41 | return p 42 | }else{ 43 | return nil 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /conf/clusterconf.json: -------------------------------------------------------------------------------- 1 | { 2 | "master":{"host": "127.0.0.1","rootport":9999, "debugport":8881}, 3 | "servers":{ 4 | "gate1":{"host": "127.0.0.1", "rootport":10000,"name":"gate1", "module": "gate", "log": "gate.log"}, 5 | "gate2":{"host": "127.0.0.1", "rootport":10001,"name":"gate2", "module": "gate", "log": "gate.log"}, 6 | "net1":{"host": "127.0.0.1", "netport":11009,"debugport":11007,"name":"net1","remotes":["gate1", "gate2"], "module": "net", "log": "net.log"}, 7 | "net2":{"host": "127.0.0.1", "netport":11010,"name":"net2","remotes":["gate1", "gate2"], "module": "net", "log": "net.log"}, 8 | "net3":{"host": "127.0.0.1", "netport":11011,"name":"net3","remotes":["gate1", "gate2"], "module": "net", "log": "net.log"}, 9 | "net4":{"host": "127.0.0.1", "netport":11012,"name":"net4","remotes":["gate1", "gate2"], "module": "net", "log": "net.log"}, 10 | "admin1":{"host": "127.0.0.1", "remotes":["gate2", "gate1"], "name":"admin1", "module": "admin", "http": [8888, "/static"]}, 11 | "game1":{"host": "127.0.0.1", "remotes":["gate2", "gate1"], "name":"game1", "module": "game"} 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "reflect" 5 | "github.com/viphxin/xingo/cluster" 6 | "encoding/json" 7 | "fmt" 8 | "runtime/debug" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | type RpcData struct { 14 | MsgType cluster.RpcSignal `json:"msgtype"` 15 | Key string `json:"key,omitempty"` 16 | Target string `json:"target,omitempty"` 17 | Args []interface{} `json:"args,omitempty"` 18 | Result []reflect.Value `json:"result,omitempty"` 19 | } 20 | 21 | a := &RpcData{ 22 | MsgType: cluster.REQUEST_NORESULT, 23 | Key: "dasdasdsa", 24 | } 25 | bbb, err := json.Marshal(a) 26 | if err != nil{ 27 | fmt.Println("error") 28 | fmt.Println(err) 29 | }else{ 30 | aa := &RpcData{} 31 | err = json.Unmarshal(bbb, aa) 32 | if err != nil{ 33 | println(err) 34 | }else{ 35 | println(aa.Key, aa.MsgType) 36 | } 37 | } 38 | defer func() { 39 | if err := recover(); err != nil { 40 | debug.PrintStack() 41 | println("panic recover!!!") 42 | } 43 | }() 44 | for { 45 | println("sdasdsadas1111111") 46 | panic("huangxinsss") 47 | time.Sleep(3*time.Second) 48 | } 49 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 viphxin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test_log.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | "strconv" 6 | "github.com/viphxin/xingo/logger" 7 | "path/filepath" 8 | ) 9 | 10 | func runlog(i int) { 11 | logger.Debug("Debug>>>>>>>>>>>>>>>>>>>>>>" + strconv.Itoa(i)) 12 | logger.Info("Info>>>>>>>>>>>>>>>>>>>>>>>>>" + strconv.Itoa(i)) 13 | logger.Warn("Warn>>>>>>>>>>>>>>>>>>>>>>>>>" + strconv.Itoa(i)) 14 | logger.Error("Error>>>>>>>>>>>>>>>>>>>>>>>>>" + strconv.Itoa(i)) 15 | logger.Fatal("Fatal>>>>>>>>>>>>>>>>>>>>>>>>>" + strconv.Itoa(i)) 16 | } 17 | 18 | func main() { 19 | runtime.GOMAXPROCS(runtime.NumCPU()) 20 | 21 | //指定是否控制台打印,默认为true 22 | logger.SetConsole(true) 23 | //指定日志文件备份方式为文件大小的方式 24 | //第一个参数为日志文件存放目录 25 | //第二个参数为日志文件命名 26 | //第三个参数为备份文件最大数量 27 | //第四个参数为备份文件大小 28 | //第五个参数为文件大小的单位 29 | //logger.SetRollingFile("d:/logtest", "test.log", 10, 5, logger.KB) 30 | 31 | //指定日志文件备份方式为日期的方式 32 | //第一个参数为日志文件存放目录 33 | //第二个参数为日志文件命名 34 | p, _ := filepath.Abs("./log") 35 | print(p) 36 | //logger.SetRollingDaily(filepath.Dir("./log"), "test111.log") 37 | 38 | //指定日志级别 ALL,DEBUG,INFO,WARN,ERROR,FATAL,OFF 级别由低到高 39 | //一般习惯是测试阶段为debug,生成环境为info以上 40 | //logger.SetLevel(logger.ERROR) 41 | // 42 | //for i := 100; i > 0; i-- { 43 | // go runlog(i) 44 | // time.Sleep(1000 * time.Millisecond) 45 | //} 46 | //time.Sleep(15 * time.Second) 47 | } -------------------------------------------------------------------------------- /game_server/test_game_rpc.go: -------------------------------------------------------------------------------- 1 | package game_server 2 | 3 | import ( 4 | "github.com/viphxin/xingo/cluster" 5 | "github.com/viphxin/xingo/logger" 6 | _"github.com/viphxin/xingo/clusterserver" 7 | "github.com/viphxin/xingo/clusterserver" 8 | "fmt" 9 | "strings" 10 | ) 11 | 12 | type TestGameRpc struct { 13 | 14 | } 15 | 16 | func (this *TestGameRpc)Proxy2Game(request *cluster.RpcRequest){ 17 | //Json反序列化数字到interface{}类型的值中,默认解析为float64类型,在使用时要注意 18 | pid := int32((request.Rpcdata.Args[0]).(float64)) 19 | message := (request.Rpcdata.Args[1]).(string) 20 | logger.Debug(pid, message) 21 | //onenet := clusterserver.GlobalClusterServer.ChildsMgr.GetRandomChild("net") 22 | //if onenet != nil{ 23 | // onenet.CallChildNotForResult("PushMsg2Client", pid, message) 24 | //} 25 | for _, child := range clusterserver.GlobalClusterServer.RemoteNodesMgr.GetChilds(){ 26 | if strings.Contains(child.GetName(), "net"){ 27 | child.CallChildNotForResult("PushMsg2Client", pid, message) 28 | } 29 | } 30 | } 31 | 32 | func (this *TestGameRpc)Add(request *cluster.RpcRequest) map[string]interface{}{ 33 | //Json反序列化数字到interface{}类型的值中,默认解析为float64类型,在使用时要注意 34 | i := int32((request.Rpcdata.Args[0]).(float64)) 35 | ii := int32((request.Rpcdata.Args[1]).(float64)) 36 | 37 | logger.Debug(fmt.Sprintf("%d + %d = %d", i, ii, i + ii)) 38 | return map[string]interface{}{ 39 | "sum": i + ii, 40 | } 41 | } -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "xingo_cluster/net_server" 7 | "xingo_cluster/gate_server" 8 | "xingo_cluster/admin_server" 9 | _ "net/http" 10 | _ "net/http/pprof" 11 | "github.com/viphxin/xingo" 12 | "xingo_cluster/game_server" 13 | ) 14 | 15 | func main() { 16 | //pprof 17 | //go func() { 18 | // println(http.ListenAndServe("localhost:6060", nil)) 19 | //}() 20 | 21 | //server code 22 | args := os.Args 23 | dir, err := filepath.Abs(filepath.Dir(".")) 24 | if err == nil{ 25 | if true{ 26 | s := xingo.NewXingoCluterServer(args[1], filepath.Join(dir, "conf", "clusterconf.json")) 27 | /* 28 | 注册分布式服务器 29 | */ 30 | //net server 31 | s.AddModule("net", &net_server.TestNetApi{}, nil, &net_server.TestNetRpc{}) 32 | //gate server 33 | s.AddModule("gate", nil, nil, &gate_server.TestGateRpc{}) 34 | //admin server 35 | s.AddModule("admin", nil, &admin_server.TestAdminHttp{}, &admin_server.TestAdminRpc{}) 36 | 37 | s.StartClusterServer() 38 | }else{ 39 | s := xingo.NewXingoCluterServer(args[1], filepath.Join(dir, "conf", "clusterconf_测试网关有root和http.json")) 40 | /* 41 | 注册分布式服务器 42 | */ 43 | //net server 44 | s.AddModule("net", &net_server.TestNetApi2{}, &net_server.TestNetHttp{}, &net_server.TestNetRpc{}) 45 | //game server 46 | s.AddModule("game", nil, nil, &game_server.TestGameRpc{}) 47 | 48 | s.StartClusterServer() 49 | } 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /gate_server/test_gate_rpc.go: -------------------------------------------------------------------------------- 1 | package gate_server 2 | 3 | import ( 4 | "github.com/viphxin/xingo/cluster" 5 | "github.com/viphxin/xingo/logger" 6 | _"github.com/viphxin/xingo/clusterserver" 7 | "github.com/viphxin/xingo/clusterserver" 8 | "fmt" 9 | "strings" 10 | ) 11 | 12 | type TestGateRpc struct { 13 | 14 | } 15 | 16 | func (this *TestGateRpc)Proxy2Game(request *cluster.RpcRequest){ 17 | //Json反序列化数字到interface{}类型的值中,默认解析为float64类型,在使用时要注意 18 | pid := (request.Rpcdata.Args[0]).(int32) 19 | message := (request.Rpcdata.Args[1]).(string) 20 | logger.Debug(pid, message) 21 | //onenet := clusterserver.GlobalClusterServer.ChildsMgr.GetRandomChild("net") 22 | //if onenet != nil{ 23 | // onenet.CallChildNotForResult("PushMsg2Client", pid, message) 24 | //} 25 | for _, child := range clusterserver.GlobalClusterServer.ChildsMgr.GetChilds(){ 26 | if strings.Contains(child.GetName(), "net"){ 27 | child.CallChildNotForResult("PushMsg2Client", pid, message) 28 | } 29 | } 30 | } 31 | 32 | func (this *TestGateRpc)Add(request *cluster.RpcRequest) map[string]interface{}{ 33 | //Json反序列化数字到interface{}类型的值中,默认解析为float64类型,在使用时要注意 34 | i := (request.Rpcdata.Args[0]).(int) 35 | ii := (request.Rpcdata.Args[1]).(int) 36 | 37 | logger.Debug(fmt.Sprintf("%d + %d = %d", i, ii, i + ii)) 38 | return map[string]interface{}{ 39 | "sum": i + ii, 40 | } 41 | } 42 | 43 | func (this *TestGateRpc)GetGSTime(request *cluster.RpcRequest){ 44 | //Json反序列化数字到interface{}类型的值中,默认解析为float64类型,在使用时要注意 45 | pid := (request.Rpcdata.Args[0]).(int32) 46 | onenet := clusterserver.GlobalClusterServer.ChildsMgr.GetRandomChild("admin") 47 | logger.Debug(onenet) 48 | if onenet != nil{ 49 | onenet.CallChildNotForResult("GetGSTime", pid) 50 | } 51 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xingo_cluster 2 | xingo地址:(https://git.oschina.net/viphxin/xingo)
3 | xingo cluster 分布式集群 示例代码
4 | ```json 5 | 启动服务器集群: 6 | go run master.go 7 | go run server.go gate1 8 | go run server.go gate2 9 | go run server.go net1 10 | go run server.go net2 11 | go run server.go net3 12 | go run server.go net4 13 | go run server.go admin 14 | 15 | 启动测试脚本: 16 | go run client_test1.go 17 | ``` 18 | 示例配置:
19 | ```json 20 | { 21 | "master":{"host": "192.168.2.225","rootport":9999}, 22 | "servers":{ 23 | "gate2":{"host": "192.168.2.225", "rootport":10000,"name":"gate2", "module": "gate", "log": "gate2.log"}, 24 | "gate1":{"host": "192.168.2.225", "rootport":10001,"name":"gate1", "module": "gate", "log": "gate1.log"}, 25 | "net1":{"host": "192.168.2.225", "netport":11009,"name":"net1","remotes":["gate2", "gate1"], 26 | "module": "net", "log": "net.log"}, 27 | "net2":{"host": "192.168.2.225", "netport":11010,"name":"net2","remotes":["gate2", "gate1"], 28 | "module": "net", "log": "net.log"}, 29 | "net3":{"host": "192.168.2.225", "netport":11011,"name":"net3","remotes":["gate2", "gate1"], 30 | "module": "net", "log": "net.log"}, 31 | "net4":{"host": "192.168.2.225", "netport":11012,"name":"net4","remotes":["gate2", "gate1"], 32 | "module": "net", "log": "net.log"}, 33 | "admin":{"host": "192.168.2.225", "remotes":["gate2", "gate1"], "name":"admin", "module": "admin", 34 | "http": [8888, "/static"]}, 35 | "game1":{"host": "192.168.2.225", "remotes":["gate2", "gate1"], "name":"game1", "module": "game"} 36 | } 37 | } 38 | ``` 39 | 架构图:
40 | ![alt text](conf/xingo_cluster_架构.png) 41 | -------------------------------------------------------------------------------- /net_server/test_net_api.go: -------------------------------------------------------------------------------- 1 | package net_server 2 | 3 | import ( 4 | "github.com/viphxin/xingo/fnet" 5 | "github.com/golang/protobuf/proto" 6 | "fmt" 7 | "xingo_cluster/pb" 8 | "github.com/viphxin/xingo/logger" 9 | "github.com/viphxin/xingo/clusterserver" 10 | "math/rand" 11 | ) 12 | 13 | type TestNetApi struct { 14 | 15 | } 16 | 17 | func (this *TestNetApi)Api_0(request *fnet.PkgAll){ 18 | msg := &pb.Talk{} 19 | err := proto.Unmarshal(request.Pdata.Data, msg) 20 | if err == nil { 21 | logger.Debug(fmt.Sprintf("user talk: content: %s.", msg.Content)) 22 | pid, err1 := request.Fconn.GetProperty("pid") 23 | if err1 == nil{ 24 | //转发到gate 25 | onegate := clusterserver.GlobalClusterServer.RemoteNodesMgr.GetRandomChild("gate") 26 | 27 | if onegate != nil{ 28 | logger.Debug("chose root: " + onegate.GetName()) 29 | onegate.CallChildNotForResult("Proxy2Game", pid.(int32), msg.Content) 30 | } 31 | }else{ 32 | logger.Error(err1) 33 | request.Fconn.LostConnection() 34 | } 35 | 36 | } else { 37 | logger.Error(err) 38 | request.Fconn.LostConnection() 39 | } 40 | } 41 | 42 | func (this *TestNetApi)Api_10(request *fnet.PkgAll){ 43 | //test rpc for result 44 | //转发到gate 45 | onegate := clusterserver.GlobalClusterServer.RemoteNodesMgr.GetRandomChild("gate") 46 | 47 | if onegate != nil{ 48 | logger.Debug("chose root: " + onegate.GetName()) 49 | i := rand.Intn(10) 50 | ii := rand.Intn(10) 51 | response, err := onegate.CallChildForResult("Add", i, ii) 52 | if err == nil{ 53 | pid, _ := request.Fconn.GetProperty("pid") 54 | p := GlobalPlayerMgr.GetPlayer(pid.(int32)) 55 | if p != nil{ 56 | msg := &pb.BroadCast{ 57 | Pid : pid.(int32), 58 | Tp: 1, 59 | Data: &pb.BroadCast_Content{ 60 | Content: fmt.Sprintf("%d + %d = %d", i, ii, response.Result["sum"].(int)), 61 | }, 62 | } 63 | p.SendMsg(10, msg) 64 | } 65 | }else{ 66 | logger.Error(err) 67 | } 68 | } 69 | } 70 | 71 | func (this *TestNetApi)Api_11(request *fnet.PkgAll){ 72 | //test rpc for result 73 | //转发到gate 74 | onegate := clusterserver.GlobalClusterServer.RemoteNodesMgr.GetRandomChild("gate") 75 | 76 | if onegate != nil{ 77 | pid, _ := request.Fconn.GetProperty("pid") 78 | onegate.CallChildNotForResult("GetGSTime", pid) 79 | 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /net_server/test_net_api_测试网关有root和http.go: -------------------------------------------------------------------------------- 1 | package net_server 2 | 3 | import ( 4 | "github.com/viphxin/xingo/fnet" 5 | "github.com/golang/protobuf/proto" 6 | "fmt" 7 | "xingo_cluster/pb" 8 | "github.com/viphxin/xingo/logger" 9 | "github.com/viphxin/xingo/clusterserver" 10 | "math/rand" 11 | ) 12 | 13 | type TestNetApi2 struct { 14 | 15 | } 16 | 17 | func (this *TestNetApi2)Api_0(request *fnet.PkgAll){ 18 | msg := &pb.Talk{} 19 | err := proto.Unmarshal(request.Pdata.Data, msg) 20 | if err == nil { 21 | logger.Debug(fmt.Sprintf("user talk: content: %s.", msg.Content)) 22 | pid, err1 := request.Fconn.GetProperty("pid") 23 | if err1 == nil{ 24 | //转发到game 25 | onegame := clusterserver.GlobalClusterServer.ChildsMgr.GetRandomChild("game") 26 | 27 | if onegame != nil{ 28 | logger.Debug("chose root: " + onegame.GetName()) 29 | onegame.CallChildNotForResult("Proxy2Game", pid.(int32), msg.Content) 30 | } 31 | }else{ 32 | logger.Error(err1) 33 | request.Fconn.LostConnection() 34 | } 35 | 36 | } else { 37 | logger.Error(err) 38 | request.Fconn.LostConnection() 39 | } 40 | } 41 | 42 | func (this *TestNetApi2)Api_10(request *fnet.PkgAll){ 43 | //test rpc for result 44 | //转发到gate 45 | onegame := clusterserver.GlobalClusterServer.ChildsMgr.GetRandomChild("game") 46 | 47 | if onegame != nil{ 48 | logger.Debug("chose child: " + onegame.GetName()) 49 | i := rand.Intn(10) 50 | ii := rand.Intn(10) 51 | response, err := onegame.CallChildForResult("Add", i, ii) 52 | if err == nil{ 53 | pid, _ := request.Fconn.GetProperty("pid") 54 | p := GlobalPlayerMgr.GetPlayer(pid.(int32)) 55 | if p != nil{ 56 | msg := &pb.BroadCast{ 57 | Pid : pid.(int32), 58 | Tp: 1, 59 | Data: &pb.BroadCast_Content{ 60 | Content: fmt.Sprintf("%d + %d = %d", i, ii, response.Result["sum"].(int)), 61 | }, 62 | } 63 | p.SendMsg(10, msg) 64 | } 65 | }else{ 66 | logger.Error(err) 67 | } 68 | } 69 | } 70 | 71 | func (this *TestNetApi2)Api_11(request *fnet.PkgAll){ 72 | //test rpc for result 73 | //转发到gate 74 | onegame := clusterserver.GlobalClusterServer.ChildsMgr.GetRandomChild("game") 75 | 76 | if onegame != nil{ 77 | pid, _ := request.Fconn.GetProperty("pid") 78 | onegame.CallChildNotForResult("GetGSTime", pid) 79 | 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /client_test1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "github.com/golang/protobuf/proto" 8 | "github.com/viphxin/xingo/fnet" 9 | "github.com/viphxin/xingo/iface" 10 | "io" 11 | "math/rand" 12 | "os" 13 | "os/signal" 14 | "time" 15 | "xingo_demo/pb" 16 | ) 17 | 18 | type PkgData struct { 19 | Len uint32 20 | MsgId uint32 21 | Data []byte 22 | } 23 | 24 | type MyPtotoc struct { 25 | Pid int32 26 | } 27 | 28 | func (this *MyPtotoc) OnConnectionMade(fconn iface.Iclient) { 29 | fmt.Println("链接建立") 30 | go func() { 31 | for { 32 | msg := &pb.Talk{ 33 | Content: "哈哈哈哈哈哈哈哈哈哈sdadasdasfas 萨达萨达撒发送的发大声大声", 34 | } 35 | this.Send(fconn, 0, msg) 36 | this.Send(fconn, 10, nil) 37 | this.Send(fconn, 11, nil) 38 | time.Sleep(2000 * time.Millisecond) 39 | } 40 | }() 41 | } 42 | 43 | func (this *MyPtotoc) OnConnectionLost(fconn iface.Iclient) { 44 | fmt.Println("链接丢失") 45 | } 46 | 47 | func (this *MyPtotoc) Unpack(headdata []byte) (head *PkgData, err error) { 48 | headbuf := bytes.NewReader(headdata) 49 | 50 | head = &PkgData{} 51 | 52 | // 读取Len 53 | if err = binary.Read(headbuf, binary.LittleEndian, &head.Len); err != nil { 54 | return nil, err 55 | } 56 | 57 | // 读取MsgId 58 | if err = binary.Read(headbuf, binary.LittleEndian, &head.MsgId); err != nil { 59 | return nil, err 60 | } 61 | 62 | // 封包太大 63 | //if head.Len > MaxPacketSize { 64 | // return nil, packageTooBig 65 | //} 66 | 67 | return head, nil 68 | } 69 | 70 | func (this *MyPtotoc) Pack(msgId uint32, data proto.Message) (out []byte, err error) { 71 | outbuff := bytes.NewBuffer([]byte{}) 72 | // 进行编码 73 | dataBytes := []byte{} 74 | if data != nil { 75 | dataBytes, err = proto.Marshal(data) 76 | } 77 | 78 | if err != nil { 79 | fmt.Println(fmt.Sprintf("marshaling error: %s", err)) 80 | } 81 | // 写Len 82 | if err = binary.Write(outbuff, binary.LittleEndian, uint32(len(dataBytes))); err != nil { 83 | return 84 | } 85 | // 写MsgId 86 | if err = binary.Write(outbuff, binary.LittleEndian, msgId); err != nil { 87 | return 88 | } 89 | 90 | //all pkg data 91 | if err = binary.Write(outbuff, binary.LittleEndian, dataBytes); err != nil { 92 | return 93 | } 94 | 95 | out = outbuff.Bytes() 96 | return 97 | 98 | } 99 | 100 | func (this *MyPtotoc) DoMsg(fconn iface.Iclient, pdata *PkgData) { 101 | //处理消息 102 | fmt.Println(fmt.Sprintf("msg id :%d, data len: %d", pdata.MsgId, pdata.Len)) 103 | if pdata.MsgId == 1 || pdata.MsgId == 10 { 104 | bdata := &pb.BroadCast{} 105 | proto.Unmarshal(pdata.Data, bdata) 106 | println(bdata.GetContent()) 107 | } 108 | } 109 | 110 | func (this *MyPtotoc) Send(fconn iface.Iclient, msgID uint32, data proto.Message) { 111 | dd, err := this.Pack(msgID, data) 112 | if err == nil { 113 | fconn.Send(dd) 114 | } else { 115 | fmt.Println(err) 116 | } 117 | 118 | } 119 | 120 | func (this *MyPtotoc) StartReadThread(fconn iface.Iclient) { 121 | go func() { 122 | for { 123 | //read per head data 124 | headdata := make([]byte, 8) 125 | 126 | if _, err := io.ReadFull(fconn.GetConnection(), headdata); err != nil { 127 | fmt.Println(err) 128 | this.OnConnectionLost(fconn) 129 | return 130 | } 131 | pkgHead, err := this.Unpack(headdata) 132 | if err != nil { 133 | this.OnConnectionLost(fconn) 134 | return 135 | } 136 | //data 137 | if pkgHead.Len > 0 { 138 | pkgHead.Data = make([]byte, pkgHead.Len) 139 | if _, err := io.ReadFull(fconn.GetConnection(), pkgHead.Data); err != nil { 140 | this.OnConnectionLost(fconn) 141 | return 142 | } 143 | } 144 | this.DoMsg(fconn, pkgHead) 145 | } 146 | }() 147 | } 148 | 149 | func (this *MyPtotoc) AddRpcRouter(router interface{}) { 150 | 151 | } 152 | 153 | func (this *MyPtotoc) GetMsgHandle() iface.Imsghandle { 154 | return nil 155 | } 156 | 157 | func (this *MyPtotoc) GetDataPack() iface.Idatapack { 158 | return nil 159 | } 160 | 161 | func (this *MyPtotoc) InitWorker(int32) { 162 | 163 | } 164 | 165 | func main() { 166 | nets := []int{11009} 167 | for i := 0; i < 1; i++ { 168 | client := fnet.NewTcpClient("127.0.0.1", nets[rand.Intn(len(nets))], &MyPtotoc{}) 169 | client.Start() 170 | time.Sleep(100 * time.Millisecond) 171 | } 172 | 173 | // close 174 | c := make(chan os.Signal, 1) 175 | signal.Notify(c, os.Interrupt, os.Kill) 176 | sig := <-c 177 | fmt.Println("=======", sig) 178 | } 179 | -------------------------------------------------------------------------------- /pb/msg.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: msg.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package pb is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | msg.proto 10 | 11 | It has these top-level messages: 12 | SyncPid 13 | Player 14 | SyncPlayers 15 | Position 16 | MovePackege 17 | BroadCast 18 | Talk 19 | */ 20 | package pb 21 | 22 | import proto "github.com/golang/protobuf/proto" 23 | import fmt "fmt" 24 | import math "math" 25 | 26 | // Reference imports to suppress errors if they are not otherwise used. 27 | var _ = proto.Marshal 28 | var _ = fmt.Errorf 29 | var _ = math.Inf 30 | 31 | // This is a compile-time assertion to ensure that this generated file 32 | // is compatible with the proto package it is being compiled against. 33 | // A compilation error at this line likely means your copy of the 34 | // proto package needs to be updated. 35 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 36 | 37 | type SyncPid struct { 38 | Pid int32 `protobuf:"varint,1,opt,name=Pid,json=pid" json:"Pid,omitempty"` 39 | } 40 | 41 | func (m *SyncPid) Reset() { *m = SyncPid{} } 42 | func (m *SyncPid) String() string { return proto.CompactTextString(m) } 43 | func (*SyncPid) ProtoMessage() {} 44 | func (*SyncPid) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 45 | 46 | type Player struct { 47 | Pid int32 `protobuf:"varint,1,opt,name=Pid,json=pid" json:"Pid,omitempty"` 48 | P *Position `protobuf:"bytes,2,opt,name=P,json=p" json:"P,omitempty"` 49 | } 50 | 51 | func (m *Player) Reset() { *m = Player{} } 52 | func (m *Player) String() string { return proto.CompactTextString(m) } 53 | func (*Player) ProtoMessage() {} 54 | func (*Player) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 55 | 56 | func (m *Player) GetP() *Position { 57 | if m != nil { 58 | return m.P 59 | } 60 | return nil 61 | } 62 | 63 | type SyncPlayers struct { 64 | Ps []*Player `protobuf:"bytes,1,rep,name=ps" json:"ps,omitempty"` 65 | } 66 | 67 | func (m *SyncPlayers) Reset() { *m = SyncPlayers{} } 68 | func (m *SyncPlayers) String() string { return proto.CompactTextString(m) } 69 | func (*SyncPlayers) ProtoMessage() {} 70 | func (*SyncPlayers) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } 71 | 72 | func (m *SyncPlayers) GetPs() []*Player { 73 | if m != nil { 74 | return m.Ps 75 | } 76 | return nil 77 | } 78 | 79 | type Position struct { 80 | X float32 `protobuf:"fixed32,1,opt,name=X,json=x" json:"X,omitempty"` 81 | Y float32 `protobuf:"fixed32,2,opt,name=Y,json=y" json:"Y,omitempty"` 82 | Z float32 `protobuf:"fixed32,3,opt,name=Z,json=z" json:"Z,omitempty"` 83 | V float32 `protobuf:"fixed32,4,opt,name=V,json=v" json:"V,omitempty"` 84 | } 85 | 86 | func (m *Position) Reset() { *m = Position{} } 87 | func (m *Position) String() string { return proto.CompactTextString(m) } 88 | func (*Position) ProtoMessage() {} 89 | func (*Position) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } 90 | 91 | type MovePackege struct { 92 | P *Position `protobuf:"bytes,1,opt,name=P,json=p" json:"P,omitempty"` 93 | ActionData int32 `protobuf:"varint,2,opt,name=ActionData,json=actionData" json:"ActionData,omitempty"` 94 | } 95 | 96 | func (m *MovePackege) Reset() { *m = MovePackege{} } 97 | func (m *MovePackege) String() string { return proto.CompactTextString(m) } 98 | func (*MovePackege) ProtoMessage() {} 99 | func (*MovePackege) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } 100 | 101 | func (m *MovePackege) GetP() *Position { 102 | if m != nil { 103 | return m.P 104 | } 105 | return nil 106 | } 107 | 108 | type BroadCast struct { 109 | Pid int32 `protobuf:"varint,1,opt,name=Pid,json=pid" json:"Pid,omitempty"` 110 | Tp int32 `protobuf:"varint,2,opt,name=Tp,json=tp" json:"Tp,omitempty"` 111 | // Types that are valid to be assigned to Data: 112 | // *BroadCast_Content 113 | // *BroadCast_P 114 | // *BroadCast_ActionData 115 | Data isBroadCast_Data `protobuf_oneof:"Data"` 116 | } 117 | 118 | func (m *BroadCast) Reset() { *m = BroadCast{} } 119 | func (m *BroadCast) String() string { return proto.CompactTextString(m) } 120 | func (*BroadCast) ProtoMessage() {} 121 | func (*BroadCast) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } 122 | 123 | type isBroadCast_Data interface { 124 | isBroadCast_Data() 125 | } 126 | 127 | type BroadCast_Content struct { 128 | Content string `protobuf:"bytes,3,opt,name=Content,json=content,oneof"` 129 | } 130 | type BroadCast_P struct { 131 | P *Position `protobuf:"bytes,4,opt,name=P,json=p,oneof"` 132 | } 133 | type BroadCast_ActionData struct { 134 | ActionData int32 `protobuf:"varint,5,opt,name=ActionData,json=actionData,oneof"` 135 | } 136 | 137 | func (*BroadCast_Content) isBroadCast_Data() {} 138 | func (*BroadCast_P) isBroadCast_Data() {} 139 | func (*BroadCast_ActionData) isBroadCast_Data() {} 140 | 141 | func (m *BroadCast) GetData() isBroadCast_Data { 142 | if m != nil { 143 | return m.Data 144 | } 145 | return nil 146 | } 147 | 148 | func (m *BroadCast) GetContent() string { 149 | if x, ok := m.GetData().(*BroadCast_Content); ok { 150 | return x.Content 151 | } 152 | return "" 153 | } 154 | 155 | func (m *BroadCast) GetP() *Position { 156 | if x, ok := m.GetData().(*BroadCast_P); ok { 157 | return x.P 158 | } 159 | return nil 160 | } 161 | 162 | func (m *BroadCast) GetActionData() int32 { 163 | if x, ok := m.GetData().(*BroadCast_ActionData); ok { 164 | return x.ActionData 165 | } 166 | return 0 167 | } 168 | 169 | // XXX_OneofFuncs is for the internal use of the proto package. 170 | func (*BroadCast) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { 171 | return _BroadCast_OneofMarshaler, _BroadCast_OneofUnmarshaler, _BroadCast_OneofSizer, []interface{}{ 172 | (*BroadCast_Content)(nil), 173 | (*BroadCast_P)(nil), 174 | (*BroadCast_ActionData)(nil), 175 | } 176 | } 177 | 178 | func _BroadCast_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { 179 | m := msg.(*BroadCast) 180 | // Data 181 | switch x := m.Data.(type) { 182 | case *BroadCast_Content: 183 | b.EncodeVarint(3<<3 | proto.WireBytes) 184 | b.EncodeStringBytes(x.Content) 185 | case *BroadCast_P: 186 | b.EncodeVarint(4<<3 | proto.WireBytes) 187 | if err := b.EncodeMessage(x.P); err != nil { 188 | return err 189 | } 190 | case *BroadCast_ActionData: 191 | b.EncodeVarint(5<<3 | proto.WireVarint) 192 | b.EncodeVarint(uint64(x.ActionData)) 193 | case nil: 194 | default: 195 | return fmt.Errorf("BroadCast.Data has unexpected type %T", x) 196 | } 197 | return nil 198 | } 199 | 200 | func _BroadCast_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { 201 | m := msg.(*BroadCast) 202 | switch tag { 203 | case 3: // Data.Content 204 | if wire != proto.WireBytes { 205 | return true, proto.ErrInternalBadWireType 206 | } 207 | x, err := b.DecodeStringBytes() 208 | m.Data = &BroadCast_Content{x} 209 | return true, err 210 | case 4: // Data.P 211 | if wire != proto.WireBytes { 212 | return true, proto.ErrInternalBadWireType 213 | } 214 | msg := new(Position) 215 | err := b.DecodeMessage(msg) 216 | m.Data = &BroadCast_P{msg} 217 | return true, err 218 | case 5: // Data.ActionData 219 | if wire != proto.WireVarint { 220 | return true, proto.ErrInternalBadWireType 221 | } 222 | x, err := b.DecodeVarint() 223 | m.Data = &BroadCast_ActionData{int32(x)} 224 | return true, err 225 | default: 226 | return false, nil 227 | } 228 | } 229 | 230 | func _BroadCast_OneofSizer(msg proto.Message) (n int) { 231 | m := msg.(*BroadCast) 232 | // Data 233 | switch x := m.Data.(type) { 234 | case *BroadCast_Content: 235 | n += proto.SizeVarint(3<<3 | proto.WireBytes) 236 | n += proto.SizeVarint(uint64(len(x.Content))) 237 | n += len(x.Content) 238 | case *BroadCast_P: 239 | s := proto.Size(x.P) 240 | n += proto.SizeVarint(4<<3 | proto.WireBytes) 241 | n += proto.SizeVarint(uint64(s)) 242 | n += s 243 | case *BroadCast_ActionData: 244 | n += proto.SizeVarint(5<<3 | proto.WireVarint) 245 | n += proto.SizeVarint(uint64(x.ActionData)) 246 | case nil: 247 | default: 248 | panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) 249 | } 250 | return n 251 | } 252 | 253 | type Talk struct { 254 | Content string `protobuf:"bytes,1,opt,name=Content,json=content" json:"Content,omitempty"` 255 | } 256 | 257 | func (m *Talk) Reset() { *m = Talk{} } 258 | func (m *Talk) String() string { return proto.CompactTextString(m) } 259 | func (*Talk) ProtoMessage() {} 260 | func (*Talk) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } 261 | 262 | func init() { 263 | proto.RegisterType((*SyncPid)(nil), "pb.SyncPid") 264 | proto.RegisterType((*Player)(nil), "pb.Player") 265 | proto.RegisterType((*SyncPlayers)(nil), "pb.SyncPlayers") 266 | proto.RegisterType((*Position)(nil), "pb.Position") 267 | proto.RegisterType((*MovePackege)(nil), "pb.MovePackege") 268 | proto.RegisterType((*BroadCast)(nil), "pb.BroadCast") 269 | proto.RegisterType((*Talk)(nil), "pb.Talk") 270 | } 271 | 272 | func init() { proto.RegisterFile("msg.proto", fileDescriptor0) } 273 | 274 | var fileDescriptor0 = []byte{ 275 | // 303 bytes of a gzipped FileDescriptorProto 276 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x91, 0x4d, 0x4b, 0xc3, 0x30, 277 | 0x18, 0xc7, 0x4d, 0xda, 0xae, 0xf6, 0xe9, 0x10, 0xc9, 0xa9, 0xa8, 0x48, 0xc9, 0x49, 0x2f, 0x3d, 278 | 0x28, 0x78, 0xb7, 0xf3, 0xa0, 0x07, 0x21, 0xc4, 0x21, 0xea, 0x2d, 0x7d, 0x61, 0x94, 0xcd, 0x26, 279 | 0xb4, 0x61, 0x58, 0xbf, 0x85, 0x5f, 0xc3, 0x4f, 0x69, 0x92, 0xea, 0x54, 0xb6, 0x53, 0xf9, 0x3d, 280 | 0x6f, 0xff, 0x5f, 0x09, 0x44, 0xaf, 0xfd, 0x22, 0x53, 0x9d, 0xd4, 0x92, 0x60, 0x55, 0xd0, 0x63, 281 | 0x08, 0x1f, 0x86, 0xb6, 0x64, 0x4d, 0x45, 0x0e, 0xc1, 0x33, 0x9f, 0x04, 0xa5, 0xe8, 0x2c, 0xe0, 282 | 0x9e, 0x6a, 0x2a, 0x7a, 0x05, 0x13, 0xb6, 0x12, 0x43, 0xdd, 0x6d, 0xf7, 0xc8, 0x11, 0x20, 0x96, 283 | 0x60, 0xc3, 0xf1, 0xc5, 0x34, 0x53, 0x45, 0xc6, 0x64, 0xdf, 0xe8, 0x46, 0xb6, 0x1c, 0x29, 0x7a, 284 | 0x0e, 0xb1, 0x3b, 0xea, 0x76, 0x7b, 0x33, 0x8a, 0x55, 0x6f, 0x76, 0x3d, 0x33, 0x0b, 0x6e, 0xd6, 285 | 0x35, 0xb8, 0xa9, 0xd2, 0x1c, 0xf6, 0x7f, 0x36, 0xc9, 0x14, 0xd0, 0x93, 0x8b, 0xc0, 0x1c, 0xbd, 286 | 0x59, 0x7a, 0x76, 0x01, 0x86, 0x06, 0x4b, 0x2f, 0x89, 0x37, 0xd2, 0xbb, 0xa5, 0xc7, 0xc4, 0x1f, 287 | 0x69, 0x4d, 0xef, 0x20, 0xbe, 0x97, 0xeb, 0x9a, 0x89, 0x72, 0x59, 0x2f, 0xea, 0xd1, 0x0c, 0xed, 288 | 0x34, 0x23, 0xa7, 0x00, 0xd7, 0xa5, 0x85, 0x1b, 0xa1, 0x85, 0xbb, 0x1e, 0x70, 0x10, 0x9b, 0x0a, 289 | 0xfd, 0x40, 0x10, 0xe5, 0x9d, 0x14, 0xd5, 0x4c, 0xf4, 0x7a, 0xc7, 0x5f, 0x1f, 0x00, 0x9e, 0xab, 290 | 0xef, 0x3d, 0xac, 0x95, 0xc9, 0x0a, 0x67, 0xb2, 0xd5, 0x75, 0xab, 0x9d, 0x5c, 0x74, 0xbb, 0xc7, 291 | 0xc3, 0x72, 0x2c, 0x90, 0x13, 0xeb, 0xe1, 0x6f, 0x7b, 0x98, 0x19, 0x63, 0x92, 0xfe, 0x33, 0x09, 292 | 0xec, 0x45, 0xd3, 0xf8, 0xe3, 0x92, 0x4f, 0xc0, 0x77, 0x4e, 0x29, 0xf8, 0x73, 0xb1, 0x5a, 0x92, 293 | 0xe4, 0x37, 0xcb, 0x1a, 0x45, 0x9b, 0xa4, 0x3c, 0xf8, 0xc4, 0x98, 0x15, 0xc5, 0xc4, 0x3d, 0xeb, 294 | 0xe5, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x50, 0x88, 0x10, 0xb8, 0xe3, 0x01, 0x00, 0x00, 295 | } 296 | --------------------------------------------------------------------------------