├── .gdbinit ├── .gitattributes ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── VERSION ├── config.ini ├── doc ├── arch.dot ├── arch.png ├── fsm.dot └── fsm.png ├── donate.png ├── donation.png ├── src ├── README.md ├── agent │ ├── AI │ │ ├── README.md │ │ ├── forward.go │ │ └── user.go │ ├── README.md │ ├── agent.go │ ├── buffer.go │ ├── close_work.go │ ├── consts.go │ ├── flush.go │ ├── gsdb │ │ ├── README.md │ │ └── online.go │ ├── hub_client │ │ ├── api.go │ │ ├── hub_client.go │ │ ├── hub_func.go │ │ └── proto.go │ ├── ipc │ │ └── ipc.go │ ├── ipc_service │ │ ├── IPC_chat.go │ │ ├── IPC_kick.go │ │ ├── IPC_ping.go │ │ ├── SYS_broadcast.go │ │ ├── SYS_multicast.go │ │ ├── proto.go │ │ └── services.go │ ├── main.go │ ├── net │ │ ├── api.go │ │ ├── handle.go │ │ └── proto.go │ ├── proxy.go │ ├── rank │ │ └── rank.go │ ├── signal.go │ ├── startup_work.go │ ├── stats_client │ │ ├── README.md │ │ ├── api.go │ │ ├── main.go │ │ └── proto.go │ ├── sys.go │ └── timer_work.go ├── cfg │ ├── README.md │ ├── config.go │ ├── config_test.go │ ├── dup.go │ └── logger.go ├── db │ ├── README.md │ ├── data_tbl │ │ ├── README.md │ │ └── data_tbl.go │ ├── forward_tbl │ │ ├── README.md │ │ └── forward.go │ ├── mongo.go │ ├── stats_tbl │ │ └── stats.go │ └── user_tbl │ │ ├── README.md │ │ └── user.go ├── gamedata │ ├── README.md │ ├── data │ │ └── sheet1.csv │ ├── gamedata.go │ └── parser.go ├── helper │ ├── conn.go │ ├── export.go │ ├── profilingtool.go │ ├── rand.go │ ├── rand_test.go │ ├── sendchan.go │ └── stack.go ├── hub │ ├── README.md │ ├── agent.go │ ├── core │ │ ├── fsm.go │ │ └── load.go │ ├── main.go │ ├── protos │ │ ├── README.md │ │ ├── api.go │ │ ├── handle.go │ │ ├── proto.go │ │ └── servers.go │ ├── proxy.go │ ├── signal.go │ └── sys.go ├── misc │ ├── README.md │ ├── alg │ │ ├── README.md │ │ ├── bitset │ │ │ ├── bitset.go │ │ │ └── bitset_test.go │ │ ├── consistent_hash │ │ │ ├── README.md │ │ │ ├── consistent_hash.go │ │ │ └── consistent_hash_test.go │ │ ├── dos │ │ │ ├── README.md │ │ │ ├── dos.go │ │ │ └── dos_test.go │ │ ├── gaussian │ │ │ ├── README.md │ │ │ ├── gaussian.go │ │ │ └── gaussian_test.go │ │ ├── interval_tree │ │ │ ├── README.md │ │ │ ├── interval_tree.go │ │ │ └── interval_tree_test.go │ │ └── queue │ │ │ ├── README.md │ │ │ ├── queue.go │ │ │ └── queue_test.go │ ├── crypto │ │ ├── diffie │ │ │ ├── README.md │ │ │ ├── dh.go │ │ │ └── dh_test.go │ │ └── pike │ │ │ ├── pike.go │ │ │ └── pike_test.go │ ├── geoip │ │ ├── GeoIPCountryWhois.csv │ │ ├── README.md │ │ ├── geoip.go │ │ └── geoip_test.go │ ├── naming │ │ ├── README.md │ │ ├── naming.go │ │ └── naming_test.go │ ├── packet │ │ ├── pack.go │ │ ├── pack_test.go │ │ ├── packet.go │ │ └── packet_test.go │ └── timer │ │ ├── README.md │ │ ├── timer.go │ │ └── timer_test.go ├── scripts │ ├── README.md │ ├── api.awk │ ├── api.txt │ ├── api_bind_req.awk │ ├── api_rcode.awk │ ├── hub_api.txt │ ├── hub_proto.txt │ ├── proto.awk │ ├── proto.txt │ ├── proto_func.awk │ ├── proto_gen.sh │ ├── stats_api.txt │ └── stats_proto.txt ├── stats │ ├── README.md │ ├── agent.go │ ├── main.go │ ├── protos │ │ ├── api.go │ │ ├── handle.go │ │ └── proto.go │ ├── proxy.go │ ├── signal.go │ └── sys.go └── types │ ├── README.md │ ├── chat.go │ ├── consts.go │ ├── ipcobject.go │ ├── samples │ └── samples.go │ ├── session.go │ └── user.go └── start-test.sh /.gdbinit: -------------------------------------------------------------------------------- 1 | source /usr/local/go/src/pkg/runtime/runtime-gdb.py 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | VERSION ident 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /gonet 3 | gs.log 4 | hub.log 5 | stats.log 6 | /nex 7 | /pkg/ 8 | /src/gopkg.in/* 9 | /src/github.com/* 10 | /tags 11 | .tags* 12 | .DS_store 13 | /net.log 14 | /startall.sh 15 | /log/* 16 | *.project 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 xtaci. All rights reserved. 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: .FORCE 2 | GO=go 3 | DOT=dot 4 | GOYACC=$(GO) tool yacc 5 | 6 | PROGS = hub \ 7 | stats \ 8 | agent 9 | 10 | DOC_DIR = ./doc 11 | SRCDIR = ./src 12 | INSPECTDIR = $(SRCDIR)/inspect 13 | 14 | NEX=nex.go 15 | 16 | GRAPHS = $(DOC_DIR)/arch.png $(DOC_DIR)/fsm.png 17 | 18 | all: $(PROGS) $(GRAPHS) 19 | 20 | $(PROGS): 21 | $(GO) install $@ 22 | 23 | $(DOC_DIR)/%.png: $(DOC_DIR)/%.dot 24 | $(DOT) -Tpng $< -o $@ 25 | 26 | clean: 27 | rm -rf bin pkg $(NEXBIN) 28 | 29 | fmt: 30 | $(GO) fmt $(SRCDIR)/... 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### A game server skeleton implemented with golang. 2 | ![Architecture](doc/arch.png) 3 | 4 | ### gonet/2 5 | 6 | gonet1已停止维护(I no longer maintain this, please move forward to http://github.com/gonet2 ) 7 | 8 | 建议移步至新架构: http://gonet2.github.io 9 | 10 | #### 部署: 11 | * Game Server(GS): 12 | 玩家直接连接GS, 处理玩家逻辑,并与 HUB/SS 通信,GS存在若干个。 13 | (Players connect directly to GS(s) and process, GS(s) will communication with HUB) 14 | 15 | * Hub Server(HUB): 16 | 若干个GS 连接到一个HUB, 只存在一个HUB,维护基础的全局信息,以及 GS<--->GS 的消息转发. 17 | (packet forwarding) 18 | 19 | * Stats Server(SS): 20 | 统计服务器,根据玩家的行为,记录策划需要的数据,以便于后期统计。 21 | 统计属于事后分析,数据量较大,性能需求不同, 故单独列为一个服务器。 22 | 23 | #### 通信原则: 24 | 1. GS到HUB/SS的通信,都是Call同步调用,即GS必须等待ACK。 25 | 2. HUB到GS的通信,只有forward数据包。 26 | 3. 单播消息在玩家离线时会存入db, 登录后的启动过程 ___GS___ 直接读取db,并forward给玩家goroutine。(持久化) 27 | 4. 多播消息会发送给所有的在线玩家(非持久化) 28 | 5. 广播消息会发送给所有的在线玩家(非持久化) 29 | 30 | #### 服务器状态一致性 31 | 1. GS节点可以单独重启 32 | 2. HUB 重启后,GS必须全部重启 33 | 3. SS 可随意重启,不影响业务 34 | 35 | #### 安装先决条件: 36 | 0. 确保安装好graphviz, gawk 37 | 1. 确保安装好mongodb 38 | 2. 确保config.ini中的mongo_xxxx配置正确 39 | 3. export GOPATH='当前目录' 40 | 41 | #### 安装: 42 | * xtaci@ubuntu:~$ git clone https://github.com/xtaci/gonet 43 | * xtaci@ubuntu:~$ cd gonet 44 | * xtaci@ubuntu:~/gonet$ export GOPATH=~/gonet 45 | * xtaci@ubuntu:~/gonet$ go get gopkg.in/mgo.v2 46 | * xtaci@ubuntu:~/gonet$ make 47 | * xtaci@ubuntu:~/gonet$ ./start-test.sh 48 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | $Id: 055c8729cdcc372500a08db659c045e16c4409fb $ 2 | -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | # config file 2 | version = 1 3 | domain = 烽火连天 4 | # Game Server 5 | service = :8888 6 | inspect = :8800 7 | # 最长刷入数据库时间间隔 8 | flush_interval = 600 9 | # 超过这个操作数,必须刷入数据库 10 | flush_ops = 256 11 | # 最大延迟容忍, 单位:毫秒 12 | max_latency = 5000 13 | # Hub Server 14 | hub_service = :8889 15 | # Stats Server 16 | stats_service = :8891 17 | # 统计队列 18 | stats_max_queue_size = 100000 19 | # 会话超时 20 | session_timeout = 10 21 | # 最多写缓冲保留packet_queue个packet,否者即视为DoS 22 | packet_queue = 15 23 | # mongodb 参数 24 | mongo_host = 127.0.0.1 25 | mongo_db = IE2 26 | # stats db 27 | mongo_host_stats = 127.0.0.1 28 | mongo_db_stats = IE2_STAT 29 | # MD5 salt 30 | salt = s1TP4HchxA 31 | # 日志文件 32 | gs_log = gs.log 33 | hub_log = hub.log 34 | stats_log = stats.log 35 | # 日志输出方向 both(控制台+文件), file(文件), none(不输出) 36 | log_output=file 37 | # 性能profile开关 38 | profile=false 39 | -------------------------------------------------------------------------------- /doc/arch.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | graph [label="The Architecture of gonet" fontsize=12]; 3 | node [colorscheme=rdbu11 fontsize=12]; 4 | edge [colorscheme=rdbu11 fontsize=12]; 5 | subgraph Agents { 6 | node [label=Agent style="rounded,filled" shape=rect color=5]; 7 | Agent1; 8 | Agent2; 9 | Agent3; 10 | Agent4; 11 | } 12 | 13 | subgraph GS { 14 | node [style=filled shape=circle color=1 fontcolor=6]; 15 | GS1; 16 | GS2; 17 | GS1->GS2 [dir=both label=IPCObject style=dashed]; 18 | } 19 | 20 | subgraph { 21 | node [style=filled shape="doublecircle" color=8]; 22 | Hub; 23 | Stats; 24 | } 25 | 26 | subgraph { 27 | node [shape=box3d style=filled fillcolor=7 color=10 fontcolor=10]; 28 | GameDB; 29 | StatsDB; 30 | } 31 | 32 | subgraph { 33 | Agent1->GS1; 34 | Agent2->GS1; 35 | Agent3->GS2; 36 | Agent4->GS2; 37 | } 38 | 39 | GS1->Hub [dir=both]; 40 | GS2->Hub [dir=both]; 41 | GS1->Stats; 42 | GS2->Stats; 43 | GS1->GameDB; 44 | GS2->GameDB; 45 | 46 | Telnet [shape=plaintext label="Telnet Console"]; 47 | Telnet->GS1 [style=dotted]; 48 | 49 | Hub->GameDB; 50 | Stats->GameDB; 51 | Stats->StatsDB; 52 | } 53 | -------------------------------------------------------------------------------- /doc/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xtaci/gonet/b8bfb19688d10a890bd60835fe59a8cd8f80c924/doc/arch.png -------------------------------------------------------------------------------- /doc/fsm.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | rankdir=LR; 3 | node [shape = doublecircle]; OFF_FREE OFF_PROT OFF_RAID; 4 | node [shape = circle]; 5 | OFF_FREE->ON_FREE [label="Login"]; 6 | OFF_FREE->OFF_RAID [label="Raid"]; 7 | OFF_PROT->ON_PROT [label="Login"]; 8 | OFF_RAID->OFF_PROT [label="Protect"]; 9 | OFF_RAID->OFF_FREE [label="Free"]; 10 | OFF_PROT->OFF_FREE [label="Expire"]; 11 | ON_FREE->OFF_FREE [label="Logout"]; 12 | ON_FREE->ON_PROT [label="Protect"]; 13 | ON_PROT->OFF_PROT [label="Logout"]; 14 | ON_PROT->ON_FREE [label="Free"]; 15 | ON_PROT->ON_FREE [label="Expire"]; 16 | ON_PROT->ON_PROT [label="Protect"]; 17 | } 18 | -------------------------------------------------------------------------------- /doc/fsm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xtaci/gonet/b8bfb19688d10a890bd60835fe59a8cd8f80c924/doc/fsm.png -------------------------------------------------------------------------------- /donate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xtaci/gonet/b8bfb19688d10a890bd60835fe59a8cd8f80c924/donate.png -------------------------------------------------------------------------------- /donation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xtaci/gonet/b8bfb19688d10a890bd60835fe59a8cd8f80c924/donation.png -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | 框架源码 2 | 3 |
 4 |   agent -- 游戏服务器
 5 |   cfg -- config.ini 读取
 6 |   db -- 数据库驱动
 7 |   gamedata -- 游戏数值处理
 8 |   helper -- 辅助函数,candy funcs....
 9 |   hub -- HUB服务器
10 |   inspect -- Telnet Console for GS
11 |   misc -- 算法等
12 |   scripts -- awk bash 脚本
13 |   stats -- 统计服务器
14 |   types -- 玩家数据结构
15 | 
16 | -------------------------------------------------------------------------------- /src/agent/AI/README.md: -------------------------------------------------------------------------------- 1 | 游戏逻辑 2 | -------------------------------------------------------------------------------- /src/agent/AI/forward.go: -------------------------------------------------------------------------------- 1 | package AI 2 | 3 | import ( 4 | "db/forward_tbl" 5 | . "types" 6 | ) 7 | 8 | //------------------------------------------------ 载入离线时收到的的IPCObject 9 | func LoadIPCObjects(user_id int32, MQ chan IPCObject) { 10 | objs := forward_tbl.PopAll(user_id) 11 | 12 | // 消息没有完全push到MQ, 存回db 13 | var k int 14 | 15 | defer func() { 16 | if x := recover(); x != nil { 17 | for k < len(objs) { 18 | forward_tbl.Push(&objs[k]) 19 | k++ 20 | } 21 | } 22 | }() 23 | 24 | for k = range objs { 25 | MQ <- objs[k] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/agent/AI/user.go: -------------------------------------------------------------------------------- 1 | package AI 2 | 3 | import ( 4 | "agent/gsdb" 5 | "misc/geoip" 6 | . "types" 7 | ) 8 | 9 | //------------------------------------------------ 登陆后的数据加载 10 | func LoginProc(sess *Session) bool { 11 | if sess.User == nil { 12 | return false 13 | } 14 | 15 | // TODO: init data structure for session, such as builds 16 | 17 | // set countrycode 18 | if sess.User.CountryCode == "" { 19 | sess.User.CountryCode = geoip.Query(sess.IP) 20 | } 21 | 22 | // register as online 23 | gsdb.RegisterOnline(sess, sess.User.Id) 24 | 25 | // load messages 26 | go LoadIPCObjects(sess.User.Id, sess.MQ) 27 | 28 | return true 29 | } 30 | -------------------------------------------------------------------------------- /src/agent/README.md: -------------------------------------------------------------------------------- 1 | ### 游戏服务器(GS)设计: 2 | 一个玩家对应一个goroutine(称之为agent), 每个goroutine接收信息类型包含: 3 | 4 | 1. 来自客户端的 5 | 2. 来自其他玩家的 6 | 3. 时钟信号 7 | 8 | ### 持久化设计 9 | 1. 玩家在线时agent负责变更内存中玩家数据,并持久化到数据库, 10 | 2. 升级逻辑以agent为准, 即agent直接改变数据库中的数据. 11 | 3. 在线时数据持久化策略: 12 | a) 超过一定的操作数量,刷入数据库 13 | b) 超过一定的时间,刷入数据库 14 | c) 离线时,刷入数据库 15 | 16 | #### 包结构 17 |
18 | |LENGTH|TIME_ELAPSED|PROTO|PAYLOAD|
19 | 
20 | |16|32|16|...|
21 | 
22 | 23 | #### 包测试: 24 |
25 | heart_beat:
26 | echo "0006 00000001 0000" | xxd -r -ps |nc 127.0.0.1 8888 -q 2|hexdump -C
27 | 
28 | -------------------------------------------------------------------------------- /src/agent/agent.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | import ( 10 | "helper" 11 | "misc/timer" 12 | . "types" 13 | ) 14 | 15 | func init() { 16 | log.SetPrefix("[GS] ") 17 | } 18 | 19 | var wg sync.WaitGroup 20 | var die chan bool // for server close 21 | 22 | func init() { 23 | die = make(chan bool) 24 | } 25 | 26 | //----------------------------------------------- Start Agent when a client is connected 27 | func StartAgent(sess *Session, in chan []byte, out *Buffer) { 28 | defer wg.Done() 29 | defer helper.PrintPanicStack() 30 | 31 | // init session 32 | sess.MQ = make(chan IPCObject, DEFAULT_MQ_SIZE) 33 | sess.ConnectTime = time.Now() 34 | sess.LastPacketTime = time.Now() 35 | 36 | // custom-sec timer, 60-sec 37 | custom_timer := make(chan int32, 1) 38 | timer.Add(-1, time.Now().Unix()+CUSTOM_TIMER, custom_timer) 39 | 40 | // cleanup work 41 | defer func() { 42 | close_work(sess) 43 | }() 44 | 45 | // the main message loop 46 | for { 47 | select { 48 | case msg, ok := <-in: // network protocol 49 | if !ok { 50 | return 51 | } 52 | 53 | sess.PacketTime = time.Now() 54 | if result := UserRequestProxy(sess, msg); result != nil { 55 | err := out.Send(result) 56 | if err != nil { 57 | helper.ERR("cannot send to client", err) 58 | return 59 | } 60 | } 61 | sess.LastPacketTime = sess.PacketTime 62 | sess.PacketCount++ // packet count 63 | case msg := <-sess.MQ: // internal IPC 64 | if result := IPCRequestProxy(sess, &msg); result != nil { 65 | err := out.Send(result) 66 | if err != nil { 67 | helper.ERR("cannot send ipc response", err) 68 | return 69 | } 70 | } 71 | case <-custom_timer: // 60-sec timer 72 | timer_work(sess) 73 | timer.Add(-1, time.Now().Unix()+CUSTOM_TIMER, custom_timer) 74 | case <-die: 75 | sess.Flag |= SESS_KICKED_OUT 76 | } 77 | 78 | // is the session been kicked out 79 | if sess.Flag&SESS_KICKED_OUT != 0 { 80 | return 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/agent/buffer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "strconv" 6 | ) 7 | 8 | import ( 9 | "cfg" 10 | . "helper" 11 | "misc/packet" 12 | . "types" 13 | ) 14 | 15 | type Buffer struct { 16 | ctrl chan bool // receive exit signal 17 | pending chan []byte // pending Packet 18 | max int // max queue size 19 | conn net.Conn // connection 20 | sess *Session // session 21 | } 22 | 23 | //------------------------------------------------ send packet 24 | // !IMPORTANT! once closed, never Send!!! 25 | func (buf *Buffer) Send(data []byte) (err error) { 26 | defer func() { 27 | if x := recover(); x != nil { 28 | WARN("buffer.Send failed", x) 29 | } 30 | }() 31 | 32 | if buf.sess.Flag&SESS_ENCRYPT != 0 { // if encryption has setup 33 | buf.sess.Encoder.Codec(data) 34 | } else if buf.sess.Flag&SESS_KEYEXCG != 0 { // whether we just exchanged the key 35 | buf.sess.Flag &= ^SESS_KEYEXCG 36 | buf.sess.Flag |= SESS_ENCRYPT 37 | } 38 | buf.pending <- data 39 | return nil 40 | } 41 | 42 | //------------------------------------------------ packet sender goroutine 43 | func (buf *Buffer) Start() { 44 | defer func() { 45 | if x := recover(); x != nil { 46 | ERR("caught panic in buffer goroutine", x) 47 | } 48 | }() 49 | 50 | for { 51 | select { 52 | case data := <-buf.pending: 53 | buf.raw_send(data) 54 | case <-buf.ctrl: // session end, send final data 55 | close(buf.pending) 56 | for data := range buf.pending { 57 | buf.raw_send(data) 58 | } 59 | // close connection 60 | buf.conn.Close() 61 | return 62 | } 63 | } 64 | } 65 | 66 | //------------------------------------------------ packet online 67 | func (buf *Buffer) raw_send(data []byte) { 68 | writer := packet.Writer() 69 | writer.WriteU16(uint16(len(data))) 70 | writer.WriteRawBytes(data) 71 | 72 | //nr := int16(data[0])<<8 | int16(data[1]) 73 | //log.Printf("\033[37;44m[ACK] %v\t%v\tSIZE:%v\033[0m\n", nr, proto.RCode[nr], len(data)) 74 | n, err := buf.conn.Write(writer.Data()) 75 | if err != nil { 76 | ERR("Error send reply, bytes:", n, "reason:", err) 77 | return 78 | } 79 | } 80 | 81 | //------------------------------------------------ create a new write buffer 82 | func NewBuffer(sess *Session, conn net.Conn, ctrl chan bool) *Buffer { 83 | config := cfg.Get() 84 | max, err := strconv.Atoi(config["outqueue_size"]) 85 | if err != nil { 86 | max = DEFAULT_OUTQUEUE_SIZE 87 | WARN("cannot parse outqueue_size from config", err, "using default:", max) 88 | } 89 | 90 | buf := Buffer{conn: conn} 91 | buf.sess = sess 92 | buf.pending = make(chan []byte, max) 93 | buf.ctrl = ctrl 94 | buf.max = max 95 | return &buf 96 | } 97 | -------------------------------------------------------------------------------- /src/agent/close_work.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "agent/gsdb" 5 | "agent/hub_client" 6 | "db/forward_tbl" 7 | . "helper" 8 | "misc/geoip" 9 | . "types" 10 | ) 11 | 12 | //----------------------------------------------- cleanup work after disconnection 13 | func close_work(sess *Session) { 14 | defer PrintPanicStack() 15 | if sess.Flag&SESS_LOGGED_IN == 0 { 16 | return 17 | } 18 | 19 | // must flush user data 20 | _flush(sess) 21 | 22 | // notify hub 23 | hub_client.Logout(sess.User.Id) 24 | 25 | // unregister online at this server 26 | gsdb.UnregisterOnline(sess.User.Id) 27 | 28 | // close MQ, and save the queue to db 29 | close(sess.MQ) 30 | for ipcobject := range sess.MQ { 31 | forward_tbl.Push(&ipcobject) 32 | NOTICE("re-pushed ipcobject back to db, userid:", sess.User.Id) 33 | } 34 | 35 | NOTICE(sess.User.Name, "disconnected from", sess.IP, "country:", geoip.Query(sess.IP)) 36 | } 37 | -------------------------------------------------------------------------------- /src/agent/consts.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | TCP_TIMEOUT = 60 // tcp read timeout 5 | MAX_DELAY_IN = 60 // packet process wait 6 | DEFAULT_INQUEUE_SIZE = 64 // default input buffer size 7 | DEFAULT_OUTQUEUE_SIZE = 15 // default output buffer size 8 | ) 9 | 10 | const ( 11 | DEFAULT_MQ_SIZE = 32 // size of user's message queue 12 | DEFAULT_FLUSH_OPS = 128 // max ops before flush 13 | CUSTOM_TIMER = 60 // a timer for each user 14 | ) 15 | 16 | const ( 17 | PACKET_EXPIRE = 120 // packet before this duration is illegal 18 | PACKET_ERROR = 5 // due to time error of client and server 19 | ) 20 | 21 | const ( 22 | SYS_MQ_SIZE = 65535 // size of sys routine's message queue 23 | GC_INTERVAL = 300 // voluntary GC interval 24 | ) 25 | -------------------------------------------------------------------------------- /src/agent/flush.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | ) 7 | 8 | import ( 9 | "cfg" 10 | "db/user_tbl" 11 | "helper" 12 | . "types" 13 | ) 14 | 15 | //------------------------------------------------ data flush control (interval + dirty flag) 16 | func _flush_work(sess *Session) { 17 | config := cfg.Get() 18 | fi := config["flush_interval"] 19 | inter, _ := strconv.Atoi(fi) 20 | fo := config["flush_ops"] 21 | ops, _ := strconv.Atoi(fo) 22 | 23 | if sess.DirtyCount() > int32(ops) || (sess.DirtyCount() > 0 && time.Now().Unix()-sess.User.LastSaveTime > int64(inter)) { 24 | helper.NOTICE("flush dirtycount:", sess.DirtyCount(), "duration(sec):", time.Now().Unix()-sess.User.LastSaveTime) 25 | _flush(sess) 26 | } 27 | } 28 | 29 | //------------------------------------------------ save to db 30 | func _flush(sess *Session) { 31 | if sess.User != nil { 32 | sess.User.LastSaveTime = time.Now().Unix() 33 | user_tbl.Set(sess.User) 34 | helper.NOTICE(sess.User.Id, sess.User.Name, "data flushed") 35 | } 36 | 37 | // TODO : save all the data in session 38 | sess.MarkClean() 39 | } 40 | -------------------------------------------------------------------------------- /src/agent/gsdb/README.md: -------------------------------------------------------------------------------- 1 | 记录在线用户的Session 2 | -------------------------------------------------------------------------------- /src/agent/gsdb/online.go: -------------------------------------------------------------------------------- 1 | package gsdb 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | import . "types" 8 | 9 | var _active map[int32]*Session 10 | var _lock sync.RWMutex 11 | 12 | //----------------------------------------------- register a user as online user 13 | func RegisterOnline(sess *Session, id int32) { 14 | defer _lock.Unlock() 15 | _lock.Lock() 16 | 17 | _active[id] = sess 18 | } 19 | 20 | //----------------------------------------------- unregister a user from online users 21 | func UnregisterOnline(id int32) { 22 | defer _lock.Unlock() 23 | _lock.Lock() 24 | 25 | delete(_active, id) 26 | } 27 | 28 | //----------------------------------------------- query a online user 29 | func QueryOnline(id int32) *Session { 30 | defer _lock.RUnlock() 31 | _lock.RLock() 32 | 33 | return _active[id] 34 | } 35 | 36 | //----------------------------------------------- list all online users 37 | func ListAll() []int32 { 38 | defer _lock.RUnlock() 39 | _lock.RLock() 40 | 41 | list := make([]int32, len(_active)) 42 | idx := 0 43 | for k := range _active { 44 | list[idx] = k 45 | idx++ 46 | } 47 | 48 | return list 49 | } 50 | 51 | func init() { 52 | _active = make(map[int32]*Session) 53 | } 54 | -------------------------------------------------------------------------------- /src/agent/hub_client/api.go: -------------------------------------------------------------------------------- 1 | package hub_client 2 | 3 | var Code = map[string]int16{ 4 | "ping_req": 0, // PING 5 | "login_req": 1, // 登陆 6 | "logout_req": 2, // 登出 7 | "raid_req": 3, // 攻击 8 | "protect_req": 4, // 加保护 9 | "free_req": 5, // 结束攻击 10 | "adduser_req": 6, // 注册一个新注册的玩家 11 | "forward_req": 100, // 转发IPC消息 12 | } 13 | 14 | var RCode = map[int16]string{ 15 | 0: "ping_req", // PING 16 | 1: "login_req", // 登陆 17 | 2: "logout_req", // 登出 18 | 3: "raid_req", // 攻击 19 | 4: "protect_req", // 加保护 20 | 5: "free_req", // 结束攻击 21 | 6: "adduser_req", // 注册一个新注册的玩家 22 | 100: "forward_req", // 转发IPC消息 23 | } 24 | -------------------------------------------------------------------------------- /src/agent/hub_client/hub_client.go: -------------------------------------------------------------------------------- 1 | package hub_client 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/json" 6 | "io" 7 | "net" 8 | "time" 9 | ) 10 | 11 | import ( 12 | "agent/gsdb" 13 | "cfg" 14 | "db/forward_tbl" 15 | . "helper" 16 | "misc/packet" 17 | . "types" 18 | ) 19 | 20 | type _request struct { 21 | data []byte 22 | ack chan []byte 23 | } 24 | 25 | type _response struct { 26 | seq_id uint32 27 | data []byte 28 | } 29 | 30 | var ( 31 | _conn net.Conn 32 | _caller chan _request 33 | _callee chan _response 34 | ) 35 | 36 | func init() { 37 | _caller = make(chan _request, 20000) 38 | _callee = make(chan _response, 50000) 39 | go _caller_routine() 40 | } 41 | 42 | //----------------------------------------------- connect to hub 43 | func DialHub() { 44 | RETRY: 45 | INFO("Connecting to HUB") 46 | config := cfg.Get() 47 | 48 | addr, err := net.ResolveTCPAddr("tcp", config["hub_service"]) 49 | if err != nil { 50 | ERR(err, addr) 51 | panic("cannot read hub_service from config") 52 | } 53 | 54 | conn, err := net.DialTCP("tcp", nil, addr) 55 | if err != nil { 56 | ERR("connect to hub failed", err, "waiting 5 seconds") 57 | time.Sleep(5 * time.Second) 58 | goto RETRY 59 | } 60 | 61 | // set parameter 62 | conn.SetLinger(-1) 63 | _conn = conn 64 | go _receiver(_conn) 65 | 66 | INFO("HUB connected") 67 | } 68 | 69 | func _caller_routine() { 70 | // waiting ACK queue. 71 | wait_ack := make(map[uint32]chan []byte) 72 | var seq_id uint32 73 | for { 74 | select { 75 | case req := <-_caller: 76 | seq_id++ 77 | if seq_id == 0 { 78 | seq_id++ 79 | } 80 | 81 | wait_ack[seq_id] = req.ack 82 | writer := packet.Writer() 83 | writer.WriteU16(uint16(len(req.data)) + 4) // data + seq id 84 | writer.WriteU32(seq_id) 85 | writer.WriteRawBytes(req.data) 86 | // send the packet 87 | n, err := _conn.Write(writer.Data()) 88 | if err != nil { 89 | ERR("Error send packet to HUB, bytes:", n, "reason:", err) 90 | // reconnect 91 | DialHub() 92 | } 93 | case resp := <-_callee: // callee 94 | ack, ok := wait_ack[resp.seq_id] 95 | if !ok { 96 | ERR("Illegal(or expired) packet seqid:", resp.seq_id, "from HUB", resp.data) 97 | continue 98 | } 99 | ack <- resp.data 100 | close(ack) 101 | delete(wait_ack, resp.seq_id) 102 | } 103 | } 104 | } 105 | 106 | //------------------------------------------------ a single IPC call 107 | func _call(data []byte) (ret []byte) { 108 | // packet size test 109 | if len(data) > packet.PACKET_LIMIT { 110 | ERR("PACKET SIZE TOO LARGE... CHANGE YOUR DESIGN!", len(data)) 111 | return nil 112 | } 113 | 114 | start := time.Now() 115 | defer func() { 116 | end := time.Now() 117 | nr := int16(data[0])<<8 | int16(data[1]) 118 | DEBUG("HUB RPC #", nr, "call time:", end.Sub(start)) 119 | }() 120 | 121 | // construct a request 122 | ack := make(chan []byte, 1) 123 | req := _request{data, ack} 124 | _caller <- req 125 | 126 | // wait for response 127 | select { 128 | case msg := <-ack: 129 | return msg 130 | case <-time.After(10 * time.Second): 131 | ERR("HUB is not responding...") 132 | } 133 | 134 | return nil 135 | } 136 | 137 | //---------------------------------------------------------- deliver an IPCObject to a user 138 | func _deliver(obj *IPCObject) { 139 | sess := gsdb.QueryOnline(obj.DestID) 140 | if sess != nil { 141 | func() { 142 | defer func() { 143 | if x := recover(); x != nil { 144 | forward_tbl.Push(obj) 145 | } 146 | }() 147 | 148 | sess.MQ <- *obj 149 | }() 150 | } else { 151 | forward_tbl.Push(obj) 152 | } 153 | } 154 | 155 | //----------------------------------------------- receive message from hub 156 | // receiver() will automatically exit if something wrong with the connection 157 | func _receiver(conn net.Conn) { 158 | defer func() { 159 | recover() 160 | }() 161 | 162 | header := make([]byte, 2) 163 | seqval := make([]byte, 4) 164 | 165 | for { 166 | // header 167 | n, err := io.ReadFull(conn, header) 168 | if err != nil { 169 | ERR("error receiving header:", n, err) 170 | break 171 | } 172 | 173 | // packet seq_id 174 | n, err = io.ReadFull(conn, seqval) 175 | if err != nil { 176 | ERR("error receiving seq_id:", n, err) 177 | break 178 | } 179 | 180 | // read big-endian header 181 | seq_id := binary.BigEndian.Uint32(seqval) 182 | size := binary.BigEndian.Uint16(header) - 4 183 | data := make([]byte, size) 184 | n, err = io.ReadFull(conn, data) 185 | 186 | if err != nil { 187 | ERR("error receiving msg:", err) 188 | break 189 | } 190 | 191 | // two kinds of IPC: 192 | // a). Hub Sends to GS, sequence number is not required (set to 0), just forwarding to session 193 | // b). Call, sequence number is needed, send will wake up blocking-chan. 194 | if seq_id == 0 { 195 | obj := &IPCObject{} 196 | err := json.Unmarshal(data, obj) 197 | if err != nil { 198 | ERR("unable to decode received IPCObject") 199 | continue 200 | } 201 | 202 | _deliver(obj) 203 | } else { 204 | resp := _response{seq_id, data} 205 | _callee <- resp 206 | } 207 | } 208 | } 209 | 210 | func checkErr(err error) { 211 | if err != nil { 212 | ERR(err) 213 | panic("error occured in protocol module") 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/agent/hub_client/hub_func.go: -------------------------------------------------------------------------------- 1 | package hub_client 2 | 3 | import ( 4 | . "helper" 5 | "misc/packet" 6 | . "types" 7 | ) 8 | 9 | type Info struct { 10 | Id int32 11 | State byte 12 | Score int32 13 | Rank int32 14 | ProtectTime int64 15 | } 16 | 17 | func Ping() bool { 18 | defer _hub_err() 19 | req := INT{} 20 | req.F_v = 1 21 | ret := _call(packet.Pack(Code["ping_req"], &req, nil)) 22 | if ret == nil { 23 | ERR("HUB Ping return", false) 24 | return false 25 | } 26 | return true 27 | } 28 | 29 | func AddUser(id int32) bool { 30 | defer _hub_err() 31 | req := ID{} 32 | req.F_id = id 33 | ret := _call(packet.Pack(Code["adduser_req"], &req, nil)) 34 | reader := packet.Reader(ret) 35 | tbl, err := PKT_INT(reader) 36 | 37 | if err != nil || tbl.F_v == 0 { 38 | NOTICE("HUB AddUser return", false, "param", id) 39 | return false 40 | } 41 | 42 | return true 43 | } 44 | 45 | func Login(id int32) bool { 46 | defer _hub_err() 47 | req := LOGIN_REQ{} 48 | req.F_id = id 49 | ret := _call(packet.Pack(Code["login_req"], &req, nil)) 50 | reader := packet.Reader(ret) 51 | tbl, _ := PKT_LOGIN_ACK(reader) 52 | 53 | if tbl.F_success == 0 { 54 | NOTICE("HUB Login return ", false, "param", id) 55 | return false 56 | } 57 | 58 | return true 59 | } 60 | 61 | func Logout(id int32) bool { 62 | defer _hub_err() 63 | req := ID{} 64 | req.F_id = id 65 | ret := _call(packet.Pack(Code["logout_req"], &req, nil)) 66 | reader := packet.Reader(ret) 67 | tbl, _ := PKT_INT(reader) 68 | 69 | if tbl.F_v == 0 { 70 | NOTICE("HUB Logout return ", false, "param", id) 71 | return false 72 | } 73 | 74 | return true 75 | } 76 | 77 | func Raid(id int32) bool { 78 | defer _hub_err() 79 | 80 | req := ID{} 81 | req.F_id = id 82 | ret := _call(packet.Pack(Code["raid_req"], &req, nil)) 83 | reader := packet.Reader(ret) 84 | tbl, _ := PKT_INT(reader) 85 | 86 | if tbl.F_v == 0 { 87 | NOTICE("HUB Raid return ", false, "param", id) 88 | return false 89 | } 90 | 91 | return true 92 | } 93 | 94 | func Protect(id int32, until int64) bool { 95 | defer _hub_err() 96 | 97 | req := PROTECT{} 98 | req.F_id = id 99 | req.F_protecttime = until 100 | ret := _call(packet.Pack(Code["protect_req"], &req, nil)) 101 | reader := packet.Reader(ret) 102 | tbl, _ := PKT_INT(reader) 103 | 104 | if tbl.F_v == 0 { 105 | NOTICE("HUB Protect return", false, "param", id, until) 106 | return false 107 | } 108 | 109 | return true 110 | } 111 | 112 | func Free(id int32) bool { 113 | defer _hub_err() 114 | 115 | req := ID{} 116 | req.F_id = id 117 | ret := _call(packet.Pack(Code["free_req"], &req, nil)) 118 | reader := packet.Reader(ret) 119 | tbl, _ := PKT_INT(reader) 120 | 121 | if tbl.F_v == 0 { 122 | NOTICE("HUB Free return", false, "param", id) 123 | return false 124 | } 125 | 126 | return true 127 | } 128 | 129 | //---------------------------------------------------------- Forward IPCObject 130 | func Forward(req *IPCObject) bool { 131 | defer _hub_err() 132 | // HUB protocol forwarding 133 | msg := FORWARDIPC{F_IPC: req.Json()} 134 | ret := _call(packet.Pack(Code["forward_req"], &msg, nil)) 135 | reader := packet.Reader(ret) 136 | tbl, err := PKT_INT(reader) 137 | 138 | if err != nil || tbl.F_v == 0 { 139 | ERR("HUB Forward return false", "param", req) 140 | return false 141 | } 142 | 143 | return true 144 | } 145 | 146 | func _hub_err() { 147 | if x := recover(); x != nil { 148 | ERR(x) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/agent/hub_client/proto.go: -------------------------------------------------------------------------------- 1 | package hub_client 2 | 3 | import "misc/packet" 4 | 5 | type FORWARDIPC struct { 6 | F_IPC []byte 7 | } 8 | 9 | type LOGIN_REQ struct { 10 | F_id int32 11 | } 12 | 13 | type LOGIN_ACK struct { 14 | F_success int32 15 | } 16 | 17 | type ID struct { 18 | F_id int32 19 | } 20 | 21 | type PROTECT struct { 22 | F_id int32 23 | F_protecttime int64 24 | } 25 | 26 | type ID_SCORE struct { 27 | F_id int32 28 | F_score int32 29 | } 30 | 31 | type LIST struct { 32 | F_items []ID_SCORE 33 | } 34 | 35 | type LONG struct { 36 | F_v int64 37 | } 38 | 39 | type STRING struct { 40 | F_v string 41 | } 42 | 43 | type INT struct { 44 | F_v int32 45 | } 46 | 47 | func PKT_FORWARDIPC(reader *packet.Packet) (tbl FORWARDIPC, err error) { 48 | tbl.F_IPC, err = reader.ReadBytes() 49 | checkErr(err) 50 | 51 | return 52 | } 53 | 54 | func PKT_LOGIN_REQ(reader *packet.Packet) (tbl LOGIN_REQ, err error) { 55 | tbl.F_id, err = reader.ReadS32() 56 | checkErr(err) 57 | 58 | return 59 | } 60 | 61 | func PKT_LOGIN_ACK(reader *packet.Packet) (tbl LOGIN_ACK, err error) { 62 | tbl.F_success, err = reader.ReadS32() 63 | checkErr(err) 64 | 65 | return 66 | } 67 | 68 | func PKT_ID(reader *packet.Packet) (tbl ID, err error) { 69 | tbl.F_id, err = reader.ReadS32() 70 | checkErr(err) 71 | 72 | return 73 | } 74 | 75 | func PKT_PROTECT(reader *packet.Packet) (tbl PROTECT, err error) { 76 | tbl.F_id, err = reader.ReadS32() 77 | checkErr(err) 78 | 79 | tbl.F_protecttime, err = reader.ReadS64() 80 | checkErr(err) 81 | 82 | return 83 | } 84 | 85 | func PKT_ID_SCORE(reader *packet.Packet) (tbl ID_SCORE, err error) { 86 | tbl.F_id, err = reader.ReadS32() 87 | checkErr(err) 88 | 89 | tbl.F_score, err = reader.ReadS32() 90 | checkErr(err) 91 | 92 | return 93 | } 94 | 95 | func PKT_LIST(reader *packet.Packet) (tbl LIST, err error) { 96 | { 97 | narr, err := reader.ReadU16() 98 | checkErr(err) 99 | 100 | tbl.F_items = make([]ID_SCORE, narr) 101 | for i := 0; i < int(narr); i++ { 102 | tbl.F_items[i], err = PKT_ID_SCORE(reader) 103 | checkErr(err) 104 | 105 | } 106 | 107 | } 108 | 109 | return 110 | } 111 | 112 | func PKT_LONG(reader *packet.Packet) (tbl LONG, err error) { 113 | tbl.F_v, err = reader.ReadS64() 114 | checkErr(err) 115 | 116 | return 117 | } 118 | 119 | func PKT_STRING(reader *packet.Packet) (tbl STRING, err error) { 120 | tbl.F_v, err = reader.ReadString() 121 | checkErr(err) 122 | 123 | return 124 | } 125 | 126 | func PKT_INT(reader *packet.Packet) (tbl INT, err error) { 127 | tbl.F_v, err = reader.ReadS32() 128 | checkErr(err) 129 | 130 | return 131 | } 132 | -------------------------------------------------------------------------------- /src/agent/ipc/ipc.go: -------------------------------------------------------------------------------- 1 | package ipc 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | ) 7 | 8 | import ( 9 | "agent/gsdb" 10 | "agent/hub_client" 11 | "db/forward_tbl" 12 | . "helper" 13 | . "types" 14 | ) 15 | 16 | const ( 17 | SVC_PING = int16(iota) // 协议ping, 测试用 18 | SVC_CHAT // 聊天消息 19 | SVC_KICK // 踢人 20 | 21 | // 系统服务 22 | SYS_BROADCAST // 系统进程专有服务, 转发实时广播 23 | SYS_MULTICAST // 系统进程专有服务, 多播 24 | ) 25 | 26 | //---------------------------------------------------------- 异步消息发送 27 | func Send(src_id, dest_id int32, service int16, object interface{}) (ret bool) { 28 | // 况序列化被传输对象为json 29 | val, err := json.Marshal(object) 30 | if err != nil { 31 | ERR("cannot marshal object to json", err) 32 | return false 33 | } 34 | // Send函数不能投递到SYS_USR 35 | if dest_id == SYS_USR { 36 | ERR("cannot Send to SYS_USR") 37 | return false 38 | } 39 | 40 | // 打包为IPCObject 41 | req := &IPCObject{SrcID: src_id, 42 | DestID: dest_id, 43 | Service: service, 44 | Object: val, 45 | Time: time.Now().Unix()} 46 | 47 | peer := gsdb.QueryOnline(dest_id) 48 | if peer != nil { // 如果玩家在本服务器 49 | // 对方的channel 可能会close, 需要处理panic的情况 50 | defer func() { 51 | if x := recover(); x != nil { 52 | ret = false 53 | forward_tbl.Push(req) 54 | } 55 | }() 56 | select { 57 | case peer.MQ <- *req: 58 | case <-time.After(time.Second): 59 | panic("deadlock") // rare case, when both chans are full. 60 | } 61 | return true 62 | } else { // 通过HUB转发IPCObject 63 | return hub_client.Forward(req) 64 | } 65 | 66 | return false 67 | } 68 | 69 | //---------------------------------------------------------- 全服广播一条动态消息 70 | func Broadcast(service int16, object interface{}) (ret bool) { 71 | obj, ok := _create_broadcast_ipcobject(service, object) 72 | if !ok { 73 | return false 74 | } 75 | 76 | // 投递到HUB 77 | hub_client.Forward(obj) 78 | 79 | // 投递到本地SYS_ROUTINE 80 | peer := gsdb.QueryOnline(SYS_USR) 81 | peer.MQ <- *obj 82 | return true 83 | } 84 | 85 | //---------------------------------------------------------- 本地广播一条动态消息 86 | func Localcast(service int16, object interface{}) (ret bool) { 87 | obj, ok := _create_broadcast_ipcobject(service, object) 88 | if !ok { 89 | return false 90 | } 91 | 92 | // 只投递到本地SYS_ROUTINE 93 | peer := gsdb.QueryOnline(SYS_USR) 94 | peer.MQ <- *obj 95 | return true 96 | } 97 | 98 | func _create_broadcast_ipcobject(service int16, object interface{}) (*IPCObject, bool) { 99 | // object序列化 100 | val, err := json.Marshal(object) 101 | if err != nil { 102 | ERR("cannot marshal object to json", err) 103 | return nil, false 104 | } 105 | 106 | // 内容包 107 | content := &IPCObject{ 108 | Service: service, 109 | Object: val, 110 | Time: time.Now().Unix(), 111 | } 112 | 113 | content_json, _ := json.Marshal(content) 114 | 115 | // 封装为Broadcast包 116 | bc := &IPCObject{ 117 | Service: SYS_BROADCAST, 118 | Object: content_json, 119 | Time: time.Now().Unix(), 120 | } 121 | 122 | return bc, true 123 | } 124 | 125 | //---------------------------------------------------------- 组播 126 | // 发消息到一组**给定的**目标ID 127 | func Multicast(ids []int32, service int16, object interface{}) (ret bool) { 128 | // object序列化 129 | val, err := json.Marshal(object) 130 | if err != nil { 131 | ERR("cannot marshal object to json", err) 132 | return false 133 | } 134 | 135 | // 内容包 136 | content := &IPCObject{ 137 | Service: service, 138 | Object: val, 139 | Time: time.Now().Unix(), 140 | } 141 | 142 | content_json, _ := json.Marshal(content) 143 | 144 | // 封装为Multicast包 145 | mc := &IPCObject{ 146 | AuxIDs: ids, 147 | Service: SYS_MULTICAST, 148 | Object: content_json, 149 | Time: time.Now().Unix(), 150 | } 151 | 152 | // 投递到HUB 153 | hub_client.Forward(mc) 154 | 155 | // 投递到本地SYS_ROUTINE 156 | peer := gsdb.QueryOnline(SYS_USR) 157 | peer.MQ <- *mc 158 | return true 159 | } 160 | -------------------------------------------------------------------------------- /src/agent/ipc_service/IPC_chat.go: -------------------------------------------------------------------------------- 1 | package ipc_service 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | import ( 8 | "agent/net" 9 | . "helper" 10 | "misc/packet" 11 | . "types" 12 | ) 13 | 14 | //---------------------------------------------------------- 聊天通知 15 | func IPC_chat(sess *Session, obj *IPCObject) []byte { 16 | w := &Words{} 17 | err := json.Unmarshal(obj.Object, w) 18 | if err != nil { 19 | ERR("cannot decode worldchat message") 20 | return nil 21 | } 22 | 23 | ret := &talk{} 24 | ret.F_user = w.Speaker 25 | ret.F_msg = w.Words 26 | return packet.Pack(net.Code["talk_notify"], ret, nil) 27 | } 28 | -------------------------------------------------------------------------------- /src/agent/ipc_service/IPC_kick.go: -------------------------------------------------------------------------------- 1 | package ipc_service 2 | 3 | import ( 4 | . "helper" 5 | . "types" 6 | ) 7 | 8 | //---------------------------------------------------------- 用于强制挤下线 9 | func IPC_kick(sess *Session, obj *IPCObject) []byte { 10 | NOTICE(sess.User.Name, "Kicked Out") 11 | sess.Flag |= SESS_KICKED_OUT 12 | return nil 13 | } 14 | -------------------------------------------------------------------------------- /src/agent/ipc_service/IPC_ping.go: -------------------------------------------------------------------------------- 1 | package ipc_service 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | import ( 8 | . "agent/ipc" 9 | . "types" 10 | ) 11 | 12 | //---------------------------------------------------------- 连通性测试 13 | func IPC_ping(sess *Session, obj *IPCObject) []byte { 14 | var str string 15 | err := json.Unmarshal(obj.Object, &str) 16 | if err == nil { 17 | if obj.SrcID != 0 { 18 | Send(0, obj.SrcID, SVC_PING, str) 19 | } 20 | } 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /src/agent/ipc_service/SYS_broadcast.go: -------------------------------------------------------------------------------- 1 | package ipc_service 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | import ( 8 | "agent/gsdb" 9 | . "helper" 10 | . "types" 11 | ) 12 | 13 | //---------------------------------------------------------- 14 | // 广播包 15 | // 只由 SYS_USR 接收 16 | // 外部只需要调用Broadcast函数即可 17 | func SYS_broadcast(sess *Session, obj *IPCObject) []byte { 18 | // 解包 19 | DEBUG("RECEIVED SYS_BROADCAST") 20 | realmsg := &IPCObject{} 21 | err := json.Unmarshal(obj.Object, realmsg) 22 | if err != nil { 23 | ERR("SYS_broadcast cannot decode msg", err, obj.Object) 24 | return nil 25 | } 26 | 27 | // 投递closure 28 | send := func(MQ chan IPCObject) { 29 | defer func() { 30 | recover() 31 | }() 32 | MQ <- *realmsg 33 | } 34 | 35 | // 循环投递 36 | DEBUG("SERVICE", realmsg.Service) 37 | users := gsdb.ListAll() 38 | for _, v := range users { 39 | peer := gsdb.QueryOnline(v) 40 | if peer != nil { 41 | if v != SYS_USR { // 广播包不能投递给系统玩家 42 | send(peer.MQ) 43 | } 44 | } 45 | } 46 | 47 | DEBUG("BROADCAST Delivered to", len(users), "users") 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /src/agent/ipc_service/SYS_multicast.go: -------------------------------------------------------------------------------- 1 | package ipc_service 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | import ( 8 | "agent/gsdb" 9 | . "helper" 10 | . "types" 11 | ) 12 | 13 | //---------------------------------------------------------- 14 | // 组播包 15 | func SYS_multicast(sess *Session, obj *IPCObject) []byte { 16 | DEBUG("RECEIVED SYS_MULTICAST") 17 | realmsg := &IPCObject{} 18 | err := json.Unmarshal(obj.Object, realmsg) 19 | if err != nil { 20 | ERR("SYS_multicast cannot decode msg", err, obj.Object) 21 | return nil 22 | } 23 | 24 | // 投递closure 25 | send := func(MQ chan IPCObject) { 26 | defer func() { 27 | recover() 28 | }() 29 | MQ <- *realmsg 30 | } 31 | 32 | // 循环投递 33 | DEBUG("SERVICE:", realmsg.Service) 34 | for _, v := range obj.AuxIDs { 35 | peer := gsdb.QueryOnline(v) 36 | if peer != nil { 37 | // if target userid is SYS_USR, it's possible that SYS_USR's MQ is full, 38 | // and deadlock will happen, so, we use GO! 39 | if v == SYS_USR { 40 | go send(peer.MQ) 41 | } else { 42 | send(peer.MQ) 43 | } 44 | } 45 | } 46 | 47 | DEBUG("MULTICAST Delivered to", len(obj.AuxIDs), "users") 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /src/agent/ipc_service/proto.go: -------------------------------------------------------------------------------- 1 | package ipc_service 2 | 3 | import "misc/packet" 4 | 5 | type user_login_info struct { 6 | F_mac_addr string 7 | F_client_version int32 8 | F_new_user bool 9 | F_user_name string 10 | } 11 | 12 | type user_snapshot struct { 13 | F_id int32 14 | F_name string 15 | F_rank int32 16 | F_archives string 17 | F_protect_time int32 18 | F_last_save_time int32 19 | F_server_time int32 20 | } 21 | 22 | type command_result_pack struct { 23 | F_rst int32 24 | } 25 | 26 | type talk struct { 27 | F_user string 28 | F_msg string 29 | } 30 | 31 | func PKT_user_login_info(reader *packet.Packet) (tbl user_login_info, err error) { 32 | tbl.F_mac_addr, err = reader.ReadString() 33 | checkErr(err) 34 | 35 | tbl.F_client_version, err = reader.ReadS32() 36 | checkErr(err) 37 | 38 | tbl.F_new_user, err = reader.ReadBool() 39 | checkErr(err) 40 | 41 | tbl.F_user_name, err = reader.ReadString() 42 | checkErr(err) 43 | 44 | return 45 | } 46 | 47 | func PKT_user_snapshot(reader *packet.Packet) (tbl user_snapshot, err error) { 48 | tbl.F_id, err = reader.ReadS32() 49 | checkErr(err) 50 | 51 | tbl.F_name, err = reader.ReadString() 52 | checkErr(err) 53 | 54 | tbl.F_rank, err = reader.ReadS32() 55 | checkErr(err) 56 | 57 | tbl.F_archives, err = reader.ReadString() 58 | checkErr(err) 59 | 60 | tbl.F_protect_time, err = reader.ReadS32() 61 | checkErr(err) 62 | 63 | tbl.F_last_save_time, err = reader.ReadS32() 64 | checkErr(err) 65 | 66 | tbl.F_server_time, err = reader.ReadS32() 67 | checkErr(err) 68 | 69 | return 70 | } 71 | 72 | func PKT_command_result_pack(reader *packet.Packet) (tbl command_result_pack, err error) { 73 | tbl.F_rst, err = reader.ReadS32() 74 | checkErr(err) 75 | 76 | return 77 | } 78 | 79 | func PKT_talk(reader *packet.Packet) (tbl talk, err error) { 80 | tbl.F_user, err = reader.ReadString() 81 | checkErr(err) 82 | 83 | tbl.F_msg, err = reader.ReadString() 84 | checkErr(err) 85 | 86 | return 87 | } 88 | -------------------------------------------------------------------------------- /src/agent/ipc_service/services.go: -------------------------------------------------------------------------------- 1 | package ipc_service 2 | 3 | import ( 4 | "log" 5 | "runtime" 6 | ) 7 | 8 | import ( 9 | . "agent/ipc" 10 | . "types" 11 | ) 12 | 13 | var IPCHandler map[int16]func(*Session, *IPCObject) []byte = map[int16]func(*Session, *IPCObject) []byte{ 14 | SVC_PING: IPC_ping, 15 | SVC_CHAT: IPC_chat, 16 | SVC_KICK: IPC_kick, 17 | 18 | SYS_BROADCAST: SYS_broadcast, 19 | SYS_MULTICAST: SYS_multicast, 20 | } 21 | 22 | func checkErr(err error) { 23 | if err != nil { 24 | funcName, file, line, ok := runtime.Caller(1) 25 | if ok { 26 | log.Printf("ERR:%v,[func:%v,file:%v,line:%v]\n", err, runtime.FuncForPC(funcName).Name(), file, line) 27 | } 28 | 29 | panic("error occured in ipc_service module") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/agent/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "net" 7 | "net/http" 8 | _ "net/http/pprof" 9 | "os" 10 | "strconv" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | import ( 16 | "cfg" 17 | . "helper" 18 | "misc/geoip" 19 | . "types" 20 | ) 21 | 22 | func main() { 23 | defer func() { 24 | if x := recover(); x != nil { 25 | ERR("caught panic in main()", x) 26 | } 27 | }() 28 | 29 | go func() { 30 | INFO(http.ListenAndServe("0.0.0.0:6060", nil)) 31 | }() 32 | 33 | // start basic services 34 | startup() 35 | 36 | // Listen 37 | config := cfg.Get() 38 | service := ":8080" 39 | if config["service"] != "" { 40 | service = config["service"] 41 | } 42 | 43 | INFO("Service:", service) 44 | tcpAddr, err := net.ResolveTCPAddr("tcp4", service) 45 | checkError(err) 46 | 47 | listener, err := net.ListenTCP("tcp", tcpAddr) 48 | checkError(err) 49 | 50 | INFO("Game Server OK.") 51 | 52 | for { 53 | conn, err := listener.AcceptTCP() 54 | if err != nil { 55 | WARN("accept failed", err) 56 | continue 57 | } 58 | 59 | go handleClient(conn) 60 | } 61 | } 62 | 63 | //----------------------------------------------- start a goroutine when a new connection is accepted 64 | func handleClient(conn *net.TCPConn) { 65 | defer func() { 66 | if x := recover(); x != nil { 67 | ERR("caught panic in handleClient", x) 68 | } 69 | }() 70 | 71 | // input buffer 72 | config := cfg.Get() 73 | inqueue_size, err := strconv.Atoi(config["inqueue_size"]) 74 | if err != nil { 75 | inqueue_size = DEFAULT_INQUEUE_SIZE 76 | WARN("cannot parse inqueue_size from config", err, "using default:", inqueue_size) 77 | } 78 | 79 | // init 80 | header := make([]byte, 2) 81 | in := make(chan []byte, inqueue_size) 82 | bufctrl := make(chan bool) 83 | 84 | defer func() { 85 | close(bufctrl) 86 | close(in) 87 | }() 88 | 89 | // create new session 90 | var sess Session 91 | sess.IP = net.ParseIP(strings.Split(conn.RemoteAddr().String(), ":")[0]) 92 | NOTICE("new connection from:", sess.IP, "country:", geoip.Query(sess.IP)) 93 | 94 | // create write buffer 95 | out := NewBuffer(&sess, conn, bufctrl) 96 | go out.Start() 97 | 98 | // start agent!! 99 | wg.Add(1) 100 | go StartAgent(&sess, in, out) 101 | 102 | for { 103 | // header 104 | conn.SetReadDeadline(time.Now().Add(TCP_TIMEOUT * time.Second)) 105 | n, err := io.ReadFull(conn, header) 106 | if err != nil { 107 | WARN("error receiving header, bytes:", n, "reason:", err) 108 | break 109 | } 110 | 111 | // data 112 | size := binary.BigEndian.Uint16(header) 113 | data := make([]byte, size) 114 | n, err = io.ReadFull(conn, data) 115 | if err != nil { 116 | WARN("error receiving msg, bytes:", n, "reason:", err) 117 | break 118 | } 119 | 120 | // NOTICE: slice is passed by reference; don't re-use a single buffer. 121 | select { 122 | case in <- data: 123 | case <-time.After(MAX_DELAY_IN * time.Second): 124 | WARN("server busy or agent closed, session flag:", sess.Flag) 125 | return 126 | } 127 | } 128 | } 129 | 130 | func checkError(err error) { 131 | if err != nil { 132 | ERR("Fatal error:", err) 133 | os.Exit(-1) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/agent/net/api.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import "misc/packet" 4 | import . "types" 5 | 6 | var Code = map[string]int16{ 7 | "heart_beat_req": 0, // 心跳包.. 8 | "user_login_req": 1, // 客户端发送用户登陆请求包 9 | "user_login_succeed_ack": 2, // 登陆成功 10 | "user_login_faild_ack": 3, // 登陆失败 11 | "talk_req": 1000, // talk给一个用户 12 | "talk_notify": 1001, // notify客户端 13 | } 14 | 15 | var RCode = map[int16]string{ 16 | 0: "heart_beat_req", // 心跳包.. 17 | 1: "user_login_req", // 客户端发送用户登陆请求包 18 | 2: "user_login_succeed_ack", // 登陆成功 19 | 3: "user_login_faild_ack", // 登陆失败 20 | 1000: "talk_req", // talk给一个用户 21 | 1001: "talk_notify", // notify客户端 22 | } 23 | 24 | var ProtoHandler map[int16]func(*Session, *packet.Packet) []byte 25 | 26 | func init() { 27 | ProtoHandler = map[int16]func(*Session, *packet.Packet) []byte{ 28 | 0: P_heart_beat_req, 29 | 1: P_user_login_req, 30 | 1000: P_talk_req, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/agent/net/handle.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "log" 5 | ) 6 | 7 | import ( 8 | "agent/AI" 9 | "agent/ipc" 10 | "db/user_tbl" 11 | . "helper" 12 | "misc/packet" 13 | . "types" 14 | ) 15 | 16 | func P_heart_beat_req(sess *Session, reader *packet.Packet) []byte { 17 | // nothing should be done 18 | return nil 19 | } 20 | 21 | func P_user_login_req(sess *Session, reader *packet.Packet) []byte { 22 | tbl, _ := PKT_user_login_info(reader) 23 | ret := command_result_pack{} 24 | 25 | if user := user_tbl.LoginMac(tbl.F_user_name, tbl.F_mac_addr); user != nil { 26 | sess.User = user 27 | AI.LoginProc(sess) 28 | } 29 | 30 | return packet.Pack(Code["user_login_succeed_ack"], &ret, nil) 31 | } 32 | 33 | func P_talk_req(sess *Session, reader *packet.Packet) []byte { 34 | tbl, _ := PKT_talk(reader) 35 | dest := user_tbl.Query(tbl.F_user) 36 | chat := Words{tbl.F_msg, sess.User.Name} 37 | if dest != nil { 38 | ipc.Send(sess.User.Id, dest.Id, ipc.SVC_CHAT, &chat) 39 | } else { 40 | log.Println("no such user :", tbl.F_user) 41 | } 42 | return nil 43 | } 44 | 45 | func checkErr(err error) { 46 | if err != nil { 47 | ERR(err) 48 | panic("error occured in protocol module") 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/agent/net/proto.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import "misc/packet" 4 | 5 | type user_login_info struct { 6 | F_mac_addr string 7 | F_client_version int32 8 | F_new_user bool 9 | F_user_name string 10 | } 11 | 12 | type user_snapshot struct { 13 | F_id int32 14 | F_name string 15 | F_rank int32 16 | F_archives string 17 | F_protect_time int32 18 | F_last_save_time int32 19 | F_server_time int32 20 | } 21 | 22 | type command_result_pack struct { 23 | F_rst int32 24 | } 25 | 26 | type talk struct { 27 | F_user string 28 | F_msg string 29 | } 30 | 31 | func PKT_user_login_info(reader *packet.Packet) (tbl user_login_info, err error) { 32 | tbl.F_mac_addr, err = reader.ReadString() 33 | checkErr(err) 34 | 35 | tbl.F_client_version, err = reader.ReadS32() 36 | checkErr(err) 37 | 38 | tbl.F_new_user, err = reader.ReadBool() 39 | checkErr(err) 40 | 41 | tbl.F_user_name, err = reader.ReadString() 42 | checkErr(err) 43 | 44 | return 45 | } 46 | 47 | func PKT_user_snapshot(reader *packet.Packet) (tbl user_snapshot, err error) { 48 | tbl.F_id, err = reader.ReadS32() 49 | checkErr(err) 50 | 51 | tbl.F_name, err = reader.ReadString() 52 | checkErr(err) 53 | 54 | tbl.F_rank, err = reader.ReadS32() 55 | checkErr(err) 56 | 57 | tbl.F_archives, err = reader.ReadString() 58 | checkErr(err) 59 | 60 | tbl.F_protect_time, err = reader.ReadS32() 61 | checkErr(err) 62 | 63 | tbl.F_last_save_time, err = reader.ReadS32() 64 | checkErr(err) 65 | 66 | tbl.F_server_time, err = reader.ReadS32() 67 | checkErr(err) 68 | 69 | return 70 | } 71 | 72 | func PKT_command_result_pack(reader *packet.Packet) (tbl command_result_pack, err error) { 73 | tbl.F_rst, err = reader.ReadS32() 74 | checkErr(err) 75 | 76 | return 77 | } 78 | 79 | func PKT_talk(reader *packet.Packet) (tbl talk, err error) { 80 | tbl.F_user, err = reader.ReadString() 81 | checkErr(err) 82 | 83 | tbl.F_msg, err = reader.ReadString() 84 | checkErr(err) 85 | 86 | return 87 | } 88 | -------------------------------------------------------------------------------- /src/agent/proxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | ) 7 | 8 | import ( 9 | "agent/ipc_service" 10 | "agent/net" 11 | . "helper" 12 | "misc/packet" 13 | . "types" 14 | ) 15 | 16 | //----------------------------------------------- client protocol handle proxy 17 | func UserRequestProxy(sess *Session, p []byte) []byte { 18 | defer PrintPanicStack() 19 | // decrypt 20 | if sess.Flag&SESS_ENCRYPT != 0 { 21 | sess.Decoder.Codec(p) 22 | } 23 | 24 | // encapsulate into reader 25 | reader := packet.Reader(p) 26 | 27 | // client timestamp check 28 | // mainly for REPLAY-ATTACK 29 | client_elapsed, err := reader.ReadU32() 30 | if err != nil { 31 | ERR("read client timestamp failed.", err) 32 | sess.Flag |= SESS_KICKED_OUT 33 | return nil 34 | } 35 | 36 | client_time := sess.ConnectTime.Unix() + int64(client_elapsed)/1000 37 | now := time.Now().Unix() 38 | if client_time > now+PACKET_ERROR || client_time < now-PACKET_EXPIRE { 39 | ERR("client timestamp is illegal.", client_elapsed, client_time, now) 40 | sess.Flag |= SESS_KICKED_OUT 41 | return nil 42 | } 43 | 44 | // read protocol number 45 | b, err := reader.ReadS16() 46 | if err != nil { 47 | ERR("read protocol number failed.") 48 | sess.Flag |= SESS_KICKED_OUT 49 | return nil 50 | } 51 | 52 | // handle validation 53 | handle := net.ProtoHandler[b] 54 | if handle == nil { 55 | ERR("service id", b, "not bind") 56 | sess.Flag |= SESS_KICKED_OUT 57 | return nil 58 | } 59 | 60 | // before HOOK 61 | if !_before_hook(sess, b) { 62 | ERR("before hook failed, code", b) 63 | sess.Flag |= SESS_KICKED_OUT 64 | return nil 65 | } 66 | 67 | // handle packet 68 | start := time.Now() 69 | ret := handle(sess, reader) 70 | end := time.Now() 71 | 72 | uid := int32(-1) 73 | name := "" 74 | if sess.Flag&SESS_LOGGED_IN != 0 { 75 | uid = sess.User.Id 76 | name = sess.User.Name 77 | } 78 | 79 | log.Printf("\033[0;36m[REQ] %v\tbytes[in:%v out:%v seq:%v]\tusr:[%v %v]\ttime:%v\033[0m\n", net.RCode[b], len(p)-6, len(ret), sess.PacketCount, uid, name, end.Sub(start)) 80 | // after HOOK 81 | _after_hook(sess, net.RCode[b]) 82 | sess.MarkDirty() 83 | return ret 84 | } 85 | 86 | //----------------------------------------------- IPC proxy 87 | func IPCRequestProxy(sess *Session, p *IPCObject) []byte { 88 | defer PrintPanicStack() 89 | 90 | // handle validation 91 | handle := ipc_service.IPCHandler[p.Service] 92 | if handle == nil { 93 | ERR("ipc service", p.Service, "not bind, internal service error.") 94 | return nil 95 | } 96 | 97 | // process IPCObject 98 | start := time.Now() 99 | ret := handle(sess, p) 100 | end := time.Now() 101 | log.Printf("\033[0;36m[IPC] %v\t%v\033[0m\n", p.Service, end.Sub(start)) 102 | 103 | sess.MarkDirty() 104 | return ret 105 | } 106 | 107 | //---------------------------------------------------------- after hook 108 | func _after_hook(sess *Session, rcode string) { 109 | if sess.Flag&SESS_LOGGED_IN == 0 { 110 | return 111 | } 112 | } 113 | 114 | //---------------------------------------------------------- before hook 115 | func _before_hook(sess *Session, rcode int16) bool { 116 | return true 117 | } 118 | -------------------------------------------------------------------------------- /src/agent/rank/rank.go: -------------------------------------------------------------------------------- 1 | package rank 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | import ( 8 | "db/user_tbl" 9 | . "helper" 10 | "misc/alg/dos" 11 | ) 12 | 13 | var ( 14 | _ranklist dos.Tree // dynamic order statistics 15 | _id_cup map[int32]int32 16 | _lock_ranklist sync.RWMutex 17 | ) 18 | 19 | func init() { 20 | INFO("Loading Dynamic Order Statstistics.") 21 | _id_cup = make(map[int32]int32) 22 | 23 | go _init_ranks() 24 | } 25 | 26 | //---------------------------------------------------------- 异步加载排行榜 27 | func _init_ranks() { 28 | // load users 29 | uds := user_tbl.GetAll() 30 | for i := range uds { 31 | Update(uds[i].Id, uds[i].Score) 32 | } 33 | INFO("Dynamic Order Statstistics Load Complete.") 34 | } 35 | 36 | //---------------------------------------------------------- update cup of a player 37 | func Update(id, newcup int32) bool { 38 | _lock_ranklist.Lock() 39 | defer _lock_ranklist.Unlock() 40 | 41 | oldcup, ok := _id_cup[id] 42 | if !ok { // new user 43 | _ranklist.Insert(newcup, id) 44 | _id_cup[id] = newcup 45 | return true 46 | } else { // old user 47 | _, n := _ranklist.Locate(oldcup, id) 48 | if n == nil { 49 | ERR("没有在DOS中查到玩家", id) 50 | return false 51 | } 52 | 53 | _ranklist.Delete(id, n) 54 | _ranklist.Insert(newcup, id) 55 | _id_cup[id] = newcup 56 | return true 57 | } 58 | } 59 | 60 | //---------------------------------------------------------- get players from ranklist within [A,B] 61 | func GetList(A, B int) (id []int32, cup []int32) { 62 | if A < 1 || A > B { 63 | return 64 | } 65 | 66 | _lock_ranklist.RLock() 67 | defer _lock_ranklist.RUnlock() 68 | 69 | if A > _ranklist.Count() { 70 | return 71 | } 72 | 73 | if B > _ranklist.Count() { 74 | B = _ranklist.Count() 75 | } 76 | 77 | id, cup = make([]int32, B-A+1), make([]int32, B-A+1) 78 | for i := A; i <= B; i++ { 79 | nid, n := _ranklist.Rank(i) 80 | id[i-A] = nid 81 | cup[i-A] = n.Score() 82 | } 83 | 84 | return 85 | } 86 | 87 | //---------------------------------------------------------- get user of rank n 88 | func RankN(n int32) int32 { 89 | _lock_ranklist.RLock() 90 | defer _lock_ranklist.RUnlock() 91 | id, node := _ranklist.Rank(int(n)) 92 | if node != nil { 93 | return id 94 | } 95 | return -1 96 | } 97 | 98 | //---------------------------------------------------------- get rank for a user 99 | func Rank(id int32) int32 { 100 | _lock_ranklist.Lock() 101 | defer _lock_ranklist.Unlock() 102 | rank, _ := _ranklist.Locate(_id_cup[id], id) 103 | return int32(rank) 104 | } 105 | -------------------------------------------------------------------------------- /src/agent/signal.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | ) 9 | 10 | import ( 11 | "cfg" 12 | "gamedata" 13 | . "helper" 14 | ) 15 | 16 | //----------------------------------------------- handle unix signals 17 | func SignalProc() { 18 | ch := make(chan os.Signal, 1) 19 | signal.Notify(ch, syscall.SIGHUP, syscall.SIGTERM) 20 | 21 | for { 22 | msg := <-ch 23 | switch msg { 24 | case syscall.SIGHUP: // reload config 25 | log.Println("\033[043;1m[SIGHUP]\033[0m") 26 | cfg.Reload() 27 | gamedata.Reload() 28 | case syscall.SIGTERM: // server close 29 | close(die) 30 | log.Println("\033[043;1m[SIGTERM]\033[0m") 31 | INFO("waiting for agents close, please wait...") 32 | wg.Wait() 33 | INFO("all work done. bye bye!!!") 34 | os.Exit(0) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/agent/startup_work.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "agent/hub_client" 5 | "agent/stats_client" 6 | "cfg" 7 | . "helper" 8 | ) 9 | 10 | //---------------------------------------------------------- game server start-up procedure 11 | func startup() { 12 | INFO("Starting GS.") 13 | // start logger 14 | config := cfg.Get() 15 | if config["gs_log"] != "" { 16 | cfg.StartLogger(config["gs_log"]) 17 | } 18 | 19 | // dial HUB 20 | hub_client.DialHub() 21 | 22 | // signal 23 | go SignalProc() 24 | 25 | // sys routine 26 | go SysRoutine() 27 | 28 | // stats 29 | go stats_client.DialStats() 30 | } 31 | -------------------------------------------------------------------------------- /src/agent/stats_client/README.md: -------------------------------------------------------------------------------- 1 | 统计服务器的函数封装 2 | -------------------------------------------------------------------------------- /src/agent/stats_client/api.go: -------------------------------------------------------------------------------- 1 | package stats_client 2 | 3 | var Code = map[string]int16{ 4 | "set_adds_req": 100, // 累计信息 5 | "set_update_req": 200, // 更新信息 6 | } 7 | 8 | var RCode = map[int16]string{ 9 | 100: "set_adds_req", // 累计信息 10 | 200: "set_update_req", // 更新信息 11 | } 12 | -------------------------------------------------------------------------------- /src/agent/stats_client/main.go: -------------------------------------------------------------------------------- 1 | package stats_client 2 | 3 | import ( 4 | "net" 5 | "os" 6 | "runtime" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | import ( 12 | "cfg" 13 | . "helper" 14 | "misc/packet" 15 | "misc/timer" 16 | ) 17 | 18 | const ( 19 | STATS_QUEUE_SIZE = 100000 20 | STATS_COLLECT_PERIOD = 30 //secs 21 | ) 22 | 23 | var _conn net.Conn 24 | var _seq_id uint64 25 | 26 | var ( 27 | AccumQueue chan SET_ADDS_REQ 28 | UpdateQueue chan SET_UPDATE_REQ 29 | ) 30 | 31 | func init() { 32 | AccumQueue = make(chan SET_ADDS_REQ, STATS_QUEUE_SIZE) 33 | UpdateQueue = make(chan SET_UPDATE_REQ, STATS_QUEUE_SIZE) 34 | 35 | go stats_sender() 36 | } 37 | 38 | //----------------------------------------------- connect to Stats server 39 | func DialStats() { 40 | INFO("Connecting to Stats server") 41 | config := cfg.Get() 42 | 43 | addr, err := net.ResolveUDPAddr("udp", config["stats_service"]) 44 | if err != nil { 45 | ERR(err) 46 | os.Exit(-1) 47 | return 48 | } 49 | 50 | conn, err := net.DialUDP("udp", nil, addr) 51 | if err != nil { 52 | ERR(err) 53 | os.Exit(-1) 54 | return 55 | } 56 | 57 | _conn = conn 58 | INFO("Stats Service Connected") 59 | } 60 | 61 | //------------------------------------------------ send msg to stats server 62 | func Send(data []byte) bool { 63 | // send the packet 64 | _, err := _conn.Write(data) 65 | if err != nil { 66 | ERR("Error send packet to Stats Server:", err) 67 | return false 68 | } 69 | return true 70 | } 71 | 72 | func stats_sender() { 73 | _accum_buffer := make(map[string]map[string]int32) 74 | _update_buffer := make(map[string]map[string]string) 75 | 76 | stats_timer := make(chan int32, 100) 77 | stats_timer <- 1 78 | for { 79 | select { 80 | case req := <-AccumQueue: 81 | if _, ok := _accum_buffer[req.F_lang]; !ok { 82 | val := make(map[string]int32) 83 | val[req.F_key] = 0 84 | _accum_buffer[req.F_lang] = val 85 | } 86 | val := _accum_buffer[req.F_lang] 87 | val[req.F_key] += req.F_value 88 | _accum_buffer[req.F_lang] = val 89 | case req := <-UpdateQueue: 90 | if _, ok := _update_buffer[req.F_lang]; !ok { 91 | val := make(map[string]string) 92 | val[req.F_key] = "" 93 | _update_buffer[req.F_lang] = val 94 | } 95 | val := _update_buffer[req.F_lang] 96 | val[req.F_key] = req.F_value 97 | _update_buffer[req.F_lang] = val 98 | case <-stats_timer: 99 | INFO("Stats Buffer:", len(_accum_buffer), len(_update_buffer)) 100 | // 累计 101 | accum := SET_ADDS_REQ{} 102 | for accum.F_lang, _ = range _accum_buffer { 103 | for accum.F_key, accum.F_value = range _accum_buffer[accum.F_lang] { 104 | Send(packet.Pack(Code["set_adds_req"], &accum, nil)) 105 | } 106 | } 107 | _accum_buffer = make(map[string]map[string]int32) 108 | 109 | // 更新 110 | update := SET_UPDATE_REQ{} 111 | for update.F_lang, _ = range _update_buffer { 112 | for update.F_key, update.F_value = range _update_buffer[update.F_lang] { 113 | Send(packet.Pack(Code["set_update_req"], &update, nil)) 114 | } 115 | } 116 | _update_buffer = make(map[string]map[string]string) 117 | 118 | // FINI 119 | config := cfg.Get() 120 | period := STATS_COLLECT_PERIOD 121 | if config["stats_collect_period"] != "" { 122 | period, _ = strconv.Atoi(config["stats_collect_period"]) 123 | } 124 | 125 | timer.Add(0, time.Now().Unix()+int64(period), stats_timer) 126 | runtime.GC() 127 | } 128 | } 129 | } 130 | 131 | func checkErr(err error) { 132 | if err != nil { 133 | ERR(err) 134 | panic("error occured in protocol module") 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/agent/stats_client/proto.go: -------------------------------------------------------------------------------- 1 | package stats_client 2 | 3 | import "misc/packet" 4 | 5 | type SET_ADDS_REQ struct { 6 | F_key string 7 | F_value int32 8 | F_lang string 9 | } 10 | 11 | type SET_UPDATE_REQ struct { 12 | F_key string 13 | F_value string 14 | F_lang string 15 | } 16 | 17 | func PKT_SET_ADDS_REQ(reader *packet.Packet) (tbl SET_ADDS_REQ, err error) { 18 | tbl.F_key, err = reader.ReadString() 19 | checkErr(err) 20 | 21 | tbl.F_value, err = reader.ReadS32() 22 | checkErr(err) 23 | 24 | tbl.F_lang, err = reader.ReadString() 25 | checkErr(err) 26 | 27 | return 28 | } 29 | 30 | func PKT_SET_UPDATE_REQ(reader *packet.Packet) (tbl SET_UPDATE_REQ, err error) { 31 | tbl.F_key, err = reader.ReadString() 32 | checkErr(err) 33 | 34 | tbl.F_value, err = reader.ReadString() 35 | checkErr(err) 36 | 37 | tbl.F_lang, err = reader.ReadString() 38 | checkErr(err) 39 | 40 | return 41 | } 42 | -------------------------------------------------------------------------------- /src/agent/sys.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | "time" 6 | ) 7 | 8 | import ( 9 | "agent/gsdb" 10 | . "helper" 11 | "misc/timer" 12 | . "types" 13 | ) 14 | 15 | //---------------------------------------------------------- system routine 16 | func SysRoutine() { 17 | var sess Session 18 | sess.MQ = make(chan IPCObject, SYS_MQ_SIZE) 19 | gsdb.RegisterOnline(&sess, SYS_USR) 20 | 21 | // timer 22 | gc_timer := make(chan int32, 10) 23 | gc_timer <- 1 24 | 25 | for { 26 | select { 27 | case msg, ok := <-sess.MQ: // IPCObject to system routine 28 | if !ok { 29 | return 30 | } 31 | IPCRequestProxy(&sess, &msg) 32 | case <-gc_timer: 33 | runtime.GC() 34 | INFO("== PERFORMANCE LOG ==") 35 | INFO("Goroutine Count:", runtime.NumGoroutine()) 36 | INFO("GC Summary:", GCSummary()) 37 | INFO("Sysroutine MQ size:", len(sess.MQ)) 38 | timer.Add(0, time.Now().Unix()+GC_INTERVAL, gc_timer) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/agent/timer_work.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | ) 7 | 8 | import ( 9 | "cfg" 10 | "helper" 11 | . "types" 12 | ) 13 | 14 | //----------------------------------------------- user's timer 15 | func timer_work(sess *Session) { 16 | if sess.Flag&SESS_LOGGED_IN == 0 { 17 | return 18 | } 19 | 20 | // limit rate of request per minute 21 | config := cfg.Get() 22 | rpm_limit, _ := strconv.ParseFloat(config["rpm_limit"], 32) 23 | rpm := float64(sess.PacketCount) / float64(time.Now().Unix()-sess.ConnectTime.Unix()) * 60 24 | 25 | if rpm > rpm_limit { 26 | sess.Flag |= SESS_KICKED_OUT 27 | helper.ERR("user RPM too high", sess.User.Id, sess.User.Name, "RPM:", rpm) 28 | return 29 | } 30 | 31 | // try save the data 32 | _flush_work(sess) 33 | } 34 | -------------------------------------------------------------------------------- /src/cfg/README.md: -------------------------------------------------------------------------------- 1 | #### 系统配置文件读取 2 | 3 | 通过Get()获取配置Hash表。 4 | 通过Reload()函数重新载入config.ini. 5 | -------------------------------------------------------------------------------- /src/cfg/config.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | "bufio" 5 | "log" 6 | "os" 7 | "regexp" 8 | "strings" 9 | "sync" 10 | ) 11 | 12 | var _DEF_CONFIG = os.Getenv("GOPATH") + "/config.ini" 13 | 14 | var ( 15 | _map map[string]string 16 | _lock sync.Mutex 17 | ) 18 | 19 | func init() { 20 | Reload() 21 | } 22 | 23 | func Get() map[string]string { 24 | _lock.Lock() 25 | defer _lock.Unlock() 26 | return _map 27 | } 28 | 29 | func Reload() { 30 | var path string 31 | if path = os.Getenv("GONET_CONFIG"); path == "" { 32 | path = _DEF_CONFIG 33 | } 34 | 35 | _lock.Lock() 36 | log.Println("Loading Config from:", path) 37 | defer log.Println("Config Loaded.") 38 | _map = _load_config(path) 39 | _lock.Unlock() 40 | } 41 | 42 | func _load_config(path string) (ret map[string]string) { 43 | ret = make(map[string]string) 44 | f, err := os.Open(path) 45 | if err != nil { 46 | log.Println(path, err) 47 | return 48 | } 49 | 50 | re := regexp.MustCompile(`[\t ]*([0-9A-Za-z_]+)[\t ]*=[\t ]*([^\t\n\f\r# ]+)[\t #]*`) 51 | 52 | // using scanner to read config file 53 | scanner := bufio.NewScanner(f) 54 | scanner.Split(bufio.ScanLines) 55 | 56 | for scanner.Scan() { 57 | line := strings.TrimSpace(scanner.Text()) 58 | // expression match 59 | slice := re.FindStringSubmatch(line) 60 | 61 | if slice != nil { 62 | ret[slice[1]] = slice[2] 63 | log.Println(slice[1], "=", slice[2]) 64 | } 65 | } 66 | 67 | return 68 | } 69 | -------------------------------------------------------------------------------- /src/cfg/config_test.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestReadConfig(t *testing.T) { 9 | config := Get() 10 | 11 | for k, v := range config { 12 | fmt.Println(k, "=", v) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/cfg/dup.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import "io" 4 | 5 | type Repeater struct { 6 | out1, out2 io.Writer // 1-input -> 2-output 7 | } 8 | 9 | //----------------------------------------------- output replicator 10 | func (r *Repeater) Write(p []byte) (int, error) { 11 | var n int 12 | var e error 13 | 14 | if r.out1 != nil { 15 | n, e = r.out1.Write(p) 16 | } 17 | 18 | if r.out2 != nil { 19 | n, e = r.out2.Write(p) 20 | } 21 | 22 | return n, e 23 | } 24 | -------------------------------------------------------------------------------- /src/cfg/logger.go: -------------------------------------------------------------------------------- 1 | package cfg 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | //---------------------------------------------------------- 通用系统日志 10 | func GetLogger(path string) *log.Logger { 11 | if !strings.HasPrefix(path, "/") { 12 | path = os.Getenv("GOPATH") + "/" + path 13 | } 14 | 15 | // 打开文件 16 | file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666) 17 | if err != nil { 18 | log.Println("error opening file %v\n", err) 19 | return nil 20 | } 21 | 22 | // 日志 23 | logger := log.New(file, "", log.LstdFlags) 24 | return logger 25 | } 26 | 27 | //---------------------------------------------------------- 同步系统日志 28 | // 用于记录至关重要的日志数据(用 O_SYNC实现) 29 | func GetSyncLogger(path string) *log.Logger { 30 | if !strings.HasPrefix(path, "/") { 31 | path = os.Getenv("GOPATH") + "/" + path 32 | } 33 | file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0666) 34 | if err != nil { 35 | log.Println("error opening file %v\n", err) 36 | return nil 37 | } 38 | 39 | logger := log.New(file, "", log.LstdFlags) 40 | return logger 41 | } 42 | 43 | //---------------------------------------------------------- 默认系统日志 44 | func StartLogger(path string) { 45 | if !strings.HasPrefix(path, "/") { 46 | path = os.Getenv("GOPATH") + "/" + path 47 | } 48 | 49 | // 打开日志文件 50 | file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) 51 | if err != nil { 52 | log.Println("cannot open logfile %v\n", err) 53 | } 54 | 55 | // 创建MUX 56 | var r Repeater 57 | config := Get() 58 | switch config["log_output"] { 59 | case "both": 60 | r.out1 = os.Stdout 61 | r.out2 = file 62 | case "file": 63 | r.out2 = file 64 | } 65 | log.SetOutput(&r) 66 | } 67 | -------------------------------------------------------------------------------- /src/db/README.md: -------------------------------------------------------------------------------- 1 | 数据库持久化模块 2 | 3 | 采用mongodb做后端,存储JSON文档。 4 | -------------------------------------------------------------------------------- /src/db/data_tbl/README.md: -------------------------------------------------------------------------------- 1 | #### 通用数据表 GET/SET 操作 2 | 3 | 注意: 表必须含有字段: 4 | * Version : uint32 5 | * UserId : int32 6 | -------------------------------------------------------------------------------- /src/db/data_tbl/data_tbl.go: -------------------------------------------------------------------------------- 1 | package data_tbl 2 | 3 | import ( 4 | "gopkg.in/mgo.v2" 5 | "log" 6 | "reflect" 7 | ) 8 | 9 | import ( 10 | . "db" 11 | ) 12 | 13 | //------------------------------------------------ pass-in *ptr 14 | func Set(collection string, data interface{}) bool { 15 | ms, c := C(collection) 16 | defer ms.Close() 17 | v := reflect.ValueOf(data).Elem() 18 | 19 | version := v.FieldByName("Version") 20 | 21 | if !version.IsValid() { 22 | log.Println(`Cannot seriazlie a struct without "Version" Field`) 23 | return false 24 | } 25 | version.SetUint(uint64(version.Interface().(uint32) + 1)) 26 | 27 | id := v.FieldByName("UserId") 28 | 29 | if !id.IsValid() { 30 | log.Println(`Cannot seriazlie a struct without "UserId" Field`) 31 | return false 32 | } 33 | 34 | info, err := c.Upsert(bson.M{"userid": id.Interface().(int32)}, data) 35 | if err != nil { 36 | log.Println(info, err) 37 | return false 38 | } 39 | 40 | return true 41 | } 42 | 43 | //------------------------------------------------ pass-in *ptr or **ptr 44 | func Get(collection string, user_id int32, data interface{}) bool { 45 | ms, c := C(collection) 46 | defer ms.Close() 47 | 48 | err := c.Find(bson.M{"userid": user_id}).One(data) 49 | if err != nil { 50 | log.Println(err, collection, user_id) 51 | return false 52 | } 53 | 54 | return true 55 | } 56 | 57 | //------------------------------------------------ pass-in *[]slice 58 | func GetAll(collection string, all interface{}) bool { 59 | ms, c := C(collection) 60 | defer ms.Close() 61 | 62 | err := c.Find(nil).All(all) 63 | if err != nil { 64 | log.Println(err, collection) 65 | return false 66 | } 67 | 68 | return true 69 | } 70 | -------------------------------------------------------------------------------- /src/db/forward_tbl/README.md: -------------------------------------------------------------------------------- 1 | P2P消息转发IPCObject类型的存储 2 | -------------------------------------------------------------------------------- /src/db/forward_tbl/forward.go: -------------------------------------------------------------------------------- 1 | package forward_tbl 2 | 3 | import ( 4 | "gopkg.in/mgo.v2/bson" 5 | "log" 6 | ) 7 | 8 | import ( 9 | . "db" 10 | . "types" 11 | ) 12 | 13 | const ( 14 | COLLECTION = "FORWARDS" 15 | ) 16 | 17 | //---------------------------------------------------------- push an ipc object to db 18 | func Push(req *IPCObject) bool { 19 | ms, c := C(COLLECTION) 20 | defer ms.Close() 21 | req.MarkDelete = false 22 | err := c.Insert(req) 23 | if err != nil { 24 | log.Println(err, req) 25 | return false 26 | } 27 | 28 | return true 29 | } 30 | 31 | //---------------------------------------------------------- pop all message for dest user 32 | func PopAll(dest_id int32) []IPCObject { 33 | ms, c := C(COLLECTION) 34 | defer ms.Close() 35 | 36 | var objects []IPCObject 37 | // mark delete 38 | info, err := c.UpdateAll(bson.M{"destid": dest_id}, bson.M{"$set": bson.M{"markdelete": true}}) 39 | if err != nil { 40 | log.Println(err, info) 41 | } 42 | 43 | // select 44 | err = c.Find(bson.M{"destid": dest_id, "markdelete": true}).Sort("$natural").All(&objects) 45 | if err != nil { 46 | log.Println(err) 47 | } 48 | 49 | // real delete 50 | info, err = c.RemoveAll(bson.M{"destid": dest_id, "markdelete": true}) 51 | if err != nil { 52 | log.Println(err, info) 53 | } 54 | 55 | return objects 56 | } 57 | -------------------------------------------------------------------------------- /src/db/mongo.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "gopkg.in/mgo.v2" 5 | "gopkg.in/mgo.v2/bson" 6 | "os" 7 | ) 8 | 9 | import ( 10 | "cfg" 11 | . "helper" 12 | ) 13 | 14 | var _global_ms *mgo.Session 15 | 16 | const ( 17 | COUNTERS = "COUNTERS" 18 | ) 19 | 20 | type Counter struct { 21 | Name string 22 | NextVal int64 23 | } 24 | 25 | func init() { 26 | config := cfg.Get() 27 | // dial mongodb 28 | sess, err := mgo.Dial(config["mongo_host"]) 29 | if err != nil { 30 | ERR("cannot connect to", config["mongo_host"], err) 31 | os.Exit(-1) 32 | } 33 | 34 | // set default session mode to strong for saving player's data 35 | sess.SetMode(mgo.Strong, true) 36 | _global_ms = sess 37 | } 38 | 39 | //------------------------------------------------ copy connection 40 | // !IMPORTANT! NEVER FORGET -----> defer ms.Close() <----- 41 | func C(collection string) (*mgo.Session, *mgo.Collection) { 42 | config := cfg.Get() 43 | ms := _global_ms.Copy() 44 | c := ms.DB(config["mongo_db"]).C(collection) 45 | return ms, c 46 | } 47 | 48 | //---------------------------------------------------------- ID GENERATOR 49 | func NextVal(countername string) int32 { 50 | ms, c := C(COUNTERS) 51 | defer ms.Close() 52 | 53 | change := mgo.Change{ 54 | Update: bson.M{"$inc": bson.M{"nextval": 1}}, 55 | Upsert: true, 56 | ReturnNew: true, 57 | } 58 | 59 | next := &Counter{} 60 | info, err := c.Find(bson.M{"name": countername}).Apply(change, &next) 61 | if err != nil { 62 | ERR(info, err) 63 | return -1 64 | } 65 | 66 | // round the nextval to 2^31 67 | return int32(next.NextVal % 2147483648) 68 | } 69 | -------------------------------------------------------------------------------- /src/db/stats_tbl/stats.go: -------------------------------------------------------------------------------- 1 | package stats_tbl 2 | 3 | import ( 4 | "cfg" 5 | "gopkg.in/mgo.v2" 6 | "gopkg.in/mgo.v2/bson" 7 | "os" 8 | "time" 9 | ) 10 | 11 | import ( 12 | . "helper" 13 | ) 14 | 15 | var _stats_db *mgo.Session // statsdb session 16 | 17 | const ( 18 | INT_GAME_INFO = "INT_GAME_INFO" 19 | STR_GAME_INFO = "STR_GAME_INFO" 20 | ) 21 | 22 | type IntGameInfo struct { 23 | IntValue int32 24 | Key string 25 | Time time.Time 26 | Lang string 27 | } 28 | 29 | type StrGameInfo struct { 30 | StrValue string 31 | Key string 32 | Time time.Time 33 | Lang string 34 | } 35 | 36 | func init() { 37 | config := cfg.Get() 38 | // dial mongodb 39 | sess, err := mgo.Dial(config["mongo_host_stats"]) 40 | if err != nil { 41 | ERR(err) 42 | os.Exit(-1) 43 | } 44 | 45 | // set default session mode to eventual 46 | sess.SetMode(mgo.Eventual, true) 47 | _stats_db = sess 48 | } 49 | 50 | //------------------------------------------------ copy connection 51 | func C(collection string) (*mgo.Session, *mgo.Collection) { 52 | config := cfg.Get() 53 | ms := _stats_db.Copy() 54 | c := ms.DB(config["mongo_db_stats"]).C(collection) 55 | return ms, c 56 | } 57 | 58 | //------------------------------------------------------------ 记录玩家累计信息 59 | func SetAdds(key string, value int32, lang string) bool { 60 | add_info := IntGameInfo{ 61 | IntValue: value, 62 | Key: key, 63 | Time: time.Now().UTC(), 64 | Lang: lang, 65 | } 66 | ms, c := C(INT_GAME_INFO) 67 | defer ms.Close() 68 | 69 | err := c.Insert(add_info) 70 | if err != nil { 71 | ERR(INT_GAME_INFO, "SetAdds", err, key, value, lang) 72 | return false 73 | } 74 | return true 75 | } 76 | 77 | //------------------------------------------------------------ 记录玩家状态信息 78 | func SetUpdate(key, value, lang string) bool { 79 | ms, c := C(STR_GAME_INFO) 80 | defer ms.Close() 81 | info := StrGameInfo{value, key, time.Now().UTC(), lang} 82 | _, err := c.Upsert(bson.M{"key": key}, info) 83 | if err != nil { 84 | ERR(STR_GAME_INFO, "SetUpdate", err, key, value, lang) 85 | } 86 | return true 87 | } 88 | -------------------------------------------------------------------------------- /src/db/user_tbl/README.md: -------------------------------------------------------------------------------- 1 | 用户基本信息表 2 | -------------------------------------------------------------------------------- /src/db/user_tbl/user.go: -------------------------------------------------------------------------------- 1 | package user_tbl 2 | 3 | import ( 4 | "crypto/md5" 5 | "gopkg.in/mgo.v2/bson" 6 | "io" 7 | "log" 8 | "time" 9 | ) 10 | 11 | import ( 12 | "cfg" 13 | . "db" 14 | . "types" 15 | ) 16 | 17 | const ( 18 | COLLECTION = "USERS" 19 | COUNTER_NAME = "USERID_GEN" 20 | ) 21 | 22 | //---------------------------------------------------------- update a user 23 | func Set(user *User) bool { 24 | ms, c := C(COLLECTION) 25 | defer ms.Close() 26 | 27 | info, err := c.Upsert(bson.M{"id": user.Id}, user) 28 | if err != nil { 29 | log.Println(info, err) 30 | return false 31 | } 32 | 33 | return true 34 | } 35 | 36 | //---------------------------------------------------------- login with name & mac address 37 | func LoginMac(name, mac string) *User { 38 | ms, c := C(COLLECTION) 39 | defer ms.Close() 40 | 41 | user := &User{} 42 | err := c.Find(bson.M{"name": name, "mac": mac}).One(user) 43 | if err != nil { 44 | log.Println(err, mac) 45 | return nil 46 | } 47 | 48 | return user 49 | } 50 | 51 | //---------------------------------------------------------- create a new user 52 | func New(name, mac string) *User { 53 | ms, c := C(COLLECTION) 54 | defer ms.Close() 55 | 56 | config := cfg.Get() 57 | user := &User{} 58 | err := c.Find(bson.M{"name": name}).One(user) 59 | if err != nil { 60 | user.Id = NextVal(COUNTER_NAME) 61 | user.Name = name 62 | user.Mac = mac 63 | user.Domain = config["domain"] 64 | user.CreatedAt = time.Now().Unix() 65 | err := c.Insert(user) 66 | if err != nil { 67 | log.Println(err, name, mac) 68 | return nil 69 | } 70 | return user 71 | } 72 | 73 | return nil 74 | } 75 | 76 | //---------------------------------------------------------- query a user by name 77 | func Query(name string) *User { 78 | ms, c := C(COLLECTION) 79 | defer ms.Close() 80 | 81 | user := &User{} 82 | err := c.Find(bson.M{"name": name}).One(user) 83 | if err != nil { 84 | log.Println(err, name) 85 | return nil 86 | } 87 | 88 | return user 89 | } 90 | 91 | //---------------------------------------------------------- load a user 92 | func Get(id int32) *User { 93 | ms, c := C(COLLECTION) 94 | defer ms.Close() 95 | 96 | user := &User{} 97 | err := c.Find(bson.M{"id": id}).One(user) 98 | if err != nil { 99 | log.Println(err, id) 100 | return nil 101 | } 102 | 103 | return user 104 | } 105 | 106 | //---------------------------------------------------------- load all userss 107 | func GetAll() []User { 108 | ms, c := C(COLLECTION) 109 | defer ms.Close() 110 | 111 | var users []User 112 | err := c.Find(nil).All(&users) 113 | if err != nil { 114 | log.Println(err) 115 | return nil 116 | } 117 | 118 | return users 119 | } 120 | 121 | func _md5(str string) []byte { 122 | config := cfg.Get() 123 | salted := str + config["salt"] 124 | h := md5.New() 125 | io.WriteString(h, salted) 126 | return h.Sum(nil) 127 | } 128 | -------------------------------------------------------------------------------- /src/gamedata/README.md: -------------------------------------------------------------------------------- 1 | Gamedata 的工作原理类似于字典 2 | 3 | 表: [行,列] 4 | 5 | 策划通常采用excel表做数值, gamedata提供一个类似的内存二维表。 6 | 7 | gamedata能处理如下格式的.csv文件 8 | 9 |
10 | 表名 | 列1   | 列2   | 列3   | ...... | 列N  |         
11 | 行1  | 值11  | 值12  | 值13  | ...... | 值1N |     
12 | 行2  | 值21  | 值22  | 值23  | ...... | 值2N |    
13 |      
14 | .....     
15 | 
16 | 行M  | 值M1  | 值M2  | 值M3  | ...... | 值MN |         
17 |  
18 | 
19 | 表名,第一行,第一列被用来索引 : 20 | 21 | (TABLE, COLUMN, FIELDNAME) -> CELL 22 | -------------------------------------------------------------------------------- /src/gamedata/data/sheet1.csv: -------------------------------------------------------------------------------- 1 | build1,build2,build3,build4,build5,build6,build7,build8,build9,build10,build11 2 | 1,1,1,1,1,1,0,1,1,0,1 3 | 2,1,2,1,1,1,0,1,1,0,1 4 | 3,2,2,1,1,1,0,1,1,0,1 5 | 4,2,2,1,1,1,0,1,1,0,1 6 | 5,2,3,2,2,1,1,1,1,1,1 7 | 6,2,3,2,2,1,1,1,1,1,1 8 | 7,3,3,2,2,1,1,1,1,1,1 9 | 8,3,3,2,2,1,1,1,1,1,1 10 | 9,3,3,2,2,1,1,1,1,1,1 11 | 10,5,5,3,3,1,1,1,1,1,2 12 | 11,5,5,3,3,1,1,1,1,1,2 13 | 12,5,5,3,3,1,1,1,1,1,2 14 | 13,5,5,3,3,1,1,1,1,1,2 15 | 14,5,5,3,3,1,1,1,1,1,2 16 | 15,5,5,3,3,1,1,1,1,1,2 17 | 16,5,5,3,3,1,1,1,1,1,2 18 | 17,5,5,3,3,1,1,1,1,1,2 19 | 18,5,5,3,3,1,1,1,1,1,2 20 | 19,5,5,3,3,1,1,1,1,1,2 21 | 20,5,5,3,3,1,1,1,1,1,2 22 | 21,5,5,3,3,1,1,1,1,1,2 23 | 22,5,5,3,3,1,1,1,1,1,2 24 | 23,5,5,3,3,1,1,1,1,1,2 25 | 24,5,5,3,3,1,1,1,1,1,2 26 | 25,5,5,3,3,1,1,1,1,1,2 27 | -------------------------------------------------------------------------------- /src/gamedata/gamedata.go: -------------------------------------------------------------------------------- 1 | package gamedata 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "strconv" 10 | "sync" 11 | ) 12 | 13 | import ( 14 | "cfg" 15 | . "helper" 16 | ) 17 | 18 | var _lock sync.RWMutex 19 | var _tables map[string]*Table 20 | 21 | //---------------------------------------------------------- info for a level 22 | type Record struct { 23 | Fields map[string]string 24 | } 25 | 26 | //---------------------------------------------------------- Numerical Table for a object 27 | type Table struct { 28 | Records map[string]*Record 29 | } 30 | 31 | func init() { 32 | Reload() 33 | } 34 | 35 | //----------------------------------------------------------- Reload *.csv 36 | func Reload() { 37 | _lock.Lock() 38 | defer _lock.Unlock() 39 | 40 | _tables = make(map[string]*Table) 41 | 42 | pattern := os.Getenv("GOPATH") + "/src/gamedata/data/*.csv" 43 | 44 | config := cfg.Get() 45 | if config["gamedata_dir"] != "" { 46 | pattern = config["gamedata_dir"] + "/*.csv" 47 | } 48 | 49 | INFO("Loading GameData From", pattern) 50 | files, err := filepath.Glob(pattern) 51 | 52 | if err != nil { 53 | ERR(err) 54 | panic(err) 55 | } 56 | 57 | for _, f := range files { 58 | file, err := os.Open(f) 59 | if err != nil { 60 | ERR("error opening file", err) 61 | continue 62 | } 63 | 64 | parse(file) 65 | file.Close() 66 | } 67 | 68 | log.Printf("\033[042;1m%v CSV(s) Loaded\033[0m\n", len(_tables)) 69 | } 70 | 71 | //---------------------------------------------------------- Set Field value 72 | func _set(tblname string, rowname string, fieldname string, value string) { 73 | tbl := _tables[tblname] 74 | 75 | if tbl == nil { 76 | tbl = &Table{} 77 | tbl.Records = make(map[string]*Record) 78 | _tables[tblname] = tbl 79 | } 80 | 81 | rec := tbl.Records[rowname] 82 | if rec == nil { 83 | rec = &Record{} 84 | rec.Fields = make(map[string]string) 85 | tbl.Records[rowname] = rec 86 | } 87 | 88 | rec.Fields[fieldname] = value 89 | } 90 | 91 | //---------------------------------------------------------- Get Field value 92 | func _get(tblname string, rowname string, fieldname string) string { 93 | _lock.RLock() 94 | defer _lock.RUnlock() 95 | 96 | tbl, ok := _tables[tblname] 97 | if !ok { 98 | panic(fmt.Sprint("table ", tblname, " not exists!")) 99 | } 100 | 101 | rec, ok := tbl.Records[rowname] 102 | if !ok { 103 | panic(fmt.Sprint("table ", tblname, " row ", rowname, " not exists!")) 104 | } 105 | 106 | value, ok := rec.Fields[fieldname] 107 | if !ok { 108 | panic(fmt.Sprint("table ", tblname, " field ", fieldname, " not exists!")) 109 | } 110 | return value 111 | } 112 | 113 | //---------------------------------------------------------- Get Field value as Integer 114 | func GetInt(tblname string, rowname string, fieldname string) int32 { 115 | val := _get(tblname, rowname, fieldname) 116 | v, err := strconv.Atoi(val) 117 | if err != nil { 118 | panic(fmt.Sprintf("cannot parse integer from gamedata %v %v %v %v\n", tblname, rowname, fieldname, err)) 119 | } 120 | 121 | return int32(v) 122 | } 123 | 124 | //---------------------------------------------------------- Get Field value as Float 125 | func GetFloat(tblname string, rowname string, fieldname string) float64 { 126 | val := _get(tblname, rowname, fieldname) 127 | if val == "" { 128 | return 0.0 129 | } 130 | 131 | f, err := strconv.ParseFloat(val, 32) 132 | if err != nil { 133 | panic(fmt.Sprintf("cannot parse float from gamedata %v %v %v %v\n", tblname, rowname, fieldname, err)) 134 | } 135 | 136 | return f 137 | } 138 | 139 | //---------------------------------------------------------- Get Field value as string 140 | func GetString(tblname string, rowname string, fieldname string) string { 141 | return _get(tblname, rowname, fieldname) 142 | } 143 | 144 | //---------------------------------------------------------- Get Row Count 145 | func Count(tblname string) int32 { 146 | tbl := _tables[tblname] 147 | 148 | if tbl == nil { 149 | return 0 150 | } 151 | 152 | return int32(len(tbl.Records)) 153 | } 154 | 155 | //---------------------------------------------------------- Test Field Exists 156 | func IsFieldExists(tblname string, fieldname string) bool { 157 | _lock.RLock() 158 | defer _lock.RUnlock() 159 | 160 | tbl := _tables[tblname] 161 | 162 | if tbl == nil { 163 | return false 164 | } 165 | 166 | key := "" 167 | // get one record key 168 | for k, _ := range tbl.Records { 169 | key = k 170 | break 171 | } 172 | 173 | rec, ok := tbl.Records[key] 174 | if !ok { 175 | return false 176 | } 177 | 178 | _, ok = rec.Fields[fieldname] 179 | if !ok { 180 | return false 181 | } 182 | 183 | return true 184 | } 185 | 186 | //---------------------------------------------------------- Load JSON From GameData Directory 187 | func LoadJSON(filename string) ([]byte, error) { 188 | prefix := os.Getenv("GOPATH") + "/src/gamedata/data" 189 | config := cfg.Get() 190 | if config["gamedata_dir"] != "" { 191 | prefix = config["gamedata_dir"] 192 | } 193 | 194 | path := prefix + "/" + filename 195 | return ioutil.ReadFile(path) 196 | } 197 | -------------------------------------------------------------------------------- /src/gamedata/parser.go: -------------------------------------------------------------------------------- 1 | package gamedata 2 | 3 | import ( 4 | "encoding/csv" 5 | "os" 6 | "path" 7 | "strings" 8 | ) 9 | 10 | import ( 11 | . "helper" 12 | ) 13 | 14 | //----------------------------------------------- parse & load a game data file into dictionary 15 | func parse(file *os.File) { 16 | // csv 读取器 17 | csv_reader := csv.NewReader(file) 18 | records, err := csv_reader.ReadAll() 19 | if err != nil { 20 | ERR("cannot parse csv file.", file.Name(), err) 21 | return 22 | } 23 | 24 | // 是否为空档 25 | if len(records) == 0 { 26 | ERR("csv file is empty", file.Name()) 27 | return 28 | } 29 | 30 | // 处理表名 31 | fi, err := file.Stat() 32 | if err != nil { 33 | ERR("cannot stat the file", file.Name()) 34 | return 35 | } 36 | tblname := strings.TrimSuffix(fi.Name(), path.Ext(file.Name())) 37 | 38 | // 记录数据, 第一行为表头,因此从第二行开始 39 | for line := 1; line < len(records); line++ { 40 | for field := 1; field < len(records[line]); field++ { // 每条记录的第一个字段作为行索引 41 | _set(tblname, records[line][0], records[0][field], records[line][field]) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/helper/conn.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | func SetConnParam(conn *net.TCPConn) { 8 | conn.SetNoDelay(false) 9 | conn.SetKeepAlive(true) 10 | conn.SetLinger(-1) 11 | } 12 | -------------------------------------------------------------------------------- /src/helper/export.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | ) 8 | 9 | import ( 10 | "cfg" 11 | ) 12 | 13 | var ( 14 | _debug_open bool 15 | ) 16 | 17 | func init() { 18 | config := cfg.Get() 19 | if config["debug"] == "true" { 20 | _debug_open = true 21 | } 22 | } 23 | 24 | //------------------------------------------------ 严重程度由高到低 25 | func ERR(v ...interface{}) { 26 | log.Printf("\033[1;4;31m[ERROR] %v \033[0m\n", strings.TrimRight(fmt.Sprintln(v...), "\n")) 27 | } 28 | 29 | func WARN(v ...interface{}) { 30 | log.Printf("\033[1;33m[WARN] %v \033[0m\n", strings.TrimRight(fmt.Sprintln(v...), "\n")) 31 | } 32 | 33 | func INFO(v ...interface{}) { 34 | log.Printf("\033[32m[INFO] %v \033[0m\n", strings.TrimRight(fmt.Sprintln(v...), "\n")) 35 | } 36 | 37 | func NOTICE(v ...interface{}) { 38 | log.Printf("[NOTICE] %v\n", strings.TrimRight(fmt.Sprintln(v...), "\n")) 39 | } 40 | 41 | func DEBUG(v ...interface{}) { 42 | if _debug_open { 43 | log.Printf("\033[1;35m[DEBUG] %v \033[0m\n", strings.TrimRight(fmt.Sprintln(v...), "\n")) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/helper/profilingtool.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "runtime/debug" 7 | "time" 8 | ) 9 | 10 | var heapProfileCounter int32 11 | var startTime = time.Now() 12 | var pid int 13 | 14 | func GCSummary() string { 15 | memStats := &runtime.MemStats{} 16 | runtime.ReadMemStats(memStats) 17 | gcstats := &debug.GCStats{PauseQuantiles: make([]time.Duration, 100)} 18 | debug.ReadGCStats(gcstats) 19 | 20 | return printGC(memStats, gcstats) 21 | } 22 | 23 | func printGC(memStats *runtime.MemStats, gcstats *debug.GCStats) string { 24 | if gcstats.NumGC > 0 { 25 | lastPause := gcstats.Pause[0] 26 | elapsed := time.Now().Sub(startTime) 27 | overhead := float64(gcstats.PauseTotal) / float64(elapsed) * 100 28 | allocatedRate := float64(memStats.TotalAlloc) / elapsed.Seconds() 29 | 30 | return fmt.Sprintf("NumGC:%d Pause:%s Pause(Avg):%s Overhead:%3.2f%% Alloc:%s Sys:%s Alloc(Rate):%s/s Histogram:%s %s %s \n", 31 | gcstats.NumGC, 32 | toS(lastPause), 33 | toS(avg(gcstats.Pause)), 34 | overhead, 35 | toH(memStats.Alloc), 36 | toH(memStats.Sys), 37 | toH(uint64(allocatedRate)), 38 | toS(gcstats.PauseQuantiles[94]), 39 | toS(gcstats.PauseQuantiles[98]), 40 | toS(gcstats.PauseQuantiles[99])) 41 | } else { 42 | // while GC has disabled 43 | elapsed := time.Now().Sub(startTime) 44 | allocatedRate := float64(memStats.TotalAlloc) / elapsed.Seconds() 45 | 46 | return fmt.Sprintf("Alloc:%s Sys:%s Alloc(Rate):%s/s\n", 47 | toH(memStats.Alloc), 48 | toH(memStats.Sys), 49 | toH(uint64(allocatedRate))) 50 | } 51 | } 52 | 53 | func avg(items []time.Duration) time.Duration { 54 | var sum time.Duration 55 | for _, item := range items { 56 | sum += item 57 | } 58 | return time.Duration(int64(sum) / int64(len(items))) 59 | } 60 | 61 | // human readable format 62 | func toH(bytes uint64) string { 63 | switch { 64 | case bytes < 1024: 65 | return fmt.Sprintf("%dB", bytes) 66 | case bytes < 1024*1024: 67 | return fmt.Sprintf("%.2fK", float64(bytes)/1024) 68 | case bytes < 1024*1024*1024: 69 | return fmt.Sprintf("%.2fM", float64(bytes)/1024/1024) 70 | default: 71 | return fmt.Sprintf("%.2fG", float64(bytes)/1024/1024/1024) 72 | } 73 | } 74 | 75 | // short string format 76 | func toS(d time.Duration) string { 77 | 78 | u := uint64(d) 79 | if u < uint64(time.Second) { 80 | switch { 81 | case u == 0: 82 | return "0" 83 | case u < uint64(time.Microsecond): 84 | return fmt.Sprintf("%.2fns", float64(u)) 85 | case u < uint64(time.Millisecond): 86 | return fmt.Sprintf("%.2fus", float64(u)/1000) 87 | default: 88 | return fmt.Sprintf("%.2fms", float64(u)/1000/1000) 89 | } 90 | } else { 91 | switch { 92 | case u < uint64(time.Minute): 93 | return fmt.Sprintf("%.2fs", float64(u)/1000/1000/1000) 94 | case u < uint64(time.Hour): 95 | return fmt.Sprintf("%.2fm", float64(u)/1000/1000/1000/60) 96 | default: 97 | return fmt.Sprintf("%.2fh", float64(u)/1000/1000/1000/60/60) 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/helper/rand.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | var x0 uint32 = uint32(time.Now().UnixNano()) 8 | var a uint32 = 1664525 9 | var c uint32 = 1013904223 10 | 11 | var LCG chan uint32 12 | 13 | //------------------------------------------------ 全局快速随机数发生器 14 | func init() { 15 | LCG = make(chan uint32) 16 | go func() { 17 | for { 18 | x0 = a*x0 + c 19 | LCG <- x0 20 | } 21 | }() 22 | } 23 | -------------------------------------------------------------------------------- /src/helper/rand_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkRand(b *testing.B) { 8 | for i := 0; i < b.N; i++ { 9 | println(<-LCG) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/helper/sendchan.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "misc/packet" 5 | ) 6 | 7 | func SendChan(seqid uint64, data []byte, output chan []byte) { 8 | writer := packet.Writer() 9 | writer.WriteU16(uint16(len(data)) + 8) 10 | writer.WriteU64(seqid) // piggyback seq id 11 | writer.WriteRawBytes(data) 12 | output <- writer.Data() 13 | } 14 | -------------------------------------------------------------------------------- /src/helper/stack.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "log" 5 | "runtime" 6 | ) 7 | 8 | func PrintPanicStack() { 9 | if x := recover(); x != nil { 10 | log.Printf("%v", x) 11 | for i := 0; i < 10; i++ { 12 | funcName, file, line, ok := runtime.Caller(i) 13 | if ok { 14 | log.Printf("frame %v:[func:%v,file:%v,line:%v]\n", i, runtime.FuncForPC(funcName).Name(), file, line) 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/hub/README.md: -------------------------------------------------------------------------------- 1 | ### HUB 服务器 2 | 3 | HUB 作为一个独立的服务运行,各个逻辑服务器(GS)启动时会连接到HUB. 4 | HUB 管理玩家基础、重要的状态信息的存取变更,如: 5 | 6 | * 排名 7 | * 状态 8 | * 联盟消息管理 9 | 10 | HUB也承担玩家之间消息转发(不在同一个服务器登陆的情况)。 11 | HUB只处理来自GS的两类消息: 12 | 13 | 1. 来自Game Server 的Call请求(request & ack) 14 | 2. Game Server 间的消息Forward 15 | 16 | ### 设计考虑 17 | 1. 玩家动态排名,基于动态有序统计 18 | 2. 玩家状态机是行级锁实现 19 | 3. 联盟管理保留最大group_msg_max这么多条消息。玩家登陆后,联盟的消息会转发过来。 20 | 21 | ![状态机](https://github.com/xtaci/gonet/raw/develop/doc/fsm.png) 22 | -------------------------------------------------------------------------------- /src/hub/agent.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "sync/atomic" 7 | ) 8 | 9 | import ( 10 | "hub/core" 11 | "hub/protos" 12 | "misc/packet" 13 | . "types" 14 | ) 15 | 16 | //---------------------------------------------------------- 连入GS的ID生成器 17 | var _host_genid int32 18 | 19 | func init() { 20 | log.SetPrefix("[HUB] ") 21 | } 22 | 23 | //---------------------------------------------------------- HUB SERVICE 24 | func StartAgent(incoming chan []byte, conn net.Conn) { 25 | hostid := atomic.AddInt32(&_host_genid, 1) 26 | log.Printf("game server [id:%v] connected\n", hostid) 27 | 28 | // forward queue (IPCObject) 29 | forward := make(chan IPCObject, 100000) 30 | protos.AddServer(hostid, forward) 31 | 32 | // closing 33 | defer func() { 34 | protos.RemoveServer(hostid) 35 | core.LogoutServer(hostid) 36 | close(forward) 37 | 38 | log.Printf("game server [id:%v] disconnected\n", hostid) 39 | }() 40 | 41 | for { 42 | select { 43 | case msg, ok := <-incoming: // request from game server 44 | if !ok { 45 | return 46 | } 47 | 48 | // read seqid 49 | reader := packet.Reader(msg) 50 | seqid, err := reader.ReadU32() 51 | if err != nil { 52 | log.Println("read SEQID failed.", err) 53 | return 54 | } 55 | 56 | // handle request 57 | ret := GSProxy(hostid, reader) 58 | // send result 59 | if len(ret) != 0 { 60 | _send(seqid, ret, conn) 61 | } 62 | case obj := <-forward: // forwarding packets(ie. seqid == 0) 63 | _send(0, obj.Json(), conn) 64 | } 65 | } 66 | } 67 | 68 | //---------------------------------------------------------- send response 69 | func _send(seqid uint32, data []byte, conn net.Conn) { 70 | writer := packet.Writer() 71 | writer.WriteU16(uint16(len(data)) + 4) 72 | writer.WriteU32(seqid) // piggyback seq id 73 | writer.WriteRawBytes(data) 74 | 75 | n, err := conn.Write(writer.Data()) // write operation is assumed to be atomic 76 | if err != nil { 77 | log.Println("Error send reply to GS, bytes:", n, "reason:", err) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/hub/core/fsm.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | import ( 9 | "misc/timer" 10 | . "types" 11 | ) 12 | 13 | const ( 14 | AUTO_EXPIRE = 300 // seconds 15 | EVENT_MAX = 50000 16 | ) 17 | 18 | //--------------------------------------------------------- player info 19 | type PlayerInfo struct { 20 | Id int32 21 | State byte 22 | Host int32 // host 23 | ProtectTimeout int64 // real protect timeout 24 | RaidStart int64 // raid start time 25 | sync.Mutex // Record lock 26 | } 27 | 28 | 29 | /********************************************************** 30 | * consider following deadlock situations. 31 | * 32 | * A(B) means lock A,lock B, unlock B, unlock A 33 | * A->B means lockA unlockA,then lockB, unlockB 34 | * 35 | * p:A(B), q:B(A), possible circular wait, deadlock!!! 36 | * p:A(B), q:A(B), ok 37 | * p.A(B), q:B or A, ok 38 | * p:A->B, q: B->A, ok 39 | * 40 | * make sure acquiring the lock IN SEQUENCE. i.e. 41 | * A: players 42 | * B: playerinfo.LCK 43 | **********************************************************/ 44 | var ( 45 | _lock_players sync.Mutex // lock players 46 | _players map[int32]*PlayerInfo // all players 47 | _waits_ch chan int32 48 | ) 49 | 50 | func init() { 51 | _players = make(map[int32]*PlayerInfo) 52 | _waits_ch = make(chan int32, EVENT_MAX) 53 | 54 | go _expire() 55 | } 56 | 57 | //--------------------------------------------------------- expires 58 | func _expire() { 59 | for { 60 | select { 61 | case user_id := <-_waits_ch: 62 | _lock_players.Lock() 63 | player := _players[user_id] 64 | _lock_players.Unlock() 65 | 66 | // modify state, using defensive strategy 67 | player.Lock() 68 | switch player.State { 69 | case ON_PROT: 70 | if player.ProtectTimeout <= time.Now().Unix() { 71 | player.State = ON_FREE 72 | } 73 | case OFF_PROT: 74 | if player.ProtectTimeout <= time.Now().Unix() { 75 | player.State = OFF_FREE 76 | } 77 | case OFF_RAID: 78 | if player.RaidStart+AUTO_EXPIRE <= time.Now().Unix() { 79 | player.State = OFF_FREE 80 | } 81 | } 82 | player.Unlock() 83 | } 84 | } 85 | } 86 | 87 | //------------------------------------------------ add a user to finite state machine manager 88 | func _add_fsm(user *User) { 89 | player := &PlayerInfo{Id: user.Id} 90 | player.ProtectTimeout = user.ProtectTimeout 91 | 92 | if user.ProtectTimeout > time.Now().Unix() { // 有保护时间 93 | player.State = OFF_PROT 94 | 95 | _lock_players.Lock() 96 | _players[player.Id] = player 97 | _lock_players.Unlock() 98 | 99 | timer.Add(user.Id, user.ProtectTimeout, _waits_ch) 100 | } else { 101 | player.State = OFF_FREE 102 | 103 | _lock_players.Lock() 104 | _players[user.Id] = player 105 | _lock_players.Unlock() 106 | } 107 | } 108 | 109 | //------------------------------------------------ when game disconnect, perform a logout for all players on that gs 110 | func LogoutServer(host int32) { 111 | _lock_players.Lock() 112 | snapshot := make(map[int32]*PlayerInfo) 113 | for k, v := range _players { 114 | snapshot[k] = v 115 | } 116 | _lock_players.Unlock() 117 | 118 | for _, v := range snapshot { 119 | v.Lock() 120 | if v.Host == host { 121 | // state correction 122 | switch v.State { 123 | case ON_FREE: 124 | v.State = OFF_FREE 125 | case ON_PROT: 126 | v.State = OFF_PROT 127 | } 128 | 129 | } 130 | v.Unlock() 131 | } 132 | } 133 | 134 | //------------------------------------------------ The State Machine Of Player 135 | func Login(id, host int32) bool { 136 | _lock_players.Lock() 137 | player := _players[id] 138 | _lock_players.Unlock() 139 | 140 | if player != nil { 141 | player.Lock() 142 | defer player.Unlock() 143 | 144 | switch player.State { 145 | case OFF_FREE: 146 | player.State = ON_FREE 147 | player.Host = host 148 | case OFF_PROT: 149 | player.State = ON_PROT 150 | player.Host = host 151 | default: 152 | return false 153 | } 154 | return true 155 | } 156 | return false 157 | } 158 | 159 | func Logout(id int32) bool { 160 | _lock_players.Lock() 161 | player := _players[id] 162 | _lock_players.Unlock() 163 | 164 | if player != nil { 165 | player.Lock() 166 | defer player.Unlock() 167 | 168 | switch player.State { 169 | case ON_FREE: 170 | player.State = OFF_FREE 171 | case ON_PROT: 172 | player.State = OFF_PROT 173 | default: 174 | return false 175 | } 176 | return true 177 | } 178 | return false 179 | } 180 | 181 | func Raid(id int32) bool { 182 | _lock_players.Lock() 183 | player := _players[id] 184 | _lock_players.Unlock() 185 | 186 | if player != nil { 187 | player.Lock() 188 | defer player.Unlock() 189 | 190 | switch player.State { 191 | case OFF_FREE: 192 | player.State = OFF_RAID 193 | player.RaidStart = time.Now().Unix() 194 | timeout := time.Now().Unix() + AUTO_EXPIRE // automatic expire when in raid 195 | timer.Add(player.Id, timeout, _waits_ch) 196 | default: 197 | return false 198 | } 199 | return true 200 | } 201 | return false 202 | } 203 | 204 | func Free(id int32) bool { 205 | _lock_players.Lock() 206 | player := _players[id] 207 | _lock_players.Unlock() 208 | 209 | if player != nil { 210 | player.Lock() 211 | defer player.Unlock() 212 | switch player.State { 213 | case ON_PROT: 214 | player.State = ON_FREE 215 | case OFF_RAID: 216 | player.State = OFF_FREE 217 | default: 218 | return false 219 | } 220 | return true 221 | } 222 | return false 223 | } 224 | 225 | func Protect(id int32, until int64) bool { 226 | _lock_players.Lock() 227 | player := _players[id] 228 | _lock_players.Unlock() 229 | 230 | if player != nil { 231 | player.Lock() 232 | defer player.Unlock() 233 | 234 | switch player.State { 235 | case ON_FREE: 236 | player.State = ON_PROT 237 | player.ProtectTimeout = until 238 | timer.Add(id, until, _waits_ch) 239 | case OFF_RAID: 240 | player.State = OFF_PROT 241 | player.ProtectTimeout = until 242 | timer.Add(id, until, _waits_ch) 243 | case ON_PROT: // protect + protect 244 | player.ProtectTimeout = until 245 | timer.Add(id, until, _waits_ch) 246 | default: 247 | return false 248 | } 249 | return true 250 | } 251 | 252 | return false 253 | } 254 | 255 | // State Readers 256 | // A->B 257 | func State(id int32) (ret byte) { 258 | _lock_players.Lock() 259 | player := _players[id] 260 | _lock_players.Unlock() 261 | 262 | if player != nil { 263 | player.Lock() 264 | ret = player.State 265 | player.Unlock() 266 | } 267 | return 268 | } 269 | 270 | func Host(id int32) (ret int32) { 271 | _lock_players.Lock() 272 | player := _players[id] 273 | _lock_players.Unlock() 274 | 275 | if player != nil { 276 | player.Lock() 277 | ret = player.Host 278 | player.Unlock() 279 | } 280 | 281 | return 282 | } 283 | -------------------------------------------------------------------------------- /src/hub/core/load.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "db/user_tbl" 5 | . "types" 6 | ) 7 | 8 | //----------------------------------------------- load all users into memory 9 | func LoadAllUsers() { 10 | // load users 11 | uds := user_tbl.GetAll() 12 | 13 | for i := range uds { 14 | _add_user(&uds[i]) 15 | } 16 | } 17 | 18 | //----------------------------------------------- load a single user 19 | func LoadUser(id int32) bool { 20 | user := user_tbl.Get(id) 21 | 22 | if user != nil { 23 | _add_user(user) 24 | return true 25 | } 26 | 27 | return false 28 | } 29 | 30 | //----------------------------------------------- feed user to data structures 31 | func _add_user(user *User) { 32 | _add_fsm(user) 33 | } 34 | -------------------------------------------------------------------------------- /src/hub/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "log" 7 | "net" 8 | "os" 9 | ) 10 | 11 | import ( 12 | "cfg" 13 | "hub/core" 14 | ) 15 | 16 | const ( 17 | DEFAULT_SERVICE = ":8889" 18 | ) 19 | 20 | //----------------------------------------------- HUB 21 | func main() { 22 | defer func() { 23 | if x := recover(); x != nil { 24 | log.Println("caught panic in main()", x) 25 | } 26 | }() 27 | 28 | // start logger 29 | config := cfg.Get() 30 | if config["hub_log"] != "" { 31 | cfg.StartLogger(config["hub_log"]) 32 | } 33 | 34 | log.Println("Starting HUB") 35 | // signal handler 36 | go SignalProc() 37 | 38 | // sys routine 39 | go SysRoutine() 40 | 41 | // Listen 42 | service := DEFAULT_SERVICE 43 | if config["hub_service"] != "" { 44 | service = config["hub_service"] 45 | } 46 | 47 | log.Println("Hub Service:", service) 48 | tcpAddr, err := net.ResolveTCPAddr("tcp4", service) 49 | checkError(err) 50 | 51 | listener, err := net.ListenTCP("tcp", tcpAddr) 52 | checkError(err) 53 | 54 | // load all users from db 55 | core.LoadAllUsers() 56 | 57 | log.Println("HUB Server OK.") 58 | for { 59 | conn, err := listener.AcceptTCP() 60 | if err != nil { 61 | continue 62 | } 63 | conn.SetLinger(-1) 64 | go handleClient(conn) 65 | } 66 | } 67 | 68 | //----------------------------------------------- handle hub request 69 | func handleClient(conn net.Conn) { 70 | defer conn.Close() 71 | header := make([]byte, 2) 72 | ch := make(chan []byte, 100000) 73 | 74 | go StartAgent(ch, conn) 75 | 76 | for { 77 | // header 78 | n, err := io.ReadFull(conn, header) 79 | if err != nil { 80 | log.Println("error receiving header, bytes:", n, "reason:", err) 81 | break 82 | } 83 | 84 | // data 85 | size := binary.BigEndian.Uint16(header) 86 | data := make([]byte, size) 87 | n, err = io.ReadFull(conn, data) 88 | if err != nil { 89 | log.Println("error receiving msg, bytes:", n, "reason:", err) 90 | break 91 | } 92 | ch <- data 93 | } 94 | 95 | close(ch) 96 | } 97 | 98 | func checkError(err error) { 99 | if err != nil { 100 | log.Printf("Fatal error: %v", err) 101 | os.Exit(-1) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/hub/protos/README.md: -------------------------------------------------------------------------------- 1 | ### GS与HUB通信协议 2 | 3 | #### 包结构 4 |
 5 | |LENGTH|SEQNUM|PROTO|PAYLOAD|
 6 | 
 7 | |16|64|16|...|
 8 | 
9 | 10 | #### 包测试: 11 |
12 | ping:
13 | echo "000E 0000000000000001 0000 0000000A" | xxd -r -ps |nc 127.0.0.1 8890 -q 2|hexdump -C
14 | 
15 | get state:
16 | echo "000E 0000000000000001 0009 00000001" | xxd -r -ps |nc 127.0.0.1 8889 -q 2|hexdump -C
17 | 
18 | forward content "ABCD" to user 1 :
19 | echo "0012 0000000000000001 0080 00000001 0002 ABCD" | xxd -r -ps |nc 127.0.0.1 8889 -q 2|hexdump -C
20 | 
21 | -------------------------------------------------------------------------------- /src/hub/protos/api.go: -------------------------------------------------------------------------------- 1 | package protos 2 | 3 | import "misc/packet" 4 | 5 | var Code = map[string]int16{ 6 | "ping_req": 0, // PING 7 | "login_req": 1, // 登陆 8 | "logout_req": 2, // 登出 9 | "raid_req": 3, // 攻击 10 | "protect_req": 4, // 加保护 11 | "free_req": 5, // 结束攻击 12 | "adduser_req": 6, // 注册一个新注册的玩家 13 | "forward_req": 100, // 转发IPC消息 14 | } 15 | 16 | var RCode = map[int16]string{ 17 | 0: "ping_req", // PING 18 | 1: "login_req", // 登陆 19 | 2: "logout_req", // 登出 20 | 3: "raid_req", // 攻击 21 | 4: "protect_req", // 加保护 22 | 5: "free_req", // 结束攻击 23 | 6: "adduser_req", // 注册一个新注册的玩家 24 | 100: "forward_req", // 转发IPC消息 25 | } 26 | 27 | var ProtoHandler map[int16]func(int32, *packet.Packet) []byte 28 | 29 | func init() { 30 | ProtoHandler = map[int16]func(int32, *packet.Packet) []byte{ 31 | 0: P_ping_req, 32 | 1: P_login_req, 33 | 2: P_logout_req, 34 | 3: P_raid_req, 35 | 4: P_protect_req, 36 | 5: P_free_req, 37 | 6: P_adduser_req, 38 | 100: P_forward_req, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/hub/protos/handle.go: -------------------------------------------------------------------------------- 1 | package protos 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "runtime" 7 | ) 8 | 9 | import ( 10 | "db/forward_tbl" 11 | "helper" 12 | "hub/core" 13 | "misc/packet" 14 | . "types" 15 | ) 16 | 17 | func HandleRequest(hostid int32, reader *packet.Packet, output chan []byte) { 18 | defer helper.PrintPanicStack() 19 | 20 | seqid, err := reader.ReadU64() // read seqid 21 | if err != nil { 22 | log.Println("Read Sequence Id failed.", err) 23 | return 24 | } 25 | 26 | b, err := reader.ReadS16() 27 | if err != nil { 28 | log.Println("read protocol error") 29 | return 30 | } 31 | 32 | handle := ProtoHandler[b] 33 | if handle != nil { 34 | ret := handle(hostid, reader) 35 | if len(ret) != 0 { 36 | helper.SendChan(seqid, ret, output) 37 | } 38 | } 39 | } 40 | 41 | func P_ping_req(hostid int32, reader *packet.Packet) []byte { 42 | tbl, _ := PKT_INT(reader) 43 | ret := INT{tbl.F_v} 44 | return packet.Pack(-1, &ret, nil) 45 | } 46 | 47 | func P_login_req(hostid int32, pkt *packet.Packet) []byte { 48 | tbl, _ := PKT_LOGIN_REQ(pkt) 49 | ret := LOGIN_ACK{F_success: 0} 50 | 51 | if core.Login(tbl.F_id, hostid) { 52 | ret.F_success = 1 53 | } 54 | 55 | return packet.Pack(-1, &ret, nil) 56 | } 57 | 58 | func P_logout_req(hostid int32, pkt *packet.Packet) []byte { 59 | tbl, _ := PKT_ID(pkt) 60 | ret := INT{F_v: 0} 61 | 62 | if core.Logout(tbl.F_id) { 63 | ret.F_v = 1 64 | } 65 | 66 | return packet.Pack(-1, &ret, nil) 67 | } 68 | 69 | func P_raid_req(hostid int32, pkt *packet.Packet) []byte { 70 | tbl, _ := PKT_ID(pkt) 71 | ret := INT{F_v: 0} 72 | 73 | if core.Raid(tbl.F_id) { 74 | ret.F_v = 1 75 | } 76 | 77 | return packet.Pack(-1, &ret, nil) 78 | } 79 | 80 | func P_protect_req(hostid int32, pkt *packet.Packet) []byte { 81 | tbl, _ := PKT_PROTECT(pkt) 82 | ret := INT{F_v: 0} 83 | 84 | if core.Protect(tbl.F_id, tbl.F_protecttime) { 85 | ret.F_v = 1 86 | } 87 | 88 | return packet.Pack(-1, &ret, nil) 89 | } 90 | 91 | func P_free_req(hostid int32, pkt *packet.Packet) []byte { 92 | tbl, _ := PKT_ID(pkt) 93 | ret := INT{F_v: 0} 94 | 95 | if core.Free(tbl.F_id) { 96 | ret.F_v = 1 97 | } 98 | 99 | return packet.Pack(-1, &ret, nil) 100 | } 101 | 102 | func P_forward_req(hostid int32, pkt *packet.Packet) []byte { 103 | tbl, _ := PKT_FORWARDIPC(pkt) 104 | 105 | obj := &IPCObject{} 106 | err := json.Unmarshal(tbl.F_IPC, obj) 107 | 108 | if err != nil { 109 | log.Println("decode forward IPCObject error", err) 110 | return nil 111 | } 112 | 113 | // to SYS_USR or to player 114 | if obj.DestID == SYS_USR { 115 | Syscast(hostid, obj) 116 | } else { 117 | _unicast(hostid, obj) 118 | } 119 | 120 | ret := INT{F_v: 1} 121 | return packet.Pack(-1, &ret, nil) 122 | } 123 | 124 | func _unicast(hostid int32, obj *IPCObject) { 125 | // if user is online, send to the server, or else send to database 126 | state := core.State(obj.DestID) 127 | 128 | switch state { 129 | case ON_PROT, ON_FREE: 130 | host := core.Host(obj.DestID) 131 | ch := ForwardChan(host) 132 | 133 | if ch != nil { 134 | ch <- *obj 135 | } else { 136 | forward_tbl.Push(obj) 137 | } 138 | default: 139 | forward_tbl.Push(obj) 140 | } 141 | } 142 | 143 | func Syscast(hostid int32, obj *IPCObject) { 144 | all := AllServers() 145 | for _, v := range all { 146 | if v != hostid { // ignore sender's server 147 | ch := ForwardChan(v) 148 | 149 | if ch != nil { 150 | ch <- *obj 151 | } 152 | } 153 | } 154 | } 155 | 156 | func P_adduser_req(hostid int32, pkt *packet.Packet) []byte { 157 | tbl, _ := PKT_ID(pkt) 158 | ret := INT{F_v: 0} 159 | 160 | if core.LoadUser(tbl.F_id) { 161 | core.Login(tbl.F_id, hostid) 162 | ret.F_v = 1 163 | } 164 | 165 | return packet.Pack(-1, &ret, nil) 166 | } 167 | 168 | func checkErr(err error) { 169 | if err != nil { 170 | funcName, file, line, ok := runtime.Caller(1) 171 | if ok { 172 | log.Printf("ERR:%v,[func:%v,file:%v,line:%v]\n", err, runtime.FuncForPC(funcName).Name(), file, line) 173 | } 174 | 175 | panic("error occured in HUB ipc module") 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/hub/protos/proto.go: -------------------------------------------------------------------------------- 1 | package protos 2 | 3 | import "misc/packet" 4 | 5 | type FORWARDIPC struct { 6 | F_IPC []byte 7 | } 8 | 9 | type LOGIN_REQ struct { 10 | F_id int32 11 | } 12 | 13 | type LOGIN_ACK struct { 14 | F_success int32 15 | } 16 | 17 | type ID struct { 18 | F_id int32 19 | } 20 | 21 | type PROTECT struct { 22 | F_id int32 23 | F_protecttime int64 24 | } 25 | 26 | type ID_SCORE struct { 27 | F_id int32 28 | F_score int32 29 | } 30 | 31 | type LIST struct { 32 | F_items []ID_SCORE 33 | } 34 | 35 | type LONG struct { 36 | F_v int64 37 | } 38 | 39 | type STRING struct { 40 | F_v string 41 | } 42 | 43 | type INT struct { 44 | F_v int32 45 | } 46 | 47 | func PKT_FORWARDIPC(reader *packet.Packet) (tbl FORWARDIPC, err error) { 48 | tbl.F_IPC, err = reader.ReadBytes() 49 | checkErr(err) 50 | 51 | return 52 | } 53 | 54 | func PKT_LOGIN_REQ(reader *packet.Packet) (tbl LOGIN_REQ, err error) { 55 | tbl.F_id, err = reader.ReadS32() 56 | checkErr(err) 57 | 58 | return 59 | } 60 | 61 | func PKT_LOGIN_ACK(reader *packet.Packet) (tbl LOGIN_ACK, err error) { 62 | tbl.F_success, err = reader.ReadS32() 63 | checkErr(err) 64 | 65 | return 66 | } 67 | 68 | func PKT_ID(reader *packet.Packet) (tbl ID, err error) { 69 | tbl.F_id, err = reader.ReadS32() 70 | checkErr(err) 71 | 72 | return 73 | } 74 | 75 | func PKT_PROTECT(reader *packet.Packet) (tbl PROTECT, err error) { 76 | tbl.F_id, err = reader.ReadS32() 77 | checkErr(err) 78 | 79 | tbl.F_protecttime, err = reader.ReadS64() 80 | checkErr(err) 81 | 82 | return 83 | } 84 | 85 | func PKT_ID_SCORE(reader *packet.Packet) (tbl ID_SCORE, err error) { 86 | tbl.F_id, err = reader.ReadS32() 87 | checkErr(err) 88 | 89 | tbl.F_score, err = reader.ReadS32() 90 | checkErr(err) 91 | 92 | return 93 | } 94 | 95 | func PKT_LIST(reader *packet.Packet) (tbl LIST, err error) { 96 | { 97 | narr, err := reader.ReadU16() 98 | checkErr(err) 99 | 100 | tbl.F_items = make([]ID_SCORE, narr) 101 | for i := 0; i < int(narr); i++ { 102 | tbl.F_items[i], err = PKT_ID_SCORE(reader) 103 | checkErr(err) 104 | 105 | } 106 | 107 | } 108 | 109 | return 110 | } 111 | 112 | func PKT_LONG(reader *packet.Packet) (tbl LONG, err error) { 113 | tbl.F_v, err = reader.ReadS64() 114 | checkErr(err) 115 | 116 | return 117 | } 118 | 119 | func PKT_STRING(reader *packet.Packet) (tbl STRING, err error) { 120 | tbl.F_v, err = reader.ReadString() 121 | checkErr(err) 122 | 123 | return 124 | } 125 | 126 | func PKT_INT(reader *packet.Packet) (tbl INT, err error) { 127 | tbl.F_v, err = reader.ReadS32() 128 | checkErr(err) 129 | 130 | return 131 | } 132 | -------------------------------------------------------------------------------- /src/hub/protos/servers.go: -------------------------------------------------------------------------------- 1 | package protos 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | import ( 8 | . "types" 9 | ) 10 | 11 | var ( 12 | // 各个服务器的Forward消息队列 13 | _servers map[int32]chan IPCObject 14 | _serverlock sync.Mutex 15 | ) 16 | 17 | func AddServer(hostid int32, forward chan IPCObject) { 18 | _serverlock.Lock() 19 | defer _serverlock.Unlock() 20 | _servers[hostid] = forward 21 | } 22 | 23 | func RemoveServer(hostid int32) { 24 | _serverlock.Lock() 25 | defer _serverlock.Unlock() 26 | delete(_servers, hostid) 27 | } 28 | 29 | func ForwardChan(hostid int32) chan IPCObject { 30 | _serverlock.Lock() 31 | defer _serverlock.Unlock() 32 | return _servers[hostid] 33 | } 34 | 35 | func AllServers() []int32 { 36 | _serverlock.Lock() 37 | defer _serverlock.Unlock() 38 | 39 | _all := make([]int32, len(_servers)) 40 | idx := 0 41 | for k := range _servers { 42 | _all[idx] = k 43 | idx++ 44 | } 45 | 46 | return _all 47 | } 48 | 49 | func init() { 50 | _servers = make(map[int32]chan IPCObject) 51 | } 52 | -------------------------------------------------------------------------------- /src/hub/proxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | ) 7 | 8 | import ( 9 | "helper" 10 | "hub/protos" 11 | "misc/packet" 12 | ) 13 | 14 | //------------------------------------------------ Game Server Request Proxy 15 | func GSProxy(hostid int32, reader *packet.Packet) (ret []byte) { 16 | defer helper.PrintPanicStack() 17 | 18 | // read protocol number 19 | b, err := reader.ReadS16() 20 | if err != nil { 21 | log.Println("read protocol error") 22 | return 23 | } 24 | 25 | // get handler 26 | handle := protos.ProtoHandler[b] 27 | if handle == nil { 28 | log.Println("service not bind", b) 29 | return 30 | } 31 | 32 | // call handler 33 | start := time.Now() 34 | ret = handle(hostid, reader) 35 | end := time.Now() 36 | log.Printf("code: %v %v TIME:%v\n", b, protos.RCode[b], end.Sub(start)) 37 | 38 | return ret 39 | } 40 | -------------------------------------------------------------------------------- /src/hub/signal.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | ) 9 | 10 | import ( 11 | "cfg" 12 | ) 13 | 14 | //----------------------------------------------- handle unix signals 15 | func SignalProc() { 16 | ch := make(chan os.Signal, 1) 17 | signal.Notify(ch, syscall.SIGHUP) 18 | 19 | for { 20 | msg := <-ch 21 | log.Println("Recevied signal:", msg) 22 | cfg.Reload() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/hub/sys.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "runtime" 6 | "time" 7 | ) 8 | 9 | import ( 10 | "helper" 11 | "misc/timer" 12 | ) 13 | 14 | const ( 15 | GC_INTERVAL = 300 16 | ) 17 | 18 | //---------------------------------------------------------- 系统routine 19 | func SysRoutine() { 20 | // timer 21 | gc_timer := make(chan int32, 10) 22 | gc_timer <- 1 23 | 24 | for { 25 | select { 26 | case <-gc_timer: 27 | // gc work 28 | runtime.GC() 29 | log.Println("GC executed") 30 | log.Println("NumGoroutine", runtime.NumGoroutine()) 31 | log.Println("GC Summary:", helper.GCSummary()) 32 | timer.Add(0, time.Now().Unix()+int64(GC_INTERVAL), gc_timer) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/misc/README.md: -------------------------------------------------------------------------------- 1 | 通用计时器 2 | 3 | 时间表示为 int64 Unix 时间 4 | 5 | 用层级的方式处理任意长度的定时器消息 6 | -------------------------------------------------------------------------------- /src/misc/alg/README.md: -------------------------------------------------------------------------------- 1 | Algorithms 2 | -------------------------------------------------------------------------------- /src/misc/alg/bitset/bitset.go: -------------------------------------------------------------------------------- 1 | package bitset 2 | 3 | type BitSet struct { 4 | _size uint32 5 | _bits []byte 6 | } 7 | 8 | func New(num_bits uint32) *BitSet { 9 | bs := &BitSet{} 10 | byte_len := num_bits/8 + 1 11 | bs._size = byte_len * 8 12 | bs._bits = make([]byte, byte_len) 13 | 14 | return bs 15 | } 16 | 17 | //---------------------------------------------------------- set 1 to position [bit] 18 | func (bs *BitSet) Set(bit uint32) { 19 | if bit >= bs._size { 20 | return 21 | } 22 | 23 | n := bit / 8 24 | off := bit % 8 25 | 26 | bs._bits[n] |= 128 >> off 27 | } 28 | 29 | //---------------------------------------------------------- set 0 to position [bit] 30 | func (bs *BitSet) Unset(bit uint32) { 31 | if bit >= bs._size { 32 | return 33 | } 34 | 35 | n := bit / 8 36 | off := bit % 8 37 | 38 | bs._bits[n] &= ^(128 >> off) 39 | } 40 | 41 | //---------------------------------------------------------- test wheather a bit is set 42 | func (bs *BitSet) Test(bit uint32) bool { 43 | if bit >= bs._size { 44 | return false 45 | } 46 | 47 | n := bit / 8 48 | off := bit % 8 49 | 50 | if bs._bits[n]&(128>>off) != 0 { 51 | return true 52 | } else { 53 | return false 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/misc/alg/bitset/bitset_test.go: -------------------------------------------------------------------------------- 1 | package bitset 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestBitSet(t *testing.T) { 9 | LEN := uint32(1024) 10 | bs := New(LEN) 11 | for i := uint32(0); i < LEN; i++ { 12 | bs.Set(i) 13 | } 14 | 15 | fmt.Println(bs._bits) 16 | for i := uint32(0); i < LEN; i++ { 17 | if !bs.Test(i) { 18 | t.Fatal("bitset failed") 19 | } 20 | 21 | bs.Unset(i) 22 | } 23 | 24 | for i := uint32(0); i < LEN; i++ { 25 | if bs.Test(i) { 26 | t.Fatal("bitset failed") 27 | } 28 | } 29 | 30 | fmt.Println(bs._bits) 31 | } 32 | -------------------------------------------------------------------------------- /src/misc/alg/consistent_hash/README.md: -------------------------------------------------------------------------------- 1 | Consistent hashing is a special kind of hashing such that when a hash table is resized and consistent hashing is used, only K/n keys need to be remapped on average, where K is the number of keys, and n is the number of slots. In contrast, in most traditional hash tables, a change in the number of array slots causes nearly all keys to be remapped. 2 | 3 | Consistent hashing achieves some of the same goals as Rendezvous hashing (also called HRW Hashing). The two techniques use different algorithms, and were devised independently and contemporaneously. 4 | -------------------------------------------------------------------------------- /src/misc/alg/consistent_hash/consistent_hash.go: -------------------------------------------------------------------------------- 1 | package consistent_hash 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type ConsistentHashing struct { 8 | split_points []uint32 9 | keys map[uint32]string 10 | sync.Mutex 11 | } 12 | 13 | func (ch *ConsistentHashing) Init() { 14 | ch.keys = make(map[uint32]string) 15 | } 16 | 17 | //----------------------------------------------- add a key with hashcode to the circle 18 | func (ch *ConsistentHashing) AddNode(key string, hashcode uint32) bool { 19 | ch.Lock() 20 | defer ch.Unlock() 21 | 22 | // test hashcode existence 23 | if _, ok := ch.keys[hashcode]; ok { 24 | return false 25 | } 26 | ch.keys[hashcode] = key 27 | 28 | // hashcode in the middle of the circle 29 | for i := 0; i < len(ch.split_points); i++ { 30 | if hashcode < ch.split_points[i] { 31 | ch.split_points = append(ch.split_points[:i], append([]uint32{hashcode}, ch.split_points[i:]...)...) 32 | return true 33 | } 34 | } 35 | 36 | // largest hashcode or empty circle 37 | ch.split_points = append(ch.split_points, hashcode) 38 | return true 39 | } 40 | 41 | //----------------------------------------------- remove a node from the circle 42 | func (ch *ConsistentHashing) RemoveNode(hashcode uint32) bool { 43 | ch.Lock() 44 | defer ch.Unlock() 45 | 46 | if _, ok := ch.keys[hashcode]; ok { 47 | delete(ch.keys, hashcode) 48 | for i := 0; i < len(ch.split_points); i++ { 49 | if ch.split_points[i] == hashcode { // node found! 50 | ch.split_points = append(ch.split_points[:i], ch.split_points[i+1:]...) 51 | return true 52 | } 53 | } 54 | } 55 | return false 56 | } 57 | 58 | //----------------------------------------------- get the node by a given hashcode 59 | func (ch *ConsistentHashing) GetNode(hashcode uint32) (key string, ok bool) { 60 | ch.Lock() 61 | defer ch.Unlock() 62 | 63 | // if empty circle 64 | if len(ch.split_points) == 0 { 65 | return "", false 66 | } 67 | 68 | // find nearest node 69 | for i := range ch.split_points { 70 | if ch.split_points[i] >= hashcode { 71 | return ch.keys[ch.split_points[i]], true 72 | } 73 | } 74 | 75 | // hashcode is larger than the largest node, return to the first node 76 | return ch.keys[ch.split_points[0]], true 77 | } 78 | -------------------------------------------------------------------------------- /src/misc/alg/consistent_hash/consistent_hash_test.go: -------------------------------------------------------------------------------- 1 | package consistent_hash 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestConsitentHashing(t *testing.T) { 9 | ch := new(ConsistentHashing) 10 | ch.Init() 11 | t.Log("testing") 12 | t.Log(1234, fmt.Sprint(ch.GetNode(1234))) 13 | t.Log(12345, fmt.Sprint(ch.GetNode(12345))) 14 | t.Log(22345, fmt.Sprint(ch.GetNode(22345))) 15 | t.Log(32345, fmt.Sprint(ch.GetNode(32345))) 16 | t.Log("adding nodes a, b, c") 17 | ch.AddNode("a", 10000) 18 | ch.AddNode("a", 10000) 19 | ch.AddNode("b", 20000) 20 | ch.AddNode("c", 30000) 21 | 22 | t.Log("testing") 23 | t.Log(1234, fmt.Sprint(ch.GetNode(1234))) 24 | t.Log(12345, fmt.Sprint(ch.GetNode(12345))) 25 | t.Log(22345, fmt.Sprint(ch.GetNode(22345))) 26 | t.Log(32345, fmt.Sprint(ch.GetNode(32345))) 27 | 28 | t.Log("remove node", 20000) 29 | ch.RemoveNode(20000) 30 | t.Log("testing") 31 | t.Log(1234, fmt.Sprint(ch.GetNode(1234))) 32 | t.Log(12345, fmt.Sprint(ch.GetNode(12345))) 33 | t.Log(22345, fmt.Sprint(ch.GetNode(22345))) 34 | t.Log(32345, fmt.Sprint(ch.GetNode(32345))) 35 | t.Log("remove all node") 36 | ch.RemoveNode(10000) 37 | ch.RemoveNode(30000) 38 | t.Log("testing") 39 | t.Log(1234, fmt.Sprint(ch.GetNode(1234))) 40 | t.Log(12345, fmt.Sprint(ch.GetNode(12345))) 41 | t.Log(22345, fmt.Sprint(ch.GetNode(22345))) 42 | t.Log(32345, fmt.Sprint(ch.GetNode(32345))) 43 | } 44 | -------------------------------------------------------------------------------- /src/misc/alg/dos/README.md: -------------------------------------------------------------------------------- 1 | DYNAMIC ORDER STATISTIC 2 | 3 | Features 4 | 5 | 1. based on red-black tree 6 | 7 | 2. O(logn) lookup time 8 | 9 | 3. Select the i-th largest element 10 | 11 | http://en.wikipedia.org/wiki/Order_statistic 12 | -------------------------------------------------------------------------------- /src/misc/alg/dos/dos.go: -------------------------------------------------------------------------------- 1 | package dos 2 | 3 | import ( 4 | "log" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | RED = true 10 | BLACK = false 11 | ) 12 | 13 | type Node struct { 14 | left *Node 15 | right *Node 16 | parent *Node 17 | 18 | size int // the size of this subtree 19 | color bool 20 | 21 | score int32 // the score 22 | ids []int32 // associated ids 23 | } 24 | 25 | func (n *Node) Ids() []int32 { 26 | return n.ids 27 | } 28 | 29 | func (n *Node) Score() int32 { 30 | return n.score 31 | } 32 | 33 | // 34 | type Tree struct { 35 | root *Node 36 | } 37 | 38 | func (t *Tree) Clear() { 39 | t.root = nil 40 | } 41 | 42 | func (t *Tree) Root() *Node { 43 | return t.root 44 | } 45 | 46 | //--------------------------------------------------------- Lookup by Rank 47 | // READ-LOCK 48 | func (t *Tree) Count() int { 49 | if t.root != nil { 50 | return t.root.size 51 | } 52 | 53 | return 0 54 | } 55 | 56 | //--------------------------------------------------------- Dos Part 57 | func _nodesize(n *Node) int { 58 | if n == nil { 59 | return 0 60 | } 61 | 62 | return n.size 63 | } 64 | 65 | func lookup_node(n *Node, rank int) (id int32, node *Node) { 66 | if n == nil { 67 | return -1, nil // beware of nil pointer 68 | } 69 | 70 | start := _nodesize(n.left) + 1 71 | end := _nodesize(n.left) + len(n.ids) 72 | 73 | if rank >= start && rank <= end { 74 | return n.ids[rank-start], n 75 | } 76 | 77 | if rank < start { 78 | return lookup_node(n.left, rank) 79 | } 80 | return lookup_node(n.right, rank-end) 81 | } 82 | 83 | func new_node(score int32, id int32, color bool, left, right *Node) *Node { 84 | n := Node{score: score, color: color, left: left, right: right, size: 1, ids: []int32{id}} 85 | return &n 86 | } 87 | 88 | //--------------------------------------------------------- Lookup by Rank 89 | // READ-LOCK 90 | func (t *Tree) Rank(rank int) (id int32, node *Node) { 91 | return lookup_node(t.root, rank) 92 | } 93 | 94 | //--------------------------------------------------------- Lookup by score 95 | func (t *Tree) _lookup_score(score int32) (rank int, n *Node) { 96 | n = t.root 97 | 98 | if n == nil { 99 | return -1, nil 100 | } 101 | 102 | base := 0 103 | for n != nil { 104 | if score == n.score { 105 | rank = base + _nodesize(n.left) + 1 // start rank 106 | return rank, n 107 | } else if score > n.score { 108 | n = n.left 109 | } else { 110 | base += _nodesize(n.left) + len(n.ids) 111 | n = n.right 112 | } 113 | } 114 | 115 | return -1, nil 116 | } 117 | 118 | //---------------------------------------------------------- locate a score & id 119 | // READ-LOCK 120 | func (t *Tree) Locate(score int32, id int32) (int, *Node) { 121 | rank, node := t._lookup_score(score) 122 | 123 | if node == nil { // no such score exists 124 | return -1, nil 125 | } 126 | 127 | // find the id in all ids 128 | for k, v := range node.ids { 129 | if v == id { 130 | // current rank plus the order in the ids 131 | return rank + k, node 132 | } 133 | } 134 | 135 | return -1, nil 136 | } 137 | 138 | //---------------------------------------------------------- Insert an element 139 | // WRITE-LOCK 140 | func (t *Tree) Insert(score int32, id int32) { 141 | inserted_node := new_node(score, id, RED, nil, nil) 142 | if t.root == nil { 143 | t.root = inserted_node 144 | } else { 145 | n := t.root 146 | for { 147 | n.size++ // the size of these nodes on the way will be increased by 1 148 | if score == n.score { // same score, just append the new id in the []ids then return, no structure changes. 149 | n.ids = append(n.ids, id) 150 | return 151 | } else if score > n.score { // find higher score in left subtree 152 | if n.left == nil { 153 | n.left = inserted_node 154 | break 155 | } else { 156 | n = n.left 157 | } 158 | } else if score < n.score { // find lower score in right subtree 159 | if n.right == nil { 160 | n.right = inserted_node 161 | break 162 | } else { 163 | n = n.right 164 | } 165 | } 166 | } 167 | inserted_node.parent = n 168 | } 169 | 170 | t.insert_case1(inserted_node) 171 | } 172 | 173 | //---------------------------------------------------------- Delete an id from a node 174 | // WRITE-LOCK 175 | func (t *Tree) Delete(id int32, n *Node) { 176 | // just delete the given id in []ids if the id is not the only one in this node 177 | if len(n.ids) > 1 { 178 | for k, v := range n.ids { 179 | if v == id { 180 | n.ids = append(n.ids[:k], n.ids[k+1:]...) 181 | // decrease size by 1 from this node to the top 182 | fixup_size(n) 183 | return 184 | } 185 | } 186 | } else { // the only id in this node, node will be deleted, and the structure will change 187 | // just decrease size by 1 from N to the root 188 | fixup_size(n) 189 | 190 | // handle red-black properties, and deletion work. 191 | if n.left != nil && n.right != nil { 192 | /* Copy fields from predecessor and then delete it instead */ 193 | pred := maximum_node(n.left) 194 | // copy score, id 195 | n.score = pred.score 196 | n.ids = pred.ids 197 | 198 | // decrease size by pred.size from pred to N 199 | tmp := pred 200 | for tmp != n { 201 | tmp.size -= len(pred.ids) 202 | tmp = tmp.parent 203 | } 204 | 205 | // deal with predecessor after. 206 | n = pred 207 | } 208 | 209 | var child *Node 210 | if n.right == nil { 211 | child = n.left 212 | } else { 213 | child = n.right 214 | } 215 | 216 | if node_color(n) == BLACK { 217 | n.color = node_color(child) 218 | t.delete_case1(n) 219 | } 220 | 221 | t.replace_node(n, child) 222 | 223 | if n.parent == nil && child != nil { 224 | child.color = BLACK 225 | } 226 | } 227 | } 228 | 229 | /** 230 | * left/right rotation call back function 231 | */ 232 | func rotate_left_callback(n, parent *Node) { 233 | parent.size = _nodesize(n) 234 | n.size = _nodesize(n.left) + _nodesize(n.right) + len(n.ids) 235 | } 236 | 237 | func rotate_right_callback(n, parent *Node) { 238 | rotate_left_callback(n, parent) 239 | } 240 | 241 | func fixup_size(n *Node) { 242 | for n != nil { 243 | n.size-- 244 | n = n.parent 245 | } 246 | } 247 | 248 | //--------------------------------------------------------- Tree part 249 | func grandparent(n *Node) *Node { 250 | return n.parent.parent 251 | } 252 | 253 | func sibling(n *Node) *Node { 254 | if n == n.parent.left { 255 | return n.parent.right 256 | } 257 | return n.parent.left 258 | } 259 | 260 | func uncle(n *Node) *Node { 261 | return sibling(n.parent) 262 | } 263 | 264 | func node_color(n *Node) bool { 265 | if n == nil { 266 | return BLACK 267 | } 268 | return n.color 269 | } 270 | 271 | func (t *Tree) rotate_left(n *Node) { 272 | r := n.right 273 | t.replace_node(n, r) 274 | n.right = r.left 275 | if r.left != nil { 276 | r.left.parent = n 277 | } 278 | r.left = n 279 | n.parent = r 280 | 281 | rotate_left_callback(n, r) 282 | } 283 | 284 | func (t *Tree) rotate_right(n *Node) { 285 | L := n.left 286 | t.replace_node(n, L) 287 | n.left = L.right 288 | if L.right != nil { 289 | L.right.parent = n 290 | } 291 | L.right = n 292 | n.parent = L 293 | 294 | rotate_right_callback(n, L) 295 | } 296 | 297 | func (t *Tree) replace_node(oldn, newn *Node) { 298 | if oldn.parent == nil { 299 | t.root = newn 300 | } else { 301 | if oldn == oldn.parent.left { 302 | oldn.parent.left = newn 303 | } else { 304 | oldn.parent.right = newn 305 | } 306 | } 307 | if newn != nil { 308 | newn.parent = oldn.parent 309 | } 310 | } 311 | 312 | func (t *Tree) insert_case1(n *Node) { 313 | if n.parent == nil { 314 | n.color = BLACK 315 | } else { 316 | t.insert_case2(n) 317 | } 318 | } 319 | 320 | func (t *Tree) insert_case2(n *Node) { 321 | if node_color(n.parent) == BLACK { 322 | return /* Tree is still valid */ 323 | } else { 324 | t.insert_case3(n) 325 | } 326 | } 327 | 328 | func (t *Tree) insert_case3(n *Node) { 329 | if node_color(uncle(n)) == RED { 330 | n.parent.color = BLACK 331 | uncle(n).color = BLACK 332 | grandparent(n).color = RED 333 | t.insert_case1(grandparent(n)) 334 | } else { 335 | t.insert_case4(n) 336 | } 337 | } 338 | 339 | func (t *Tree) insert_case4(n *Node) { 340 | if n == n.parent.right && n.parent == grandparent(n).left { 341 | t.rotate_left(n.parent) 342 | n = n.left 343 | } else if n == n.parent.left && n.parent == grandparent(n).right { 344 | t.rotate_right(n.parent) 345 | n = n.right 346 | } 347 | t.insert_case5(n) 348 | } 349 | 350 | func (t *Tree) insert_case5(n *Node) { 351 | n.parent.color = BLACK 352 | grandparent(n).color = RED 353 | if n == n.parent.left && n.parent == grandparent(n).left { 354 | t.rotate_right(grandparent(n)) 355 | } else { 356 | t.rotate_left(grandparent(n)) 357 | } 358 | } 359 | 360 | func maximum_node(n *Node) *Node { 361 | for n.right != nil { 362 | n = n.right 363 | } 364 | return n 365 | } 366 | 367 | func (t *Tree) delete_case1(n *Node) { 368 | if n.parent == nil { 369 | return 370 | } else { 371 | t.delete_case2(n) 372 | } 373 | } 374 | 375 | func (t *Tree) delete_case2(n *Node) { 376 | if node_color(sibling(n)) == RED { 377 | n.parent.color = RED 378 | sibling(n).color = BLACK 379 | if n == n.parent.left { 380 | t.rotate_left(n.parent) 381 | } else { 382 | t.rotate_right(n.parent) 383 | } 384 | } 385 | t.delete_case3(n) 386 | } 387 | 388 | func (t *Tree) delete_case3(n *Node) { 389 | if node_color(n.parent) == BLACK && 390 | node_color(sibling(n)) == BLACK && 391 | node_color(sibling(n).left) == BLACK && 392 | node_color(sibling(n).right) == BLACK { 393 | sibling(n).color = RED 394 | t.delete_case1(n.parent) 395 | } else { 396 | t.delete_case4(n) 397 | } 398 | } 399 | 400 | func (t *Tree) delete_case4(n *Node) { 401 | if node_color(n.parent) == RED && 402 | node_color(sibling(n)) == BLACK && 403 | node_color(sibling(n).left) == BLACK && 404 | node_color(sibling(n).right) == BLACK { 405 | sibling(n).color = RED 406 | n.parent.color = BLACK 407 | } else { 408 | t.delete_case5(n) 409 | } 410 | } 411 | 412 | func (t *Tree) delete_case5(n *Node) { 413 | if n == n.parent.left && 414 | node_color(sibling(n)) == BLACK && 415 | node_color(sibling(n).left) == RED && 416 | node_color(sibling(n).right) == BLACK { 417 | sibling(n).color = RED 418 | sibling(n).left.color = BLACK 419 | t.rotate_right(sibling(n)) 420 | } else if n == n.parent.right && 421 | node_color(sibling(n)) == BLACK && 422 | node_color(sibling(n).right) == RED && 423 | node_color(sibling(n).left) == BLACK { 424 | sibling(n).color = RED 425 | sibling(n).right.color = BLACK 426 | t.rotate_left(sibling(n)) 427 | } 428 | t.delete_case6(n) 429 | } 430 | 431 | func (t *Tree) delete_case6(n *Node) { 432 | sibling(n).color = node_color(n.parent) 433 | n.parent.color = BLACK 434 | if n == n.parent.left { 435 | sibling(n).right.color = BLACK 436 | t.rotate_left(n.parent) 437 | } else { 438 | sibling(n).left.color = BLACK 439 | t.rotate_right(n.parent) 440 | } 441 | } 442 | 443 | //---------------------------------------------------------- tree print 444 | const INDENT_STEP = 4 445 | 446 | func Print_helper(n *Node, indent int) { 447 | if n == nil { 448 | log.Printf("") 449 | return 450 | } 451 | if n.right != nil { 452 | Print_helper(n.right, indent+INDENT_STEP) 453 | } 454 | if n.color == BLACK { 455 | log.Printf(strings.Repeat(" ", indent)+"[score:%v size:%v id:%v len:%v]\n", n.score, n.size, n.ids, len(n.ids)) 456 | } else { 457 | log.Printf(strings.Repeat(" ", indent)+"*[score:%v size:%v id:%v len:%v]\n", n.score, n.size, n.ids, len(n.ids)) 458 | } 459 | 460 | if n.left != nil { 461 | Print_helper(n.left, indent+INDENT_STEP) 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /src/misc/alg/dos/dos_test.go: -------------------------------------------------------------------------------- 1 | package dos 2 | 3 | import "testing" 4 | 5 | func Benchmark(b *testing.B) { 6 | tree := Tree{} 7 | 8 | for i := 0; i < b.N; i++ { 9 | tree.Insert(100, int32(i)) 10 | } 11 | 12 | for i := 0; i < b.N; i++ { 13 | tree.Locate(100, int32(i)) 14 | } 15 | } 16 | 17 | func TestDos(t *testing.T) { 18 | tree := Tree{} 19 | 20 | N := 500 21 | t.Log("testing tree.Insert()") 22 | for i := 0; i < N; i++ { 23 | tree.Insert(int32(i), int32(N-i)) 24 | } 25 | t.Log("Count:", tree.Count()) 26 | 27 | t.Log("testing tree.Locate()") 28 | for i := 0; i < N; i++ { 29 | rank, node := tree.Locate(int32(i), int32(N-i)) 30 | if node != nil { 31 | t.Log("id:", N-i, "score:", i, "rank:", rank, "ids", node.ids) 32 | } 33 | } 34 | t.Log("Count:", tree.Count()) 35 | 36 | Print_helper(tree.Root(), 0) 37 | 38 | t.Log("testing tree.Rank()") 39 | for i := 1; i <= tree.Count()+1; i++ { 40 | id, node := tree.Rank(i) 41 | if node != nil { 42 | t.Log("rank:", i, "id", id) 43 | } 44 | } 45 | t.Log("Count:", tree.Count()) 46 | 47 | t.Log("testing tree.Delete()") 48 | cnt := tree.Count() 49 | for i := 0; i < cnt; i++ { 50 | id, n := tree.Rank(1) 51 | if n != nil { 52 | t.Log("deleting id", id) 53 | tree.Delete(id, n) 54 | } 55 | } 56 | t.Log("Count:", tree.Count()) 57 | Print_helper(tree.Root(), 0) 58 | 59 | t.Log("testing tree.Locate()") 60 | for i := 0; i < N; i++ { 61 | rank, n := tree.Locate(int32(i), int32(20-i)) 62 | if rank != -1 { 63 | t.Logf("score %v, ids %v rank %v \n", n.Score(), n.Ids(), rank) 64 | } 65 | } 66 | t.Log("Count:", tree.Count()) 67 | } 68 | -------------------------------------------------------------------------------- /src/misc/alg/gaussian/README.md: -------------------------------------------------------------------------------- 1 | #### 高斯分布函数 2 | 3 | 主要用于: 4 | 在已知网络延迟的样本下,判定CD延迟欺骗的概率 5 | 6 | #### 3σ原则: 7 | 1. P(μ-σB,并且,B的建造,必须要在A建造完成之后进行, A的建造时间是30s. 15 | 16 | Mars/Lan分别间隔30s发送了两个数据包[A, B] 17 | 18 | 理论上,我们只需要检测两者的时间间隔是否是30s就可以判定建造的合理性。 即: 19 | 20 | gap(B-A) == 30s 21 | 22 | 但是网络传输并不一定是一种稳定的延迟, 也许A会晚一秒到达,B早一秒到达。A,B的时间间隔为28s,导致判定失效。 23 | 并且,在纯异步的环境下,Mars/Lan的操作,都应该以本地时间为准。 24 | 25 | 为了解决这样的问题,我们在每个数据包中,都带上一个当前客户端的发送时间戳。好,这样似乎解决问题了,判断: 26 | 27 | timestamp(B-A) >= 30s 28 | 29 | timestamp差是否是30s以上,就ok. 30 | 31 | 但是,主角Lan登场。 他发了两个连续(间隔为0)的数据包分别宣称, 我一年之前就把A建造完成了, 我一年-30s之前就把B建造完成了。 32 | OK,无法避免的欺诈发生了,纯粹靠数据包自己的timestamp判断一定是不行的,因为外界是邪恶的。 33 | 对,你发现了, 两者混合,既判定两个数据包真正先后到达的时间gap,又判定宣称的timestamp,就能解决问题。 34 | 35 | 例如: 36 | 37 | timestamp(B-A) == gap(B-A) 38 | 39 | 可是新的问题又来了,怎么判断'==', 因为latency, gap并不是绝对的啊。我们只能判定约等于,即: 40 | 41 | timestamp(B-A) ~= gap(B-A) 42 | 43 | 方法: 44 | 45 | Abs(timestamp(B-A) - gap(B-A)) < N 46 | 47 | 如何确定N的值呢,最简单方法是,固定值,例如设定N=5, 但是,我更倾向于用概率方法来做,因为 : 48 | 高延迟的,必然有高的误差率, 这个误差率,可以用概率分布描述。 49 | 50 | 我们统计若干个sample,例如32个64个sample。每个sample取值为: 51 | 52 | sample := { timestamp(B-A) - gap(B-A), A,B是两个连续的数据包 } 53 | 54 | 高斯分布可以计算出误差的σ,根据高斯分布的3σ原则。误差 > 2σ 的都是小概率事件,对于新的一个观察(observe) 即可判定。 55 | 56 | 方法: 57 | 58 | Abs(Observe - μ) < 2σ 59 | -------------------------------------------------------------------------------- /src/misc/alg/gaussian/gaussian.go: -------------------------------------------------------------------------------- 1 | package gaussian 2 | 3 | import "math" 4 | 5 | const ( 6 | SQRT2PI = float64(2.50662827463100050241576528481) 7 | ) 8 | 9 | type Dist struct { 10 | Samples []int 11 | Ptr int 12 | N int 13 | Sigma float64 14 | Mean float64 15 | } 16 | 17 | func NewDist(num_samples int) *Dist { 18 | dist := &Dist{} 19 | dist.Samples = make([]int, num_samples) 20 | return dist 21 | } 22 | 23 | func (dist *Dist) IsSampleOk() bool { 24 | if dist.N >= len(dist.Samples) { 25 | return true 26 | } else { 27 | return false 28 | } 29 | } 30 | 31 | func (dist *Dist) Add(x int) { 32 | dist.Samples[dist.Ptr] = x 33 | if dist.Ptr++; dist.Ptr >= len(dist.Samples) { 34 | dist.Ptr = 0 35 | } 36 | 37 | if dist.N < len(dist.Samples) { 38 | dist.N++ 39 | } 40 | 41 | if dist.N == len(dist.Samples) { 42 | // caculate mean 43 | sum := int64(0) 44 | for i := 0; i < dist.N; i++ { 45 | sum += int64(dist.Samples[i]) 46 | } 47 | 48 | dist.Mean = float64(sum) / float64(dist.N) 49 | 50 | // caculate standard deviation 51 | sum2 := float64(0.0) 52 | for i := 0; i < dist.N; i++ { 53 | v := float64(dist.Samples[i]) - dist.Mean 54 | v = v * v 55 | sum2 += v 56 | } 57 | 58 | dist.Sigma = math.Sqrt(sum2 / float64(dist.N)) 59 | } 60 | } 61 | 62 | func (dist *Dist) P(x int) float64 { 63 | X := float64(x) 64 | A := 1.0 / (dist.Sigma * SQRT2PI) 65 | B := math.Exp(-((X - dist.Mean) * (X - dist.Mean)) / (2 * dist.Sigma * dist.Sigma)) 66 | return A * B 67 | } 68 | -------------------------------------------------------------------------------- /src/misc/alg/gaussian/gaussian_test.go: -------------------------------------------------------------------------------- 1 | package gaussian 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "math/rand" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestGauss(t *testing.T) { 12 | src := rand.NewSource(time.Now().Unix()) 13 | gen := rand.New(src) 14 | 15 | // gaussian 16 | gaussian := NewDist(128) 17 | for i := 0; i < 1000; i++ { 18 | v := gen.Intn(200) 19 | gaussian.Add(v) 20 | } 21 | 22 | fmt.Println("N-samples:", gaussian.N, ", σ:", gaussian.Sigma) 23 | 24 | // testing 25 | fmt.Println("range [0,200]") 26 | sigma := gaussian.Sigma 27 | mean := gaussian.Mean 28 | for i := 0; i < 10; i++ { 29 | v := gen.Intn(200) 30 | fmt.Printf("X:%4d: P(v)=%0.4f, deriv:%.2fσ\n", v, gaussian.P(v), math.Abs(float64(v)-mean)/sigma) 31 | } 32 | 33 | fmt.Println("range [0,400]") 34 | for i := 0; i < 10; i++ { 35 | v := gen.Intn(400) 36 | fmt.Printf("X:%4d: P(v)=%0.4f, deriv:%.2fσ\n", v, gaussian.P(v), math.Abs(float64(v)-mean)/sigma) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/misc/alg/interval_tree/README.md: -------------------------------------------------------------------------------- 1 | 区间树 2 | 3 | 用于处理 IP地址到国家的查找 4 | 5 | 例如: IP [A,B] -> CN 6 | 7 | 一个IP区间,对应一个国家 8 | -------------------------------------------------------------------------------- /src/misc/alg/interval_tree/interval_tree.go: -------------------------------------------------------------------------------- 1 | package interval_tree 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | const ( 8 | RED = iota 9 | BLACK 10 | ) 11 | 12 | type Node struct { 13 | left *Node 14 | right *Node 15 | parent *Node 16 | 17 | low int64 // lower-bound 18 | high int64 // higher-bound 19 | m int64 // max subtree upper bound 20 | color int 21 | 22 | data interface{} // associated data 23 | } 24 | 25 | func (n *Node) Data() interface{} { 26 | return n.data 27 | } 28 | 29 | // 30 | type Tree struct { 31 | root *Node 32 | } 33 | 34 | func new_node(low, high int64, data interface{}, color int, left, right *Node) *Node { 35 | n := Node{low: low, high: high, m: high, color: color, left: left, right: right, data: data} 36 | return &n 37 | } 38 | 39 | /** 40 | * interval tree lookup function 41 | * 42 | * search range [low, high] for overlap, return only one element 43 | * use lookup & delete & insert schema to get multiple elements 44 | * 45 | * nil is returned if not found. 46 | */ 47 | func (t *Tree) Lookup(low, high int64) *Node { 48 | n := t.root 49 | for n != nil && (low > n.high || n.low > high) { // should search in childs 50 | if n.left != nil && low <= n.left.m { 51 | n = n.left // path choice on m. 52 | } else { 53 | n = n.right 54 | } 55 | } 56 | 57 | return n 58 | } 59 | 60 | /** 61 | * Insert 62 | * insert range [low, high] into red-black tree 63 | */ 64 | func (t *Tree) Insert(low, high int64, data interface{}) { 65 | inserted_node := new_node(low, high, data, RED, nil, nil) 66 | if t.root == nil { 67 | t.root = inserted_node 68 | } else { 69 | n := t.root 70 | for { 71 | // update 'm' for each node traversed from root 72 | if inserted_node.m > n.m { 73 | n.m = inserted_node.m 74 | } 75 | 76 | // find a proper position 77 | if low < n.low { 78 | if n.left == nil { 79 | n.left = inserted_node 80 | break 81 | } else { 82 | n = n.left 83 | } 84 | } else { 85 | if n.right == nil { 86 | n.right = inserted_node 87 | break 88 | } else { 89 | n = n.right 90 | } 91 | } 92 | } 93 | inserted_node.parent = n 94 | } 95 | 96 | t.insert_case1(inserted_node) 97 | } 98 | 99 | func (t *Tree) DeleteNode(n *Node) { 100 | /* Copy fields from predecessor and then delete it instead */ 101 | if n.left != nil && n.right != nil { 102 | pred := maximum_node(n.left) 103 | n.low = pred.low 104 | n.high = pred.high 105 | n.data = pred.data 106 | n = pred 107 | } 108 | 109 | // fix up size 110 | fixup_m(n) 111 | 112 | var child *Node 113 | if n.right == nil { 114 | child = n.left 115 | } else { 116 | child = n.right 117 | } 118 | 119 | if node_color(n) == BLACK { 120 | n.color = node_color(child) 121 | t.delete_case1(n) 122 | } 123 | 124 | t.replace_node(n, child) 125 | if child != nil { // copy low, high & data 126 | n.low = child.low 127 | n.high = child.high 128 | n.data = child.data 129 | } 130 | 131 | if n.parent == nil && child != nil { 132 | child.color = BLACK 133 | } 134 | } 135 | 136 | func Max(a, b int64) int64 { 137 | if a > b { 138 | return a 139 | } 140 | 141 | return b 142 | } 143 | 144 | func M(n *Node) int64 { 145 | if n == nil { 146 | return int64(math.MinInt64) 147 | } 148 | 149 | return n.m 150 | } 151 | 152 | /** 153 | * fix 'm' value caused by rotation 154 | */ 155 | func rotate_left_callback(n, parent *Node) { 156 | // parent inherit max m value 157 | parent.m = n.m 158 | // update node 'm' value by it's children. 159 | n.m = Max(n.high, Max(M(n.left), M(n.right))) 160 | } 161 | 162 | func rotate_right_callback(n, parent *Node) { 163 | rotate_left_callback(n, parent) 164 | } 165 | 166 | /** 167 | * fix up 'm' value caued by deletion 168 | */ 169 | func fixup_m(n *Node) { 170 | m := M(n) 171 | m_new := Max(M(n.left), M(n.right)) 172 | 173 | // if current 'm' is not decided by n, just return. 174 | if m == m_new { 175 | return 176 | } 177 | 178 | for n.parent != nil { 179 | n.parent.m = Max(n.parent.high, Max(m_new, M(sibling(n)))) 180 | 181 | if M(n.parent) > m { 182 | break // since node n does not affect 183 | // the result anymore, we break. 184 | } 185 | n = n.parent 186 | } 187 | } 188 | 189 | //--------------------------------------------------------- Tree part 190 | func grandparent(n *Node) *Node { 191 | return n.parent.parent 192 | } 193 | 194 | func sibling(n *Node) *Node { 195 | if n == n.parent.left { 196 | return n.parent.right 197 | } 198 | return n.parent.left 199 | } 200 | 201 | func uncle(n *Node) *Node { 202 | return sibling(n.parent) 203 | } 204 | 205 | func node_color(n *Node) int { 206 | if n == nil { 207 | return BLACK 208 | } 209 | return n.color 210 | } 211 | 212 | func (t *Tree) rotate_left(n *Node) { 213 | r := n.right 214 | t.replace_node(n, r) 215 | n.right = r.left 216 | if r.left != nil { 217 | r.left.parent = n 218 | } 219 | r.left = n 220 | n.parent = r 221 | 222 | rotate_left_callback(n, r) 223 | } 224 | 225 | func (t *Tree) rotate_right(n *Node) { 226 | L := n.left 227 | t.replace_node(n, L) 228 | n.left = L.right 229 | if L.right != nil { 230 | L.right.parent = n 231 | } 232 | L.right = n 233 | n.parent = L 234 | 235 | rotate_right_callback(n, L) 236 | } 237 | 238 | func (t *Tree) replace_node(oldn, newn *Node) { 239 | if oldn.parent == nil { 240 | t.root = newn 241 | } else { 242 | if oldn == oldn.parent.left { 243 | oldn.parent.left = newn 244 | } else { 245 | oldn.parent.right = newn 246 | } 247 | } 248 | if newn != nil { 249 | newn.parent = oldn.parent 250 | } 251 | } 252 | 253 | func (t *Tree) insert_case1(n *Node) { 254 | if n.parent == nil { 255 | n.color = BLACK 256 | } else { 257 | t.insert_case2(n) 258 | } 259 | } 260 | 261 | func (t *Tree) insert_case2(n *Node) { 262 | if node_color(n.parent) == BLACK { 263 | return /* Tree is still valid */ 264 | } else { 265 | t.insert_case3(n) 266 | } 267 | } 268 | 269 | func (t *Tree) insert_case3(n *Node) { 270 | if node_color(uncle(n)) == RED { 271 | n.parent.color = BLACK 272 | uncle(n).color = BLACK 273 | grandparent(n).color = RED 274 | t.insert_case1(grandparent(n)) 275 | } else { 276 | t.insert_case4(n) 277 | } 278 | } 279 | 280 | func (t *Tree) insert_case4(n *Node) { 281 | if n == n.parent.right && n.parent == grandparent(n).left { 282 | t.rotate_left(n.parent) 283 | n = n.left 284 | } else if n == n.parent.left && n.parent == grandparent(n).right { 285 | t.rotate_right(n.parent) 286 | n = n.right 287 | } 288 | t.insert_case5(n) 289 | } 290 | 291 | func (t *Tree) insert_case5(n *Node) { 292 | n.parent.color = BLACK 293 | grandparent(n).color = RED 294 | if n == n.parent.left && n.parent == grandparent(n).left { 295 | t.rotate_right(grandparent(n)) 296 | } else { 297 | t.rotate_left(grandparent(n)) 298 | } 299 | } 300 | 301 | func maximum_node(n *Node) *Node { 302 | for n.right != nil { 303 | n = n.right 304 | } 305 | return n 306 | } 307 | 308 | func (t *Tree) delete_case1(n *Node) { 309 | if n.parent == nil { 310 | return 311 | } else { 312 | t.delete_case2(n) 313 | } 314 | } 315 | 316 | func (t *Tree) delete_case2(n *Node) { 317 | if node_color(sibling(n)) == RED { 318 | n.parent.color = RED 319 | sibling(n).color = BLACK 320 | if n == n.parent.left { 321 | t.rotate_left(n.parent) 322 | } else { 323 | t.rotate_right(n.parent) 324 | } 325 | } 326 | t.delete_case3(n) 327 | } 328 | 329 | func (t *Tree) delete_case3(n *Node) { 330 | if node_color(n.parent) == BLACK && 331 | node_color(sibling(n)) == BLACK && 332 | node_color(sibling(n).left) == BLACK && 333 | node_color(sibling(n).right) == BLACK { 334 | sibling(n).color = RED 335 | t.delete_case1(n.parent) 336 | } else { 337 | t.delete_case4(n) 338 | } 339 | } 340 | 341 | func (t *Tree) delete_case4(n *Node) { 342 | if node_color(n.parent) == RED && 343 | node_color(sibling(n)) == BLACK && 344 | node_color(sibling(n).left) == BLACK && 345 | node_color(sibling(n).right) == BLACK { 346 | sibling(n).color = RED 347 | n.parent.color = BLACK 348 | } else { 349 | t.delete_case5(n) 350 | } 351 | } 352 | 353 | func (t *Tree) delete_case5(n *Node) { 354 | if n == n.parent.left && 355 | node_color(sibling(n)) == BLACK && 356 | node_color(sibling(n).left) == RED && 357 | node_color(sibling(n).right) == BLACK { 358 | sibling(n).color = RED 359 | sibling(n).left.color = BLACK 360 | t.rotate_right(sibling(n)) 361 | } else if n == n.parent.right && 362 | node_color(sibling(n)) == BLACK && 363 | node_color(sibling(n).right) == RED && 364 | node_color(sibling(n).left) == BLACK { 365 | sibling(n).color = RED 366 | sibling(n).right.color = BLACK 367 | t.rotate_left(sibling(n)) 368 | } 369 | t.delete_case6(n) 370 | } 371 | 372 | func (t *Tree) delete_case6(n *Node) { 373 | sibling(n).color = node_color(n.parent) 374 | n.parent.color = BLACK 375 | if n == n.parent.left { 376 | sibling(n).right.color = BLACK 377 | t.rotate_left(n.parent) 378 | } else { 379 | sibling(n).left.color = BLACK 380 | t.rotate_right(n.parent) 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /src/misc/alg/interval_tree/interval_tree_test.go: -------------------------------------------------------------------------------- 1 | package interval_tree 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestIntervalTree(t *testing.T) { 9 | tree := Tree{} 10 | 11 | for i := int64(0); i < 100; i++ { 12 | tree.Insert(i, i+10, string([]byte{byte(i%26 + 65)})) 13 | } 14 | 15 | print_helper(tree.root, 0) 16 | 17 | fmt.Printf("searching for %v:%v\n", 10, 10) 18 | node := tree.Lookup(10, 10) 19 | fmt.Println(node) 20 | } 21 | 22 | const INDENT_STEP = 4 23 | 24 | func print_helper(n *Node, indent int) { 25 | if n == nil { 26 | fmt.Printf("") 27 | return 28 | } 29 | if n.right != nil { 30 | print_helper(n.right, indent+INDENT_STEP) 31 | } 32 | for i := 0; i < indent; i++ { 33 | fmt.Printf(" ") 34 | } 35 | if n.color == BLACK { 36 | fmt.Printf("[%v %v m->%v Value: %v]\n", n.low, n.high, n.m, n.Data()) 37 | } else { 38 | fmt.Printf("*[%v %v m->%v Value %v]\n", n.low, n.high, n.m, n.Data()) 39 | } 40 | 41 | if n.left != nil { 42 | print_helper(n.left, indent+INDENT_STEP) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/misc/alg/queue/README.md: -------------------------------------------------------------------------------- 1 | Queue (abstract data type) 2 | 3 | http://en.wikipedia.org/wiki/Queue_%28abstract_data_type%29 4 | -------------------------------------------------------------------------------- /src/misc/alg/queue/queue.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | type Queue struct { 4 | capacity int 5 | size int 6 | front int 7 | rear int 8 | elements []interface{} 9 | } 10 | 11 | func New(max int) *Queue { 12 | queue := &Queue{capacity: max, size: 0, front: 0, rear: -1} 13 | queue.elements = make([]interface{}, max) 14 | return queue 15 | } 16 | 17 | //----------------------------------------------- Enqueue 18 | func (q *Queue) Enqueue(elem interface{}) bool { 19 | if q.size < q.capacity { 20 | q.size++ 21 | q.rear++ 22 | 23 | if q.rear == q.capacity { 24 | q.rear = 0 25 | } 26 | 27 | q.elements[q.rear] = elem 28 | 29 | return true 30 | } 31 | 32 | return false 33 | } 34 | 35 | //----------------------------------------------- Dequeue 36 | func (q *Queue) Dequeue() (interface{}, bool) { 37 | if q.size > 0 { 38 | ret := q.elements[q.front] 39 | 40 | q.size-- 41 | q.front++ 42 | 43 | if q.front == q.capacity { 44 | q.front = 0 45 | } 46 | return ret, true 47 | } 48 | 49 | return nil, false 50 | } 51 | 52 | //----------------------------------------------- return queue 53 | func (q *Queue) All() (all []interface{}) { 54 | all = make([]interface{}, q.size) 55 | 56 | count := q.size 57 | idx := q.front 58 | 59 | for count > 0 { 60 | all[q.size-count] = q.elements[idx] 61 | 62 | idx++ 63 | if idx >= q.capacity { 64 | idx = 0 65 | } 66 | 67 | count-- 68 | } 69 | 70 | return 71 | } 72 | -------------------------------------------------------------------------------- /src/misc/alg/queue/queue_test.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import "testing" 4 | import "fmt" 5 | 6 | func TestQueue(t *testing.T) { 7 | q := New(10) 8 | 9 | for i := 0; i < 10; i++ { 10 | q.Enqueue(i + 10) 11 | } 12 | 13 | q.Dequeue() 14 | q.Enqueue(999) 15 | 16 | fmt.Println("testing All") 17 | s := q.All() 18 | 19 | for k := range s { 20 | fmt.Println(s[k]) 21 | } 22 | 23 | fmt.Println("testing dequeue") 24 | for { 25 | if v, ok := q.Dequeue(); ok { 26 | fmt.Println(v) 27 | } else { 28 | break 29 | } 30 | } 31 | } 32 | 33 | func BenchmarkQueue(b *testing.B) { 34 | q := New(b.N) 35 | 36 | for i := 0; i < b.N; i++ { 37 | q.Enqueue(i) 38 | } 39 | 40 | q.All() 41 | for { 42 | if _, ok := q.Dequeue(); ok { 43 | } else { 44 | break 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/misc/crypto/diffie/README.md: -------------------------------------------------------------------------------- 1 | Diffie-Hellman key-exchange 2 | 3 | http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange 4 | -------------------------------------------------------------------------------- /src/misc/crypto/diffie/dh.go: -------------------------------------------------------------------------------- 1 | /*********************************************************** 2 | * 3 | * Diffie–Hellman key exchange 4 | * 5 | */ 6 | package diffie 7 | 8 | import "math/big" 9 | import "math/rand" 10 | import "time" 11 | 12 | var rng = rand.New(rand.NewSource(time.Now().UnixNano())) 13 | 14 | var DH1BASE = big.NewInt(3) 15 | var DH1PRIME, _ = big.NewInt(0).SetString("0xFFFFFFFB", 0) 16 | 17 | //----------------------------------------------- Diffie-Hellman Key-exchange 18 | func DHGenKey(G, P *big.Int) (*big.Int, *big.Int) { 19 | X := big.NewInt(0).Rand(rng, P) 20 | E := big.NewInt(0).Exp(G, X, P) 21 | return X, E 22 | } 23 | -------------------------------------------------------------------------------- /src/misc/crypto/diffie/dh_test.go: -------------------------------------------------------------------------------- 1 | package diffie 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "testing" 7 | ) 8 | 9 | func TestDH(t *testing.T) { 10 | X1, E1 := DHGenKey(DH1BASE, DH1PRIME) 11 | X2, E2 := DHGenKey(DH1BASE, DH1PRIME) 12 | 13 | fmt.Println("Secret 1:", X1, E1) 14 | fmt.Println("Secret 2:", X2, E2) 15 | 16 | KEY1 := big.NewInt(0).Exp(E2, X1, DH1PRIME) 17 | KEY2 := big.NewInt(0).Exp(E1, X2, DH1PRIME) 18 | 19 | fmt.Println("KEY1:", KEY1) 20 | fmt.Println("KEY2:", KEY2) 21 | 22 | if KEY1.Cmp(KEY2) != 0 { 23 | t.Error("Diffie-Hellman failed") 24 | } 25 | } 26 | 27 | func BenchmarkDH(b *testing.B) { 28 | for i := 0; i < b.N; i++ { 29 | X1, E1 := DHGenKey(DH1BASE, DH1PRIME) 30 | X2, E2 := DHGenKey(DH1BASE, DH1PRIME) 31 | 32 | big.NewInt(0).Exp(E2, X1, DH1PRIME) 33 | big.NewInt(0).Exp(E1, X2, DH1PRIME) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/misc/crypto/pike/pike.go: -------------------------------------------------------------------------------- 1 | package pike 2 | 3 | type ff_addikey struct { 4 | sd uint32 5 | dis1 int32 6 | dis2 int32 7 | index int32 8 | carry int32 9 | buffer [64]uint32 10 | } 11 | 12 | type Pike struct { 13 | sd uint32 14 | index int32 15 | addikey [3]ff_addikey 16 | buffer [4096]byte 17 | } 18 | 19 | const ( 20 | GENIUS_NUMBER = 0x05027929 21 | ) 22 | 23 | //----------------------------------------------- Encode/Decode a given buffer 24 | func (ctx *Pike) Codec(data []byte) { 25 | length := int32(len(data)) 26 | if length == 0 { 27 | return 28 | } 29 | 30 | off := int32(0) 31 | 32 | for { 33 | remnant := 4096 - ctx.index 34 | if remnant <= 0 { 35 | _generate(ctx) 36 | continue 37 | } 38 | 39 | if remnant > length { 40 | remnant = length 41 | } 42 | length -= remnant 43 | 44 | for i := int32(0); i < remnant; i++ { 45 | data[off] ^= ctx.buffer[ctx.index+i] 46 | off++ 47 | } 48 | ctx.index += remnant 49 | 50 | if length <= 0 { 51 | break 52 | } 53 | } 54 | } 55 | 56 | //----------------------------------------------- Create New Pike 57 | func NewCtx(sd uint32) (ctx *Pike) { 58 | ctx = &Pike{} 59 | ctx.sd = sd ^ GENIUS_NUMBER 60 | 61 | ctx.addikey[0].sd = ctx.sd 62 | ctx.addikey[0].sd = linearity(ctx.addikey[0].sd) 63 | ctx.addikey[0].dis1 = 55 64 | ctx.addikey[0].dis2 = 24 65 | 66 | ctx.addikey[1].sd = ((ctx.sd & 0xAAAAAAAA) >> 1) | ((ctx.sd & 0x55555555) << 1) 67 | ctx.addikey[1].sd = linearity(ctx.addikey[1].sd) 68 | ctx.addikey[1].dis1 = 57 69 | ctx.addikey[1].dis2 = 7 70 | 71 | ctx.addikey[2].sd = ^(((ctx.sd & 0xF0F0F0F0) >> 4) | ((ctx.sd & 0x0F0F0F0F) << 4)) 72 | ctx.addikey[2].sd = linearity(ctx.addikey[2].sd) 73 | ctx.addikey[2].dis1 = 58 74 | ctx.addikey[2].dis2 = 19 75 | 76 | for i := 0; i < 3; i++ { 77 | tmp := ctx.addikey[i].sd 78 | for j := 0; j < 64; j++ { 79 | for k := 0; k < 32; k++ { 80 | tmp = linearity(tmp) 81 | } 82 | ctx.addikey[i].buffer[j] = tmp 83 | } 84 | ctx.addikey[i].carry = 0 85 | ctx.addikey[i].index = 63 86 | } 87 | 88 | ctx.index = 4096 89 | 90 | return 91 | } 92 | 93 | /*! 参见<<应用密码学>>中的线性反馈移位寄存器算法*/ 94 | func linearity(key uint32) uint32 { 95 | return ((((key >> 31) ^ (key >> 6) ^ (key >> 4) ^ (key >> 2) ^ (key >> 1) ^ key) & 0x00000001) << 31) | (key >> 1) 96 | } 97 | 98 | func _addikey_next(addikey *ff_addikey) { 99 | tmp := addikey.index + 1 100 | addikey.index = tmp & 0x03F 101 | 102 | i1 := ((addikey.index | 0x40) - addikey.dis1) & 0x03F 103 | i2 := ((addikey.index | 0x40) - addikey.dis2) & 0x03F 104 | 105 | addikey.buffer[addikey.index] = addikey.buffer[i1] + addikey.buffer[i2] 106 | 107 | if (addikey.buffer[addikey.index] < addikey.buffer[i1]) || (addikey.buffer[addikey.index] < addikey.buffer[i2]) { 108 | addikey.carry = 1 109 | } else { 110 | addikey.carry = 0 111 | } 112 | } 113 | 114 | func _generate(ctx *Pike) { 115 | for i := 0; i < 1024; i++ { 116 | carry := ctx.addikey[0].carry + ctx.addikey[1].carry + ctx.addikey[2].carry 117 | 118 | if carry == 0 || carry == 3 { /*!< 如果三个位相同(全0或全1),那么钟控所有的发生器*/ 119 | _addikey_next(&ctx.addikey[0]) 120 | _addikey_next(&ctx.addikey[1]) 121 | _addikey_next(&ctx.addikey[2]) 122 | } else { /*!< 如果三个位不全相同,则钟控两个相同的发生器*/ 123 | flag := int32(0) 124 | 125 | if carry == 2 { 126 | flag = 1 127 | } 128 | 129 | for j := 0; j < 3; j++ { 130 | if ctx.addikey[j].carry == flag { 131 | _addikey_next(&ctx.addikey[j]) 132 | } 133 | } 134 | } 135 | 136 | tmp := ctx.addikey[0].buffer[ctx.addikey[0].index] ^ ctx.addikey[1].buffer[ctx.addikey[1].index] ^ ctx.addikey[2].buffer[ctx.addikey[2].index] 137 | base := i << 2 138 | ctx.buffer[base] = byte(tmp) 139 | ctx.buffer[base+1] = byte(tmp >> 8) 140 | ctx.buffer[base+2] = byte(tmp >> 16) 141 | ctx.buffer[base+3] = byte(tmp >> 24) 142 | } 143 | 144 | ctx.index = 0 145 | } 146 | -------------------------------------------------------------------------------- /src/misc/crypto/pike/pike_test.go: -------------------------------------------------------------------------------- 1 | package pike 2 | 3 | import "testing" 4 | import "fmt" 5 | 6 | func TestPike(t *testing.T) { 7 | ctx := NewCtx(1234) 8 | fmt.Println("###") 9 | fmt.Println(ctx.sd) 10 | fmt.Println(ctx.addikey[0].buffer) 11 | fmt.Println(ctx.addikey[1].buffer) 12 | fmt.Println(ctx.addikey[2].buffer) 13 | 14 | data := make([]byte, 1024*1024) 15 | for i := 0; i < len(data); i++ { 16 | data[i] = byte(i % 256) 17 | } 18 | ctx.Codec(data) 19 | //fmt.Println("ciphertext:", string(data), len(data)) 20 | ctx1 := NewCtx(1234) 21 | ctx1.Codec(data) 22 | for i := 0; i < 1024*1024; i++ { 23 | if data[i] != byte(i%256) { 24 | t.Error("解码错误") 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/misc/geoip/README.md: -------------------------------------------------------------------------------- 1 | #### 用于处理区间方式表达的IP段->国家名的映射 2 | [StartIP,EndIP] -> Country Code 3 | 4 | "1.0.0.0","1.0.0.255","16777216","16777471","AU","Australia" 5 | "1.0.1.0","1.0.3.255","16777472","16778239","CN","China" 6 | "1.0.4.0","1.0.7.255","16778240","16779263","AU","Australia" 7 | "1.0.8.0","1.0.15.255","16779264","16781311","CN","China" 8 | "1.0.16.0","1.0.31.255","16781312","16785407","JP","Japan" 9 | "1.0.32.0","1.0.63.255","16785408","16793599","CN","China" 10 | "1.0.64.0","1.0.127.255","16793600","16809983","JP","Japan" 11 | "1.0.128.0","1.0.255.255","16809984","16842751","TH","Thailand" 12 | "1.1.0.0","1.1.0.255","16842752","16843007","CN","China" 13 | "1.1.1.0","1.1.1.255","16843008","16843263","AU","Australia" 14 | 15 | ...... 16 | -------------------------------------------------------------------------------- /src/misc/geoip/geoip.go: -------------------------------------------------------------------------------- 1 | package geoip 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/csv" 6 | "log" 7 | "net" 8 | "os" 9 | "strconv" 10 | ) 11 | 12 | import ( 13 | itree "misc/alg/interval_tree" 14 | ) 15 | 16 | const ( 17 | GEOIP = "GeoIPCountryWhois.csv" 18 | FROM_IDX = 2 19 | TO_IDX = 3 20 | COUNTRY_CODE_IDX = 4 21 | ) 22 | 23 | var _geoip itree.Tree 24 | 25 | func init() { 26 | log.Println("Loading GEOIP...") 27 | defer log.Println("GEOIP Load Complete.") 28 | path := os.Getenv("GOPATH") + "/src/misc/geoip/" + GEOIP 29 | file, err := os.Open(path) 30 | if err != nil { 31 | log.Println(err) 32 | panic("error opening geoip file") 33 | } 34 | defer file.Close() 35 | 36 | csv_reader := csv.NewReader(file) 37 | records, err := csv_reader.ReadAll() 38 | if err != nil { 39 | log.Println(err) 40 | panic("cannot read file geoip file") 41 | } 42 | 43 | for k := range records { 44 | from, _ := strconv.Atoi(records[k][FROM_IDX]) 45 | to, _ := strconv.Atoi(records[k][TO_IDX]) 46 | 47 | if from <= to { 48 | _geoip.Insert(int64(from), int64(to), records[k][COUNTRY_CODE_IDX]) 49 | } 50 | } 51 | } 52 | 53 | //------------------------------------------------ Get Country Code 54 | func Query(ip net.IP) string { 55 | i64 := _int64_ip(ip) 56 | if n := _geoip.Lookup(i64, i64); n != nil { 57 | return n.Data().(string) 58 | } 59 | 60 | return "" 61 | } 62 | 63 | func _int64_ip(_ip net.IP) int64 { 64 | ip := _ip.To4() 65 | if ip != nil { 66 | ipv4 := binary.BigEndian.Uint32(ip) 67 | return int64(ipv4) 68 | } 69 | 70 | return 0 71 | } 72 | -------------------------------------------------------------------------------- /src/misc/geoip/geoip_test.go: -------------------------------------------------------------------------------- 1 | package geoip 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | ) 7 | 8 | func TestGeoIP(t *testing.T) { 9 | ip := net.ParseIP("103.14.100.100") 10 | t.Log(Query(ip)) 11 | ip = net.ParseIP("171.216.90.18") 12 | t.Log(Query(ip)) 13 | ip = net.ParseIP("8.35.201.33") 14 | t.Log(Query(ip)) 15 | ip = net.ParseIP("62.216.125.241") 16 | t.Log(Query(ip)) 17 | } 18 | 19 | func BenchmarkGeoIP(b *testing.B) { 20 | ip := net.ParseIP("103.14.100.100") 21 | 22 | for i := 0; i < b.N; i++ { 23 | Query(ip) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/misc/naming/README.md: -------------------------------------------------------------------------------- 1 | 字符串相关的函数: 2 | 3 | foo_bar <-> FooBar 4 | 5 | 字符串hash函数: 6 | 7 | http://www.isthe.com/chongo/tech/comp/fnv/# 8 | 9 | http://en.wikipedia.org/wiki/Fowler_Noll_Vo_hash 10 | 11 | FNV-1a hash 12 | 13 | The FNV-1a hash differs from the FNV-1 hash by only the order in which the multiply and XOR is performed:[7] 14 |
15 |    hash = FNV_offset_basis       
16 |    for each octet_of_data to be hashed      
17 |       hash = hash XOR octet_of_data       
18 |       hash = hash × FNV_prime       
19 |    return hash        
20 | 
21 | -------------------------------------------------------------------------------- /src/misc/naming/naming.go: -------------------------------------------------------------------------------- 1 | package naming 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | //----------------------------------------------- "FooBar" => "foo_bar" 9 | var regexp1 = regexp.MustCompile(`([A-Z]+)([A-Z][a-z])`) 10 | var regexp2 = regexp.MustCompile(`([a-z])([A-Z])`) 11 | 12 | func UnderScore(str string) string { 13 | ret := regexp1.ReplaceAllString(str, `${1}_${2}`) 14 | ret = regexp2.ReplaceAllString(ret, `${1}_${2}`) 15 | 16 | return strings.ToLower(strings.Replace(string(ret), "-", "_", -1)) 17 | } 18 | 19 | //----------------------------------------------- "foo_bar" -> "FooBar" 20 | var regexp3 = regexp.MustCompile(`^[a-z]|_[a-z]`) 21 | 22 | func CamelCase(str string) string { 23 | str = strings.ToLower(str) 24 | 25 | ret := regexp3.ReplaceAllFunc([]byte(str), func(match []byte) []byte { 26 | v := strings.TrimLeft(string(match), "_") 27 | v = strings.ToUpper(v) 28 | return []byte(v) 29 | }) 30 | 31 | return string(ret) 32 | } 33 | 34 | //------------------------------------------------ FNV-1a 32-bit String Hash 35 | func FNV1a(str string) uint32 { 36 | FNV_prime := uint32(16777619) 37 | hash := uint32(2166136261) 38 | octects := []byte(str) 39 | 40 | for _, v := range octects { 41 | hash = hash ^ uint32(v) 42 | hash = hash * FNV_prime 43 | } 44 | 45 | return hash 46 | } 47 | -------------------------------------------------------------------------------- /src/misc/naming/naming_test.go: -------------------------------------------------------------------------------- 1 | package naming 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestNaming(t *testing.T) { 9 | if CamelCase("foo_bar") != "FooBar" { 10 | t.Error("failed foo_bar") 11 | } 12 | 13 | if CamelCase("_foo_bar") != "FooBar" { 14 | t.Error("failed _foo_bar") 15 | } 16 | 17 | if UnderScore("FooBar") != "foo_bar" { 18 | t.Error("failed FooBar") 19 | } 20 | 21 | fmt.Println("FNV-1a Hashing:") 22 | fmt.Printf("%x\n", FNV1a("")) 23 | fmt.Printf("%x\n", FNV1a("a")) 24 | fmt.Printf("%x\n", FNV1a("foobar")) 25 | 26 | if FNV1a("foobar") != 0xbf9cf968 { 27 | t.Error("FNV hash failed") 28 | } 29 | } 30 | 31 | func BenchmarkFNV(b *testing.B) { 32 | for i := 0; i < b.N; i++ { 33 | FNV1a("foobar") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/misc/packet/pack.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "log" 5 | "reflect" 6 | ) 7 | 8 | type FastPack interface { 9 | Pack(w *Packet) 10 | } 11 | 12 | //----------------------------------------------- export struct fields with packet writer. 13 | func Pack(tos int16, tbl interface{}, writer *Packet) []byte { 14 | // create writer if not specified 15 | if writer == nil { 16 | writer = Writer() 17 | } 18 | 19 | // write protocol number 20 | if tos != -1 { 21 | writer.WriteU16(uint16(tos)) 22 | } 23 | 24 | // is the table nil? 25 | if tbl == nil { 26 | return writer.Data() 27 | } 28 | 29 | // fastpack 30 | if fastpack, ok := tbl.(FastPack); ok { 31 | fastpack.Pack(writer) 32 | return writer.Data() 33 | } 34 | 35 | // pack by reflection 36 | _pack(reflect.ValueOf(tbl), writer) 37 | 38 | // return byte array 39 | return writer.Data() 40 | } 41 | 42 | //----------------------------------------------- export struct fields with packet writer. 43 | func _pack(v reflect.Value, writer *Packet) { 44 | switch v.Kind() { 45 | case reflect.Bool: 46 | writer.WriteBool(v.Bool()) 47 | case reflect.Uint8: 48 | writer.WriteByte(byte(v.Uint())) 49 | case reflect.Uint16: 50 | writer.WriteU16(uint16(v.Uint())) 51 | case reflect.Uint32: 52 | writer.WriteU32(uint32(v.Uint())) 53 | case reflect.Uint64: 54 | writer.WriteU64(uint64(v.Uint())) 55 | 56 | case reflect.Int16: 57 | writer.WriteS16(int16(v.Int())) 58 | case reflect.Int32: 59 | writer.WriteS32(int32(v.Int())) 60 | case reflect.Int64: 61 | writer.WriteS64(int64(v.Int())) 62 | 63 | case reflect.Float32: 64 | writer.WriteFloat32(float32(v.Float())) 65 | case reflect.Float64: 66 | writer.WriteFloat64(float64(v.Float())) 67 | 68 | case reflect.String: 69 | writer.WriteString(v.String()) 70 | case reflect.Ptr, reflect.Interface: 71 | if v.IsNil() { 72 | return 73 | } 74 | _pack(v.Elem(), writer) 75 | case reflect.Slice: 76 | l := v.Len() 77 | writer.WriteU16(uint16(l)) 78 | for i := 0; i < l; i++ { 79 | _pack(v.Index(i), writer) 80 | } 81 | case reflect.Struct: 82 | numFields := v.NumField() 83 | for i := 0; i < numFields; i++ { 84 | _pack(v.Field(i), writer) 85 | } 86 | default: 87 | log.Println("cannot pack type:", v) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/misc/packet/pack_test.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import "testing" 4 | import "fmt" 5 | 6 | type SUB2 struct { 7 | M []int16 8 | } 9 | 10 | type SUB struct { 11 | H int16 12 | I uint16 13 | } 14 | type TEST struct { 15 | BOOL bool 16 | A int32 17 | B string 18 | C float32 19 | D uint32 20 | E float64 21 | F []byte 22 | Sub []SUB 23 | S2 SUB 24 | S3 SUB2 25 | } 26 | 27 | type TEST2 struct { 28 | BOOL bool 29 | A int32 30 | B string 31 | C float32 32 | D uint32 33 | E float64 34 | } 35 | 36 | func TestPack(t *testing.T) { 37 | 38 | test := TEST{BOOL: true, A: 16, B: string([]byte{65}), C: 1.0, D: 32, E: 1.0, F: []byte{1, 2, 3, 4, 5}} 39 | test.Sub = make([]SUB, 2) 40 | test.Sub[0].H = 1024 41 | test.Sub[0].I = 2048 42 | test.Sub[1].H = 4096 43 | test.Sub[1].I = 8192 44 | test.S2.H = 100 45 | test.S3.M = make([]int16, 10) 46 | 47 | fmt.Println(Pack(128, test, nil)) 48 | fmt.Println(Pack(128, &test, nil)) 49 | fmt.Println(Pack(129, nil, nil)) 50 | } 51 | 52 | func BenchmarkPack(b *testing.B) { 53 | test := TEST2{BOOL: true, A: 16, B: string([]byte{65}), C: 1.0, D: 32, E: 1.0} 54 | for i := 0; i < b.N; i++ { 55 | Pack(128, test, nil) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/misc/packet/packet.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | ) 7 | 8 | const ( 9 | PACKET_LIMIT = 65535 10 | PACKET_POOL = 10000 11 | ) 12 | 13 | var ( 14 | _pool = make(chan *Packet, PACKET_POOL) 15 | ) 16 | 17 | type Packet struct { 18 | pos int 19 | data []byte 20 | } 21 | 22 | func init() { 23 | go func() { 24 | for { 25 | _pool <- &Packet{data: make([]byte, 0, 512)} 26 | } 27 | }() 28 | } 29 | 30 | func (p *Packet) Data() []byte { 31 | return p.data 32 | } 33 | 34 | func (p *Packet) Length() int { 35 | return len(p.data) 36 | } 37 | 38 | //=============================================== Readers 39 | func (p *Packet) ReadBool() (ret bool, err error) { 40 | b, _err := p.ReadByte() 41 | 42 | if b != byte(1) { 43 | return false, _err 44 | } 45 | 46 | return true, _err 47 | } 48 | 49 | func (p *Packet) ReadByte() (ret byte, err error) { 50 | if p.pos >= len(p.data) { 51 | err = errors.New("read byte failed") 52 | return 53 | } 54 | 55 | ret = p.data[p.pos] 56 | p.pos++ 57 | return 58 | } 59 | 60 | func (p *Packet) ReadBytes() (ret []byte, err error) { 61 | if p.pos+2 > len(p.data) { 62 | err = errors.New("read bytes header failed") 63 | return 64 | } 65 | size, _ := p.ReadU16() 66 | if p.pos+int(size) > len(p.data) { 67 | err = errors.New("read bytes data failed") 68 | return 69 | } 70 | 71 | ret = p.data[p.pos : p.pos+int(size)] 72 | p.pos += int(size) 73 | return 74 | } 75 | 76 | func (p *Packet) ReadString() (ret string, err error) { 77 | if p.pos+2 > len(p.data) { 78 | err = errors.New("read string header failed") 79 | return 80 | } 81 | 82 | size, _ := p.ReadU16() 83 | if p.pos+int(size) > len(p.data) { 84 | err = errors.New("read string data failed") 85 | return 86 | } 87 | 88 | bytes := p.data[p.pos : p.pos+int(size)] 89 | p.pos += int(size) 90 | ret = string(bytes) 91 | return 92 | } 93 | 94 | func (p *Packet) ReadU16() (ret uint16, err error) { 95 | if p.pos+2 > len(p.data) { 96 | err = errors.New("read uint16 failed") 97 | return 98 | } 99 | 100 | buf := p.data[p.pos : p.pos+2] 101 | ret = uint16(buf[0])<<8 | uint16(buf[1]) 102 | p.pos += 2 103 | return 104 | } 105 | 106 | func (p *Packet) ReadS16() (ret int16, err error) { 107 | _ret, _err := p.ReadU16() 108 | ret = int16(_ret) 109 | err = _err 110 | return 111 | } 112 | 113 | func (p *Packet) ReadU24() (ret uint32, err error) { 114 | if p.pos+3 > len(p.data) { 115 | err = errors.New("read uint24 failed") 116 | return 117 | } 118 | 119 | buf := p.data[p.pos : p.pos+3] 120 | ret = uint32(buf[0])<<16 | uint32(buf[1])<<8 | uint32(buf[2]) 121 | p.pos += 3 122 | return 123 | } 124 | 125 | func (p *Packet) ReadS24() (ret int32, err error) { 126 | _ret, _err := p.ReadU24() 127 | ret = int32(_ret) 128 | err = _err 129 | return 130 | } 131 | 132 | func (p *Packet) ReadU32() (ret uint32, err error) { 133 | if p.pos+4 > len(p.data) { 134 | err = errors.New("read uint32 failed") 135 | return 136 | } 137 | 138 | buf := p.data[p.pos : p.pos+4] 139 | ret = uint32(buf[0])<<24 | uint32(buf[1])<<16 | uint32(buf[2])<<8 | uint32(buf[3]) 140 | p.pos += 4 141 | return 142 | } 143 | 144 | func (p *Packet) ReadS32() (ret int32, err error) { 145 | _ret, _err := p.ReadU32() 146 | ret = int32(_ret) 147 | err = _err 148 | return 149 | } 150 | 151 | func (p *Packet) ReadU64() (ret uint64, err error) { 152 | if p.pos+8 > len(p.data) { 153 | err = errors.New("read uint64 failed") 154 | return 155 | } 156 | 157 | ret = 0 158 | buf := p.data[p.pos : p.pos+8] 159 | for i, v := range buf { 160 | ret |= uint64(v) << uint((7-i)*8) 161 | } 162 | p.pos += 8 163 | return 164 | } 165 | 166 | func (p *Packet) ReadS64() (ret int64, err error) { 167 | _ret, _err := p.ReadU64() 168 | ret = int64(_ret) 169 | err = _err 170 | return 171 | } 172 | 173 | func (p *Packet) ReadFloat32() (ret float32, err error) { 174 | bits, _err := p.ReadU32() 175 | if _err != nil { 176 | return float32(0), _err 177 | } 178 | 179 | ret = math.Float32frombits(bits) 180 | if math.IsNaN(float64(ret)) || math.IsInf(float64(ret), 0) { 181 | return 0, nil 182 | } 183 | 184 | return ret, nil 185 | } 186 | 187 | func (p *Packet) ReadFloat64() (ret float64, err error) { 188 | bits, _err := p.ReadU64() 189 | if _err != nil { 190 | return float64(0), _err 191 | } 192 | 193 | ret = math.Float64frombits(bits) 194 | if math.IsNaN(ret) || math.IsInf(ret, 0) { 195 | return 0, nil 196 | } 197 | 198 | return ret, nil 199 | } 200 | 201 | //================================================ Writers 202 | func (p *Packet) WriteZeros(n int) { 203 | for i := 0; i < n; i++ { 204 | p.data = append(p.data, byte(0)) 205 | } 206 | } 207 | 208 | func (p *Packet) WriteBool(v bool) { 209 | if v { 210 | p.data = append(p.data, byte(1)) 211 | } else { 212 | p.data = append(p.data, byte(0)) 213 | } 214 | } 215 | 216 | func (p *Packet) WriteByte(v byte) { 217 | p.data = append(p.data, v) 218 | } 219 | 220 | func (p *Packet) WriteBytes(v []byte) { 221 | p.WriteU16(uint16(len(v))) 222 | p.data = append(p.data, v...) 223 | } 224 | 225 | func (p *Packet) WriteRawBytes(v []byte) { 226 | p.data = append(p.data, v...) 227 | } 228 | 229 | func (p *Packet) WriteString(v string) { 230 | bytes := []byte(v) 231 | p.WriteU16(uint16(len(bytes))) 232 | p.data = append(p.data, bytes...) 233 | } 234 | 235 | func (p *Packet) WriteU16(v uint16) { 236 | p.data = append(p.data, byte(v>>8), byte(v)) 237 | } 238 | 239 | func (p *Packet) WriteS16(v int16) { 240 | p.WriteU16(uint16(v)) 241 | } 242 | 243 | func (p *Packet) WriteU24(v uint32) { 244 | p.data = append(p.data, byte(v>>16), byte(v>>8), byte(v)) 245 | } 246 | 247 | func (p *Packet) WriteU32(v uint32) { 248 | p.data = append(p.data, byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) 249 | } 250 | 251 | func (p *Packet) WriteS32(v int32) { 252 | p.WriteU32(uint32(v)) 253 | } 254 | 255 | func (p *Packet) WriteU64(v uint64) { 256 | p.data = append(p.data, byte(v>>56), byte(v>>48), byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) 257 | } 258 | 259 | func (p *Packet) WriteS64(v int64) { 260 | p.WriteU64(uint64(v)) 261 | } 262 | 263 | func (p *Packet) WriteFloat32(f float32) { 264 | v := math.Float32bits(f) 265 | p.WriteU32(v) 266 | } 267 | 268 | func (p *Packet) WriteFloat64(f float64) { 269 | v := math.Float64bits(f) 270 | p.WriteU64(v) 271 | } 272 | 273 | func Reader(data []byte) *Packet { 274 | return &Packet{data: data} 275 | } 276 | 277 | func Writer() *Packet { 278 | return <-_pool 279 | } 280 | -------------------------------------------------------------------------------- /src/misc/packet/packet_test.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestPacketWriter(t *testing.T) { 9 | p := Writer() 10 | a := byte(0xFF) 11 | b := uint16(0xFF00) 12 | c := uint32(0xFF0000) 13 | d := uint32(0xFF000000) 14 | e := uint64(0xFF00000000000000) 15 | f32 := float32(1.0) 16 | f64 := float64(2.0) 17 | 18 | p.WriteBool(true) 19 | p.WriteByte(a) 20 | p.WriteU16(b) 21 | p.WriteU24(c) 22 | p.WriteU32(d) 23 | p.WriteU64(e) 24 | p.WriteFloat32(f32) 25 | p.WriteFloat64(f64) 26 | 27 | p.WriteString("hello world") 28 | p.WriteBytes([]byte("hello world")) 29 | var nil_bytes []byte 30 | p.WriteBytes(nil_bytes) 31 | 32 | reader := Reader(p.Data()) 33 | 34 | BOOL, _ := reader.ReadBool() 35 | if BOOL != true { 36 | t.Error("packet readbool mismatch") 37 | } 38 | 39 | tmp, _ := reader.ReadByte() 40 | if a != tmp { 41 | t.Error("packet readbyte mismatch") 42 | } 43 | 44 | tmp1, _ := reader.ReadU16() 45 | if b != tmp1 { 46 | t.Error("packet readu16 mismatch") 47 | } 48 | 49 | tmp2, _ := reader.ReadU24() 50 | if c != tmp2 { 51 | t.Error("packet readu24 mismatch") 52 | } 53 | 54 | tmp3, _ := reader.ReadU32() 55 | if d != tmp3 { 56 | t.Error("packet readu32 mismatch") 57 | } 58 | 59 | tmp4, _ := reader.ReadU64() 60 | if e != tmp4 { 61 | t.Error("packet readu64 mismatch") 62 | } 63 | 64 | tmp5, _ := reader.ReadFloat32() 65 | if f32 != tmp5 { 66 | t.Error("packet readf32 mismatch") 67 | } 68 | 69 | tmp6, _ := reader.ReadFloat64() 70 | if f64 != tmp6 { 71 | t.Error("packet readf32 mismatch") 72 | } 73 | 74 | tmp100, _ := reader.ReadString() 75 | 76 | if "hello world" != tmp100 { 77 | t.Error("packet read string mistmatch") 78 | } 79 | 80 | tmp101, _ := reader.ReadBytes() 81 | 82 | fmt.Println(tmp101) 83 | if tmp101[0] != 'h' { 84 | t.Error("packet read bytes mistmatch") 85 | } 86 | 87 | tmp102, _ := reader.ReadBytes() 88 | fmt.Println("NIL:", tmp102) 89 | if len(tmp102) != 0 { 90 | t.Error("packet read nil bytes mistmatch") 91 | } 92 | 93 | _, err := reader.ReadByte() 94 | 95 | if err == nil { 96 | t.Error("overflow check failed") 97 | } 98 | } 99 | 100 | func BenchmarkPacketWriter(b *testing.B) { 101 | for i := 0; i < b.N; i++ { 102 | p := Writer() 103 | p.WriteU16(128) 104 | p.WriteBool(true) 105 | p.WriteS32(-16) 106 | p.WriteString("A") 107 | p.WriteFloat32(1.0) 108 | p.WriteU32(16) 109 | p.WriteFloat64(1.0) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/misc/timer/README.md: -------------------------------------------------------------------------------- 1 | 通用计时器 2 | 3 | 时间表示为 int64 Unix 时间 4 | 5 | 用层级的方式处理任意长度的定时器消息 6 | -------------------------------------------------------------------------------- /src/misc/timer/timer.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | import ( 4 | "log" 5 | "time" 6 | ) 7 | 8 | const ( 9 | TIMER_LEVEL = 20 // 时间段最大分级,最大时间段为 2^TIMERLEVEL 10 | QUEUE_MAX = 65536 11 | ) 12 | 13 | type _timer_event struct { 14 | Id int32 // 用户定义的ID 15 | Timeout int64 // 到期时间 Unix Time 16 | CH chan int32 // 发送通道 17 | } 18 | 19 | var ( 20 | _eventlist [TIMER_LEVEL]map[uint32]_timer_event // 事件列表 21 | _eventqueue chan _timer_event // 事件添加队列 22 | ) 23 | 24 | func init() { 25 | for k := range _eventlist { 26 | _eventlist[k] = make(map[uint32]_timer_event) 27 | } 28 | 29 | _eventqueue = make(chan _timer_event, QUEUE_MAX) 30 | 31 | go _timer() 32 | } 33 | 34 | //------------------------------------------------ 35 | // 定时器 goroutine 36 | // 根据程序启动后经过的秒数计数 37 | func _timer() { 38 | defer func() { 39 | if x := recover(); x != nil { 40 | log.Println("TIMER CRASHED", x) 41 | } 42 | }() 43 | 44 | last := time.Now().Unix() 45 | timer_id := uint32(0) // 内部事件编号 46 | 47 | sleep_timer := time.NewTimer(time.Second) 48 | for { 49 | select { 50 | case new_event := <-_eventqueue: 51 | timer_id++ 52 | // 最小的时间间隔,处理为1s 53 | diff := new_event.Timeout - time.Now().Unix() 54 | if diff <= 0 { 55 | diff = 1 56 | } 57 | 58 | // 发到合适的框 59 | for i := TIMER_LEVEL - 1; i >= 0; i-- { 60 | if diff >= 1<= 0; i-- { 81 | mask := (1 << uint(i)) - 1 82 | if last&int64(mask) == 0 { 83 | _trigger(i) 84 | } 85 | } 86 | 87 | // 如果到达当前时间,停止循环 88 | if last == now { 89 | break 90 | } 91 | } 92 | } 93 | } 94 | } 95 | 96 | //------------------------------------------------ 单级触发 97 | func _trigger(level int) { 98 | now := time.Now().Unix() 99 | list := _eventlist[level] 100 | 101 | for k, v := range list { 102 | if v.Timeout-now < 1<code(uint16) 3 | ## 4 | ## packet_type:0 5 | ## name:heart_beat_req 6 | ## payload:null 7 | ## desc:心跳包.. 8 | ## 9 | BEGIN { RS = ""; FS ="\n" 10 | print "var Code = map[string]int16 {" 11 | } 12 | { 13 | for (i=1;i<=NF;i++) 14 | { 15 | if ($i ~ /^#.*/) { 16 | continue 17 | } 18 | 19 | split($i, a, ":") 20 | if (a[1] == "packet_type") { 21 | array["packet_type"] = a[2] 22 | } else if (a[1] == "name") { 23 | array["name"] = a[2] 24 | } else if (a[1] == "payload") { 25 | array["payload"] = a[2] 26 | } else if (a[1] == "desc") { 27 | array["desc"] = a[2] 28 | } 29 | } 30 | 31 | if ("packet_type" in array && "name" in array) { 32 | print "\t\""array["name"]"\":"array["packet_type"]",\t// "array["desc"] 33 | } 34 | 35 | delete array 36 | } 37 | END { 38 | print "}\n" 39 | } 40 | -------------------------------------------------------------------------------- /src/scripts/api.txt: -------------------------------------------------------------------------------- 1 | # 格式说明= 2 | # 客户端请求名_req结束. 3 | # 服务端回复包_ack结束. 4 | # 服务端通知包_notify结束. 5 | 6 | packet_type:0 7 | name:heart_beat_req 8 | payload:null 9 | desc:心跳包.. 10 | 11 | packet_type:1 12 | name:user_login_req 13 | payload:user_login_info 14 | desc:客户端发送用户登陆请求包 15 | 16 | packet_type:2 17 | name:user_login_succeed_ack 18 | payload:user_snapshot 19 | desc:登陆成功 20 | 21 | packet_type:3 22 | name:user_login_faild_ack 23 | payload:command_result_pack 24 | desc:登陆失败 25 | 26 | packet_type:1000 27 | name:talk_req 28 | payload:talk 29 | desc:talk给一个用户 30 | 31 | packet_type:1001 32 | name:talk_notify 33 | payload:talk 34 | desc:notify客户端 35 | -------------------------------------------------------------------------------- /src/scripts/api_bind_req.awk: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | ## Scripts for generate ProtoHandler map binding code 3 | ## 4 | BEGIN { RS = ""; FS ="\n" } 5 | { 6 | for (i=1;i<=NF;i++) 7 | { 8 | if ($i ~ /^#.*/) { 9 | continue 10 | } 11 | 12 | split($i, a, ":") 13 | if (a[1] == "packet_type") { 14 | array["packet_type"] = a[2] 15 | } else if (a[1] == "name") { 16 | if (a[2] !~ /.*_req$/) { 17 | break 18 | } else { 19 | array["name"] = a[2] 20 | } 21 | } 22 | } 23 | 24 | if ("packet_type" in array && "name" in array) { 25 | print "\t"array["packet_type"]":P_"array["name"]"," 26 | } 27 | 28 | delete array 29 | } 30 | -------------------------------------------------------------------------------- /src/scripts/api_rcode.awk: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | ## Scripts for generate protocol code(uint16)->(string) 3 | ## 4 | BEGIN { RS = ""; FS ="\n" 5 | print "var RCode = map[int16]string {" 6 | } 7 | { 8 | for (i=1;i<=NF;i++) 9 | { 10 | if ($i ~ /^#.*/) { 11 | continue 12 | } 13 | 14 | split($i, a, ":") 15 | if (a[1] == "packet_type") { 16 | array["packet_type"] = a[2] 17 | } else if (a[1] == "name") { 18 | array["name"] = a[2] 19 | } else if (a[1] == "payload") { 20 | array["payload"] = a[2] 21 | } else if (a[1] == "desc") { 22 | array["desc"] = a[2] 23 | } 24 | } 25 | 26 | if ("packet_type" in array && "name" in array) { 27 | print "\t"array["packet_type"]":\""array["name"]"\",\t// "array["desc"] 28 | } 29 | 30 | delete array 31 | } 32 | END { 33 | print "}\n" 34 | } 35 | -------------------------------------------------------------------------------- /src/scripts/hub_api.txt: -------------------------------------------------------------------------------- 1 | # HUB 通信 2 | 3 | packet_type:0 4 | name:ping_req 5 | payload:INT 6 | desc:PING 7 | 8 | ############## 9 | packet_type:1 10 | name:login_req 11 | payload:id 12 | desc:登陆 13 | 14 | packet_type:2 15 | name:logout_req 16 | payload:id 17 | desc:登出 18 | 19 | packet_type:3 20 | name:raid_req 21 | payload:id 22 | desc:攻击 23 | 24 | packet_type:4 25 | name:protect_req 26 | payload:PROTECT 27 | desc:加保护 28 | 29 | ### 30 | packet_type:5 31 | name:free_req 32 | payload:id 33 | desc:结束攻击 34 | 35 | ##### 36 | packet_type:6 37 | name:adduser_req 38 | payload:id 39 | desc:注册一个新注册的玩家 40 | 41 | ########## IPC forward 42 | packet_type:100 43 | name:forward_req 44 | payload:FORWARDIPC 45 | desc:转发IPC消息 46 | -------------------------------------------------------------------------------- /src/scripts/hub_proto.txt: -------------------------------------------------------------------------------- 1 | # 转发IPC消息 2 | FORWARDIPC= 3 | IPC array byte 4 | === 5 | 6 | #用户登陆发包 7 | LOGIN_REQ= 8 | id int32 9 | === 10 | 11 | #用户登录回包 12 | LOGIN_ACK= 13 | success int32 14 | === 15 | 16 | #只携带ID的包 17 | ID= 18 | id int32 19 | === 20 | 21 | PROTECT= 22 | id int32 23 | protecttime int64 24 | === 25 | 26 | ID_SCORE= 27 | id int32 28 | score int32 29 | === 30 | 31 | LIST= 32 | items array ID_SCORE 33 | === 34 | 35 | LONG= 36 | v int64 37 | === 38 | 39 | STRING= 40 | v string 41 | === 42 | 43 | INT= 44 | v int32 45 | === 46 | -------------------------------------------------------------------------------- /src/scripts/proto.awk: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | ## generate proto payload struct 3 | ## 4 | BEGIN { RS = "==="; FS ="\n" 5 | print "" 6 | print "import \"misc/packet\"\n" 7 | ## 类型映射 8 | TYPES["byte"]="byte" 9 | TYPES["short"]="int16" 10 | TYPES["int16"]="int16" 11 | TYPES["uint16"]="uint16" 12 | TYPES["string"]="string" 13 | TYPES["integer"]="int32" 14 | TYPES["int32"]="int32" 15 | TYPES["uint32"]="uint32" 16 | TYPES["long"]="int64" 17 | TYPES["int64"]="int64" 18 | TYPES["uint64"]="uint64" 19 | TYPES["bool"]="bool" 20 | TYPES["boolean"]="bool" 21 | TYPES["float"]="float32" 22 | TYPES["float32"]="float32" 23 | TYPES["double"]="float64" 24 | TYPES["float64"]="float64" 25 | } 26 | { 27 | for (i=1;i<=NF;i++) 28 | { 29 | if ($i ~ /[A-Za-z_]+=/) { 30 | name = substr($i,1, match($i,/=/)-1) 31 | print "type " name " struct {" 32 | typeok = "true" 33 | } else { 34 | if ($i!="" && typeok) { 35 | v = _field($i) 36 | print v 37 | } 38 | } 39 | 40 | } 41 | 42 | if (typeok) print "}\n" 43 | typeok=false 44 | } 45 | END { } 46 | 47 | function _field(line) { 48 | split(line, a, " ") 49 | 50 | if (a[2] in TYPES) { 51 | return "\tF_"a[1] " " TYPES[a[2]] 52 | } else if (a[2] == "array") { 53 | if (a[3] in TYPES) { 54 | return "\tF_"a[1]" []" TYPES[a[3]] 55 | } else { 56 | return "\tF_"a[1]" []" a[3] 57 | } 58 | } else { 59 | return "\tF_"a[1]" " a[2] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/scripts/proto.txt: -------------------------------------------------------------------------------- 1 | #用户登陆发包 2 | user_login_info= 3 | mac_addr string 4 | client_version integer 5 | new_user boolean 6 | user_name string 7 | === 8 | 9 | #用户信息包. 10 | user_snapshot= 11 | id integer 12 | name string 13 | rank integer 14 | archives string 15 | protect_time integer 16 | last_save_time integer 17 | server_time integer 18 | === 19 | 20 | #普通错误信息 21 | command_result_pack= 22 | rst integer 23 | === 24 | 25 | #talk 26 | talk= 27 | user string 28 | msg string 29 | === 30 | -------------------------------------------------------------------------------- /src/scripts/proto_func.awk: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | ## generate protocol packet reader 3 | ## 4 | BEGIN { RS = ""; FS ="\n" 5 | ## 读取函数映射 6 | TYPES["byte"]="ReadByte" 7 | TYPES["short"]="ReadS16" 8 | TYPES["int16"]="ReadS16" 9 | TYPES["uint16"]="ReadU16" 10 | TYPES["string"]="ReadString" 11 | TYPES["integer"]="ReadS32" 12 | TYPES["int32"]="ReadS32" 13 | TYPES["uint32"]="ReadU32" 14 | TYPES["long"]="ReadS64" 15 | TYPES["int64"]="ReadS64" 16 | TYPES["uint64"]="ReadU64" 17 | TYPES["bool"]="ReadBool" 18 | TYPES["boolean"]="ReadBool" 19 | TYPES["float"]="ReadFloat32" 20 | TYPES["float32"]="ReadFloat32" 21 | TYPES["double"]="ReadFloat64" 22 | TYPES["float64"]="ReadFloat64" 23 | } 24 | { 25 | for (i=1;i<=NF;i++) 26 | { 27 | if ($i ~ /^#.*/ || $i ~ /^===/) { 28 | continue 29 | } 30 | 31 | split($i, a, " ") 32 | if (a[1] ~ /[A-Za-z_]+=/) { 33 | name = substr(a[1],1, match(a[1],/=/)-1) 34 | print "func PKT_"name"(reader *packet.Packet)(tbl "name", err error){" 35 | typeok = "true" 36 | } else if (a[2] == "array") { 37 | if (a[3] == "byte") { ## bytes 38 | print "\ttbl.F_"a[1]", err = reader.ReadBytes()" 39 | print "\tcheckErr(err)\n" 40 | } else if (a[3] in TYPES) { ## primitives 41 | print "\t{" 42 | print "\tnarr,err := reader.ReadU16()" 43 | print "\tcheckErr(err)\n" 44 | print "\tfor i:=0;i proto.go 7 | gawk -f proto.awk proto.txt >> proto.go 8 | gawk -f proto_func.awk proto.txt >> proto.go 9 | 10 | printf "package net\n" > api.go 11 | printf "\n" >> api.go 12 | printf "import \"misc/packet\"\n" >> api.go 13 | printf "import . \"types\"\n" >> api.go 14 | printf "\n" >> api.go 15 | 16 | gawk -f api.awk api.txt >> api.go 17 | gawk -f api_rcode.awk api.txt >> api.go 18 | 19 | printf "var ProtoHandler map[int16]func(*Session, *packet.Packet) []byte\n" >> api.go 20 | printf "func init() {\n" >> api.go 21 | printf "ProtoHandler = map[int16]func(*Session, *packet.Packet) []byte {\n" >> api.go 22 | gawk -f api_bind_req.awk api.txt >> api.go 23 | printf "}" >> api.go 24 | printf "}" >> api.go 25 | 26 | mv -f proto.go ../agent/net 27 | mv -f api.go ../agent/net 28 | go fmt ../agent/net 29 | ################################################## 30 | ### a copy of proto into ipc 31 | ################################################## 32 | printf "package ipc_service\n" > proto.go 33 | gawk -f proto.awk proto.txt >> proto.go 34 | gawk -f proto_func.awk proto.txt >> proto.go 35 | 36 | mv -f proto.go ../agent/ipc_service 37 | go fmt ../agent/ipc_service 38 | 39 | ################################################## 40 | ### hub proto & api 41 | ################################################## 42 | printf "package protos\n" > proto.go 43 | gawk -f proto.awk hub_proto.txt >> proto.go 44 | gawk -f proto_func.awk hub_proto.txt >> proto.go 45 | 46 | printf "package protos\n" > api.go 47 | printf "\n" >> api.go 48 | printf "import \"misc/packet\"\n" >> api.go 49 | printf "\n" >> api.go 50 | 51 | gawk -f api.awk hub_api.txt >> api.go 52 | gawk -f api_rcode.awk hub_api.txt >> api.go 53 | 54 | printf "var ProtoHandler map[int16]func(int32, *packet.Packet) []byte\n" >> api.go 55 | printf "func init() {\n" >> api.go 56 | printf "ProtoHandler = map[int16]func(int32, *packet.Packet) []byte {\n" >> api.go 57 | gawk -f api_bind_req.awk hub_api.txt >> api.go 58 | printf "}" >> api.go 59 | printf "}" >> api.go 60 | 61 | mv -f proto.go ../hub/protos 62 | mv -f api.go ../hub/protos 63 | go fmt ../hub/protos 64 | 65 | ################################################## 66 | ### a copy of hub proto & api into agent/hub_client 67 | ################################################## 68 | printf "package hub_client\n" > proto.go 69 | gawk -f proto.awk hub_proto.txt >> proto.go 70 | gawk -f proto_func.awk hub_proto.txt >> proto.go 71 | 72 | printf "package hub_client\n" > api.go 73 | printf "\n" >> api.go 74 | 75 | gawk -f api.awk hub_api.txt >> api.go 76 | gawk -f api_rcode.awk hub_api.txt >> api.go 77 | 78 | mv -f proto.go ../agent/hub_client 79 | mv -f api.go ../agent/hub_client 80 | go fmt ../agent/hub_client 81 | 82 | ################################################## 83 | ### stats proto & api 84 | ################################################## 85 | printf "package protos\n" > proto.go 86 | gawk -f proto.awk stats_proto.txt >> proto.go 87 | gawk -f proto_func.awk stats_proto.txt >> proto.go 88 | 89 | printf "package protos\n" > api.go 90 | printf "\n" >> api.go 91 | printf "import \"misc/packet\"\n" >> api.go 92 | printf "\n" >> api.go 93 | 94 | gawk -f api.awk stats_api.txt >> api.go 95 | gawk -f api_rcode.awk stats_api.txt >> api.go 96 | 97 | printf "var ProtoHandler map[int16]func(*packet.Packet) []byte\n" >> api.go 98 | printf "func init() {\n" >> api.go 99 | printf "ProtoHandler = map[int16]func(*packet.Packet) []byte {\n" >> api.go 100 | gawk -f api_bind_req.awk stats_api.txt >> api.go 101 | printf "}" >> api.go 102 | printf "}" >> api.go 103 | 104 | mv -f proto.go ../stats/protos 105 | mv -f api.go ../stats/protos 106 | go fmt ../stats/protos 107 | 108 | ################################################## 109 | ### a copy of proto into stats_client 110 | ################################################## 111 | printf "package stats_client\n" > proto.go 112 | gawk -f proto.awk stats_proto.txt >> proto.go 113 | gawk -f proto_func.awk stats_proto.txt >> proto.go 114 | 115 | printf "package stats_client\n" > api.go 116 | printf "\n" >> api.go 117 | 118 | gawk -f api.awk stats_api.txt >> api.go 119 | gawk -f api_rcode.awk stats_api.txt >> api.go 120 | 121 | mv -f proto.go ../agent/stats_client 122 | mv -f api.go ../agent/stats_client 123 | go fmt ../agent/stats_client 124 | -------------------------------------------------------------------------------- /src/scripts/stats_api.txt: -------------------------------------------------------------------------------- 1 | packet_type:100 2 | name:set_adds_req 3 | payload:SET_ADDS_REQ 4 | desc:累计信息 5 | 6 | packet_type:200 7 | name:set_update_req 8 | payload:SET_UPDATE_REQ 9 | desc:更新信息 10 | -------------------------------------------------------------------------------- /src/scripts/stats_proto.txt: -------------------------------------------------------------------------------- 1 | #累计信息 2 | SET_ADDS_REQ= 3 | key string 4 | value integer 5 | lang string 6 | === 7 | 8 | #更新信息 9 | SET_UPDATE_REQ= 10 | key string 11 | value string 12 | lang string 13 | === 14 | -------------------------------------------------------------------------------- /src/stats/README.md: -------------------------------------------------------------------------------- 1 | ### 统计服务器 2 | UDP 通信 3 | -------------------------------------------------------------------------------- /src/stats/agent.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "time" 7 | ) 8 | 9 | import ( 10 | "misc/packet" 11 | "misc/timer" 12 | ) 13 | 14 | const ( 15 | DEFAULT_MAX_QUEUE = 1024 * 1000 16 | PRINT_INTERVAL = 300 17 | ) 18 | 19 | func init() { 20 | log.SetPrefix("[STATS] ") 21 | } 22 | 23 | //------------------------------------------------ Stats Server Agent 24 | func StatsAgent(incoming chan []byte, conn net.Conn) { 25 | queue_timer := make(chan int32, 1) 26 | queue_timer <- 1 27 | 28 | for { 29 | select { 30 | case sample := <-incoming: 31 | reader := packet.Reader(sample) 32 | HandleRequest(reader) 33 | case <-queue_timer: 34 | log.Println("============== STATS QUEUE SIZE:", len(incoming), "===================") 35 | timer.Add(1, time.Now().Unix()+PRINT_INTERVAL, queue_timer) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/stats/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "os" 7 | "strconv" 8 | ) 9 | 10 | import ( 11 | "cfg" 12 | ) 13 | 14 | const ( 15 | DEFAULT_SERVICE = ":8891" 16 | ) 17 | 18 | //----------------------------------------------- Stats Server start 19 | func main() { 20 | defer func() { 21 | if x := recover(); x != nil { 22 | log.Println("caught panic in main()", x) 23 | } 24 | }() 25 | 26 | config := cfg.Get() 27 | // start logger 28 | if config["stats_log"] != "" { 29 | cfg.StartLogger(config["stats_log"]) 30 | } 31 | 32 | log.Println("Starting Stats Server") 33 | go SignalProc() 34 | go SysRoutine() 35 | 36 | // Listen 37 | service := DEFAULT_SERVICE 38 | if config["stats_service"] != "" { 39 | service = config["stats_service"] 40 | } 41 | 42 | log.Println("Stats Service:", service) 43 | udpAddr, err := net.ResolveUDPAddr("udp", service) 44 | checkError(err) 45 | 46 | udpconn, err := net.ListenUDP("udp", udpAddr) 47 | checkError(err) 48 | 49 | log.Println("Stats Server OK.") 50 | handleClient(udpconn) 51 | } 52 | 53 | //----------------------------------------------- handle cooldown request 54 | func handleClient(conn *net.UDPConn) { 55 | // init receive buffer 56 | config := cfg.Get() 57 | maxchan, e := strconv.Atoi(config["stats_max_queue_size"]) 58 | if e != nil { 59 | maxchan = DEFAULT_MAX_QUEUE 60 | log.Println("cannot parse stats_max_queue_size from config", e) 61 | } 62 | 63 | ch := make(chan []byte, maxchan) 64 | defer close(ch) 65 | 66 | go StatsAgent(ch, conn) 67 | 68 | // loop receiving 69 | for { 70 | // udp receive buffer, max 512 packet 71 | data := make([]byte, 512) 72 | n, addr, err := conn.ReadFromUDP(data) 73 | if err != nil { 74 | log.Println("read udp failed", n, addr, err) 75 | continue 76 | } 77 | 78 | ch <- data[:n] 79 | } 80 | } 81 | 82 | func checkError(err error) { 83 | if err != nil { 84 | log.Printf("Fatal error: %v", err) 85 | os.Exit(-1) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/stats/protos/api.go: -------------------------------------------------------------------------------- 1 | package protos 2 | 3 | import "misc/packet" 4 | 5 | var Code = map[string]int16{ 6 | "set_adds_req": 100, // 累计信息 7 | "set_update_req": 200, // 更新信息 8 | } 9 | 10 | var RCode = map[int16]string{ 11 | 100: "set_adds_req", // 累计信息 12 | 200: "set_update_req", // 更新信息 13 | } 14 | 15 | var ProtoHandler map[int16]func(*packet.Packet) []byte 16 | 17 | func init() { 18 | ProtoHandler = map[int16]func(*packet.Packet) []byte{ 19 | 100: P_set_adds_req, 20 | 200: P_set_update_req, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/stats/protos/handle.go: -------------------------------------------------------------------------------- 1 | package protos 2 | 3 | import ( 4 | "log" 5 | "runtime" 6 | ) 7 | 8 | import ( 9 | "db/stats_tbl" 10 | "misc/packet" 11 | ) 12 | 13 | func checkErr(err error) { 14 | if err != nil { 15 | funcName, file, line, ok := runtime.Caller(1) 16 | if ok { 17 | log.Printf("ERR:%v,[func:%v,file:%v,line:%v]\n", err, runtime.FuncForPC(funcName).Name(), file, line) 18 | } 19 | 20 | panic("error occured in Stats Protocol Module") 21 | } 22 | } 23 | 24 | func P_set_adds_req(reader *packet.Packet) []byte { 25 | tbl, err := PKT_SET_ADDS_REQ(reader) 26 | //log.Println(tbl) 27 | checkErr(err) 28 | stats_tbl.SetAdds(tbl.F_key, tbl.F_value, tbl.F_lang) 29 | return nil 30 | } 31 | 32 | func P_set_update_req(reader *packet.Packet) []byte { 33 | tbl, err := PKT_SET_UPDATE_REQ(reader) 34 | //log.Println(tbl) 35 | checkErr(err) 36 | stats_tbl.SetUpdate(tbl.F_key, tbl.F_value, tbl.F_lang) 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /src/stats/protos/proto.go: -------------------------------------------------------------------------------- 1 | package protos 2 | 3 | import "misc/packet" 4 | 5 | type SET_ADDS_REQ struct { 6 | F_key string 7 | F_value int32 8 | F_lang string 9 | } 10 | 11 | type SET_UPDATE_REQ struct { 12 | F_key string 13 | F_value string 14 | F_lang string 15 | } 16 | 17 | func PKT_SET_ADDS_REQ(reader *packet.Packet) (tbl SET_ADDS_REQ, err error) { 18 | tbl.F_key, err = reader.ReadString() 19 | checkErr(err) 20 | 21 | tbl.F_value, err = reader.ReadS32() 22 | checkErr(err) 23 | 24 | tbl.F_lang, err = reader.ReadString() 25 | checkErr(err) 26 | 27 | return 28 | } 29 | 30 | func PKT_SET_UPDATE_REQ(reader *packet.Packet) (tbl SET_UPDATE_REQ, err error) { 31 | tbl.F_key, err = reader.ReadString() 32 | checkErr(err) 33 | 34 | tbl.F_value, err = reader.ReadString() 35 | checkErr(err) 36 | 37 | tbl.F_lang, err = reader.ReadString() 38 | checkErr(err) 39 | 40 | return 41 | } 42 | -------------------------------------------------------------------------------- /src/stats/proxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | ) 6 | 7 | import ( 8 | . "helper" 9 | "misc/packet" 10 | "stats/protos" 11 | ) 12 | 13 | func HandleRequest(reader *packet.Packet) { 14 | defer PrintPanicStack() 15 | b, err := reader.ReadS16() 16 | if err != nil { 17 | log.Println("read protocol error") 18 | return 19 | } 20 | 21 | handle := protos.ProtoHandler[b] 22 | //DEBUG("=== stats protocal====", b) 23 | if handle == nil { 24 | log.Println("service not bind", b) 25 | return 26 | } 27 | handle(reader) 28 | } 29 | -------------------------------------------------------------------------------- /src/stats/signal.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | // "sync/atomic" 9 | ) 10 | 11 | import ( 12 | "cfg" 13 | ) 14 | 15 | //----------------------------------------------- handle unix signals 16 | func SignalProc() { 17 | ch := make(chan os.Signal, 1) 18 | signal.Notify(ch, syscall.SIGHUP) 19 | for { 20 | msg := <-ch 21 | switch msg { 22 | case syscall.SIGHUP: 23 | log.Println("Recevied signal:", msg) 24 | cfg.Reload() 25 | /* 26 | case syscall.SIGTREM: 27 | atomic.StoreInt32(&SIGTREM,1) 28 | wg.Wait() 29 | os.Exit(-1) 30 | */ 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/stats/sys.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "runtime" 6 | "time" 7 | ) 8 | 9 | import ( 10 | "helper" 11 | "misc/timer" 12 | ) 13 | 14 | const ( 15 | GC_INTERVAL = 300 16 | ) 17 | 18 | //---------------------------------------------------------- 系统routine 19 | func SysRoutine() { 20 | // timer 21 | gc_timer := make(chan int32, 1) 22 | timer.Add(0, time.Now().Unix()+GC_INTERVAL, gc_timer) 23 | 24 | for { 25 | select { 26 | case <-gc_timer: 27 | runtime.GC() 28 | log.Println("GC executed") 29 | log.Println("NumGoroutine", runtime.NumGoroutine()) 30 | log.Println("GC Summary:", helper.GCSummary()) 31 | timer.Add(0, time.Now().Unix()+GC_INTERVAL, gc_timer) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/types/README.md: -------------------------------------------------------------------------------- 1 | 玩家数据/会话信息定义 2 | -------------------------------------------------------------------------------- /src/types/chat.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | //---------------------------------------------------------- 单条聊天记录 4 | type Words struct { 5 | Words string 6 | Speaker string 7 | } 8 | -------------------------------------------------------------------------------- /src/types/consts.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | //------------------------------------------------ 状态机定义 4 | const ( 5 | UNKNOWN = byte(iota) 6 | OFF_FREE 7 | OFF_RAID 8 | OFF_PROT 9 | ON_FREE 10 | ON_PROT 11 | ) 12 | -------------------------------------------------------------------------------- /src/types/ipcobject.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | //---------------------------------------------------------- IPCObject 定义 8 | type IPCObject struct { 9 | SrcID int32 // 发送方用户ID 10 | DestID int32 // 接收放用户ID 11 | AuxIDs []int32 `bson:",omitempty"` // 目标用户ID集合(用于组播) 12 | Service int16 // 服务号 13 | Object []byte // 投递的 JSON STRING 14 | Time int64 // 发送时间 15 | MarkDelete bool // 数据库标记删除 16 | } 17 | 18 | //---------------------------------------------------------- 将整个IPCObject转为JSON 19 | func (obj *IPCObject) Json() []byte { 20 | val, _ := json.Marshal(obj) 21 | return val 22 | } 23 | -------------------------------------------------------------------------------- /src/types/samples/samples.go: -------------------------------------------------------------------------------- 1 | package samples 2 | 3 | import ( 4 | "log" 5 | "strconv" 6 | ) 7 | 8 | import ( 9 | "cfg" 10 | "misc/alg/gaussian" 11 | ) 12 | 13 | const ( 14 | COLLECTION = "SAMPLES" 15 | DEFAULT_SAMPLES = 128 16 | ) 17 | 18 | type Samples struct { 19 | UserId int32 20 | Version uint32 21 | G *gaussian.Dist 22 | } 23 | 24 | func (s *Samples) Init() { 25 | config := cfg.Get() 26 | samples, err := strconv.Atoi(config["samples"]) 27 | if err != nil { 28 | log.Println("cannot parse samples from config", err) 29 | samples = DEFAULT_SAMPLES 30 | } 31 | 32 | s.G = gaussian.NewDist(samples) 33 | } 34 | -------------------------------------------------------------------------------- /src/types/session.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "misc/crypto/pike" 6 | "net" 7 | "time" 8 | ) 9 | 10 | import ( 11 | // TODO: import data structure 12 | ) 13 | 14 | const ( 15 | SESS_LOGGED_IN = 0x1 16 | SESS_KICKED_OUT = 0x2 17 | SESS_REGISTERED = 0x4 18 | SESS_KEYEXCG = 0x8 19 | SESS_ENCRYPT = 0x10 20 | ) 21 | 22 | type Session struct { 23 | IP net.IP 24 | MQ chan IPCObject // Player's Internal Message Queue 25 | Encoder *pike.Pike 26 | Decoder *pike.Pike 27 | 28 | // TODO: all user data structure 29 | User *User 30 | 31 | // session related 32 | LoggedIn bool // flag for weather the user is logged in 33 | KickOut bool // flag for player is kicked out 34 | 35 | // session flag 36 | Flag int32 37 | 38 | // time related variables 39 | ConnectTime time.Time // tcp connection establish time, in millsecond(ms) 40 | PacketTime time.Time // last packet time 41 | LastPacketTime time.Time // last packet arrive time, in seconds(s) 42 | _dirtycount int32 // dirty ops count 43 | 44 | // packet rate control 45 | PacketCount int64 // count packets 46 | } 47 | 48 | func (sess *Session) MarkDirty() { 49 | sess._dirtycount++ 50 | } 51 | 52 | func (sess *Session) DirtyCount() int32 { 53 | return sess._dirtycount 54 | } 55 | 56 | func (sess *Session) MarkClean() { 57 | sess._dirtycount = 0 58 | } 59 | 60 | //------------------------------------------------ integer to string 61 | func S(a interface{}) string { 62 | return fmt.Sprint(a) 63 | } 64 | -------------------------------------------------------------------------------- /src/types/user.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const ( 4 | SYS_USR = 0 5 | ) 6 | 7 | type User struct { 8 | Id int32 // 用户id 9 | Domain string // 玩家所在分服 10 | Name string // 用户名 11 | Flag int32 // 状态标记 12 | Pass []byte // 密码(MD5 Hash) 13 | Score int32 // 分数 14 | ProtectTimeout int64 // 护盾截止时间 15 | Mac string // 玩家MAC地址 16 | CountryCode string // 国家代码 17 | Language string // 界面语言 18 | DeviceType string // 设备类型 19 | LastSaveTime int64 // 服务器最后一次刷入数据库的时间 20 | CreatedAt int64 // 注册时间 21 | } 22 | -------------------------------------------------------------------------------- /start-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | killall -9 agent 3 | killall -9 stats 4 | killall -9 hub 5 | 6 | killall -0 agent 7 | while [ $? -ne 1 ]; do 8 | sleep 1 9 | killall -0 agent 10 | done 11 | 12 | killall -0 stats 13 | while [ $? -ne 1 ]; do 14 | sleep 1 15 | killall -0 stats 16 | done 17 | 18 | killall -0 hub 19 | while [ $? -ne 1 ]; do 20 | sleep 1 21 | killall -0 hub 22 | done 23 | 24 | $GOPATH/bin/hub & 25 | $GOPATH/bin/stats & 26 | sleep 1 27 | $GOPATH/bin/agent & 28 | --------------------------------------------------------------------------------