├── version.go ├── network ├── agent.go ├── conn.go ├── processor.go ├── tcp_conn.go ├── ws_conn.go ├── tcp_client.go ├── tcp_server.go ├── ws_client.go ├── tcp_msg.go ├── ws_server.go ├── json │ └── json.go └── protobuf │ └── protobuf.go ├── gate ├── agent.go └── gate.go ├── util ├── semaphore.go ├── example_test.go ├── rand.go ├── map.go └── deepcopy.go ├── conf └── conf.go ├── recordfile ├── test.txt ├── example_test.go └── recordfile.go ├── README.md ├── log ├── example_test.go └── log.go ├── db └── mongodb │ ├── example_test.go │ └── mongodb.go ├── leaf.go ├── go ├── example_test.go └── go.go ├── timer ├── example_test.go ├── timer.go └── cronexpr.go ├── module ├── module.go └── skeleton.go ├── cluster └── cluster.go ├── console ├── console.go └── command.go ├── chanrpc ├── example_test.go └── chanrpc.go ├── LICENSE ├── TUTORIAL_ZH.md └── TUTORIAL_EN.md /version.go: -------------------------------------------------------------------------------- 1 | package leaf 2 | 3 | const version = "1.1.3" 4 | -------------------------------------------------------------------------------- /network/agent.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | type Agent interface { 4 | Run() 5 | OnClose() 6 | } 7 | -------------------------------------------------------------------------------- /network/conn.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | type Conn interface { 8 | ReadMsg() ([]byte, error) 9 | WriteMsg(args ...[]byte) error 10 | LocalAddr() net.Addr 11 | RemoteAddr() net.Addr 12 | Close() 13 | Destroy() 14 | } 15 | -------------------------------------------------------------------------------- /gate/agent.go: -------------------------------------------------------------------------------- 1 | package gate 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | type Agent interface { 8 | WriteMsg(msg interface{}) 9 | LocalAddr() net.Addr 10 | RemoteAddr() net.Addr 11 | Close() 12 | Destroy() 13 | UserData() interface{} 14 | SetUserData(data interface{}) 15 | } 16 | -------------------------------------------------------------------------------- /util/semaphore.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | type Semaphore chan struct{} 4 | 5 | func MakeSemaphore(n int) Semaphore { 6 | return make(Semaphore, n) 7 | } 8 | 9 | func (s Semaphore) Acquire() { 10 | s <- struct{}{} 11 | } 12 | 13 | func (s Semaphore) Release() { 14 | <-s 15 | } 16 | -------------------------------------------------------------------------------- /network/processor.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | type Processor interface { 4 | // must goroutine safe 5 | Route(msg interface{}, userData interface{}) error 6 | // must goroutine safe 7 | Unmarshal(data []byte) (interface{}, error) 8 | // must goroutine safe 9 | Marshal(msg interface{}) ([][]byte, error) 10 | } 11 | -------------------------------------------------------------------------------- /conf/conf.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | var ( 4 | LenStackBuf = 4096 5 | 6 | // log 7 | LogLevel string 8 | LogPath string 9 | LogFlag int 10 | 11 | // console 12 | ConsolePort int 13 | ConsolePrompt string = "Leaf# " 14 | ProfilePath string 15 | 16 | // cluster 17 | ListenAddr string 18 | ConnAddrs []string 19 | PendingWriteNum int 20 | ) 21 | -------------------------------------------------------------------------------- /recordfile/test.txt: -------------------------------------------------------------------------------- 1 | 数字索引 字符串索引 数字类型 字符串类型 数组类型 嵌套数组 变长数组 结构体类型 map类型 2 | 1 one 0 knife "[1, 2]" "[[0,1], [1,2], [2,3]]" "[1, 2, 3]" "{""name"": ""name5566"", ""num"": 1}" "{""key1"": 1, ""key2"": 2}" 3 | 2 two 0 cat "[3, 4]" "[[1,2], [2,3], [3,4]]" "[4, 5]" "{""name"": ""name5566"", ""num"": 2}" "{""key3"": 3, ""key4"": 4}" 4 | 3 three 0 book "[5, 6]" "[[2,3], [3,4], [4,5]]" [6] "{""name"": ""name5566"", ""num"": 3}" "{""key5"": 5, ""key6"": 6}" 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Leaf 2 | ==== 3 | A pragmatic game server framework in Go (golang). 4 | 5 | Features 6 | --------- 7 | 8 | * Extremely easy to use 9 | * Reliable 10 | * Multicore support 11 | * Modularity 12 | 13 | Community 14 | --------- 15 | 16 | 17 | 18 | Documentation 19 | --------- 20 | 21 | * [中文文档](https://github.com/name5566/leaf/blob/master/TUTORIAL_ZH.md) 22 | * [English](https://github.com/name5566/leaf/blob/master/TUTORIAL_EN.md) 23 | 24 | Licensing 25 | --------- 26 | 27 | Leaf is licensed under the Apache License, Version 2.0. See [LICENSE](https://github.com/name5566/leaf/blob/master/LICENSE) for the full license text. 28 | -------------------------------------------------------------------------------- /log/example_test.go: -------------------------------------------------------------------------------- 1 | package log_test 2 | 3 | import ( 4 | "github.com/name5566/leaf/log" 5 | l "log" 6 | ) 7 | 8 | func Example() { 9 | name := "Leaf" 10 | 11 | log.Debug("My name is %v", name) 12 | log.Release("My name is %v", name) 13 | log.Error("My name is %v", name) 14 | // log.Fatal("My name is %v", name) 15 | 16 | logger, err := log.New("release", "", l.LstdFlags) 17 | if err != nil { 18 | return 19 | } 20 | defer logger.Close() 21 | 22 | logger.Debug("will not print") 23 | logger.Release("My name is %v", name) 24 | 25 | log.Export(logger) 26 | 27 | log.Debug("will not print") 28 | log.Release("My name is %v", name) 29 | } 30 | -------------------------------------------------------------------------------- /db/mongodb/example_test.go: -------------------------------------------------------------------------------- 1 | package mongodb_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/name5566/leaf/db/mongodb" 6 | "gopkg.in/mgo.v2" 7 | ) 8 | 9 | func Example() { 10 | c, err := mongodb.Dial("localhost", 10) 11 | if err != nil { 12 | fmt.Println(err) 13 | return 14 | } 15 | defer c.Close() 16 | 17 | // session 18 | s := c.Ref() 19 | defer c.UnRef(s) 20 | err = s.DB("test").C("counters").RemoveId("test") 21 | if err != nil && err != mgo.ErrNotFound { 22 | fmt.Println(err) 23 | return 24 | } 25 | 26 | // auto increment 27 | err = c.EnsureCounter("test", "counters", "test") 28 | if err != nil { 29 | fmt.Println(err) 30 | return 31 | } 32 | for i := 0; i < 3; i++ { 33 | id, err := c.NextSeq("test", "counters", "test") 34 | if err != nil { 35 | fmt.Println(err) 36 | return 37 | } 38 | fmt.Println(id) 39 | } 40 | 41 | // index 42 | c.EnsureUniqueIndex("test", "counters", []string{"key1"}) 43 | 44 | // Output: 45 | // 1 46 | // 2 47 | // 3 48 | } 49 | -------------------------------------------------------------------------------- /leaf.go: -------------------------------------------------------------------------------- 1 | package leaf 2 | 3 | import ( 4 | "github.com/name5566/leaf/cluster" 5 | "github.com/name5566/leaf/conf" 6 | "github.com/name5566/leaf/console" 7 | "github.com/name5566/leaf/log" 8 | "github.com/name5566/leaf/module" 9 | "os" 10 | "os/signal" 11 | ) 12 | 13 | func Run(mods ...module.Module) { 14 | // logger 15 | if conf.LogLevel != "" { 16 | logger, err := log.New(conf.LogLevel, conf.LogPath, conf.LogFlag) 17 | if err != nil { 18 | panic(err) 19 | } 20 | log.Export(logger) 21 | defer logger.Close() 22 | } 23 | 24 | log.Release("Leaf %v starting up", version) 25 | 26 | // module 27 | for i := 0; i < len(mods); i++ { 28 | module.Register(mods[i]) 29 | } 30 | module.Init() 31 | 32 | // cluster 33 | cluster.Init() 34 | 35 | // console 36 | console.Init() 37 | 38 | // close 39 | c := make(chan os.Signal, 1) 40 | signal.Notify(c, os.Interrupt, os.Kill) 41 | sig := <-c 42 | log.Release("Leaf closing down (signal: %v)", sig) 43 | console.Destroy() 44 | cluster.Destroy() 45 | module.Destroy() 46 | } 47 | -------------------------------------------------------------------------------- /go/example_test.go: -------------------------------------------------------------------------------- 1 | package g_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/name5566/leaf/go" 6 | "time" 7 | ) 8 | 9 | func Example() { 10 | d := g.New(10) 11 | 12 | // go 1 13 | var res int 14 | d.Go(func() { 15 | fmt.Println("1 + 1 = ?") 16 | res = 1 + 1 17 | }, func() { 18 | fmt.Println(res) 19 | }) 20 | 21 | d.Cb(<-d.ChanCb) 22 | 23 | // go 2 24 | d.Go(func() { 25 | fmt.Print("My name is ") 26 | }, func() { 27 | fmt.Println("Leaf") 28 | }) 29 | 30 | d.Close() 31 | 32 | // Output: 33 | // 1 + 1 = ? 34 | // 2 35 | // My name is Leaf 36 | } 37 | 38 | func ExampleLinearContext() { 39 | d := g.New(10) 40 | 41 | // parallel 42 | d.Go(func() { 43 | time.Sleep(time.Second / 2) 44 | fmt.Println("1") 45 | }, nil) 46 | d.Go(func() { 47 | fmt.Println("2") 48 | }, nil) 49 | 50 | d.Cb(<-d.ChanCb) 51 | d.Cb(<-d.ChanCb) 52 | 53 | // linear 54 | c := d.NewLinearContext() 55 | c.Go(func() { 56 | time.Sleep(time.Second / 2) 57 | fmt.Println("1") 58 | }, nil) 59 | c.Go(func() { 60 | fmt.Println("2") 61 | }, nil) 62 | 63 | d.Close() 64 | 65 | // Output: 66 | // 2 67 | // 1 68 | // 1 69 | // 2 70 | } 71 | -------------------------------------------------------------------------------- /timer/example_test.go: -------------------------------------------------------------------------------- 1 | package timer_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/name5566/leaf/timer" 6 | "time" 7 | ) 8 | 9 | func ExampleTimer() { 10 | d := timer.NewDispatcher(10) 11 | 12 | // timer 1 13 | d.AfterFunc(1, func() { 14 | fmt.Println("My name is Leaf") 15 | }) 16 | 17 | // timer 2 18 | t := d.AfterFunc(1, func() { 19 | fmt.Println("will not print") 20 | }) 21 | t.Stop() 22 | 23 | // dispatch 24 | (<-d.ChanTimer).Cb() 25 | 26 | // Output: 27 | // My name is Leaf 28 | } 29 | 30 | func ExampleCronExpr() { 31 | cronExpr, err := timer.NewCronExpr("0 * * * *") 32 | if err != nil { 33 | return 34 | } 35 | 36 | fmt.Println(cronExpr.Next(time.Date( 37 | 2000, 1, 1, 38 | 20, 10, 5, 39 | 0, time.UTC, 40 | ))) 41 | 42 | // Output: 43 | // 2000-01-01 21:00:00 +0000 UTC 44 | } 45 | 46 | func ExampleCron() { 47 | d := timer.NewDispatcher(10) 48 | 49 | // cron expr 50 | cronExpr, err := timer.NewCronExpr("* * * * * *") 51 | if err != nil { 52 | return 53 | } 54 | 55 | // cron 56 | var c *timer.Cron 57 | c = d.CronFunc(cronExpr, func() { 58 | fmt.Println("My name is Leaf") 59 | c.Stop() 60 | }) 61 | 62 | // dispatch 63 | (<-d.ChanTimer).Cb() 64 | 65 | // Output: 66 | // My name is Leaf 67 | } 68 | -------------------------------------------------------------------------------- /recordfile/example_test.go: -------------------------------------------------------------------------------- 1 | package recordfile_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/name5566/leaf/recordfile" 6 | ) 7 | 8 | func Example() { 9 | type Record struct { 10 | // index 0 11 | IndexInt int "index" 12 | // index 1 13 | IndexStr string "index" 14 | _Number int32 15 | Str string 16 | Arr1 [2]int 17 | Arr2 [3][2]int 18 | Arr3 []int 19 | St struct { 20 | Name string "name" 21 | Num int "num" 22 | } 23 | M map[string]int 24 | } 25 | 26 | rf, err := recordfile.New(Record{}) 27 | if err != nil { 28 | return 29 | } 30 | 31 | err = rf.Read("test.txt") 32 | if err != nil { 33 | return 34 | } 35 | 36 | for i := 0; i < rf.NumRecord(); i++ { 37 | r := rf.Record(i).(*Record) 38 | fmt.Println(r.IndexInt) 39 | } 40 | 41 | r := rf.Index(2).(*Record) 42 | fmt.Println(r.Str) 43 | 44 | r = rf.Indexes(0)[2].(*Record) 45 | fmt.Println(r.Str) 46 | 47 | r = rf.Indexes(1)["three"].(*Record) 48 | fmt.Println(r.Str) 49 | fmt.Println(r.Arr1[1]) 50 | fmt.Println(r.Arr2[2][0]) 51 | fmt.Println(r.Arr3[0]) 52 | fmt.Println(r.St.Name) 53 | fmt.Println(r.M["key6"]) 54 | 55 | // Output: 56 | // 1 57 | // 2 58 | // 3 59 | // cat 60 | // cat 61 | // book 62 | // 6 63 | // 4 64 | // 6 65 | // name5566 66 | // 6 67 | } 68 | -------------------------------------------------------------------------------- /module/module.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "github.com/name5566/leaf/conf" 5 | "github.com/name5566/leaf/log" 6 | "runtime" 7 | "sync" 8 | ) 9 | 10 | type Module interface { 11 | OnInit() 12 | OnDestroy() 13 | Run(closeSig chan bool) 14 | } 15 | 16 | type module struct { 17 | mi Module 18 | closeSig chan bool 19 | wg sync.WaitGroup 20 | } 21 | 22 | var mods []*module 23 | 24 | func Register(mi Module) { 25 | m := new(module) 26 | m.mi = mi 27 | m.closeSig = make(chan bool, 1) 28 | 29 | mods = append(mods, m) 30 | } 31 | 32 | func Init() { 33 | for i := 0; i < len(mods); i++ { 34 | mods[i].mi.OnInit() 35 | } 36 | 37 | for i := 0; i < len(mods); i++ { 38 | m := mods[i] 39 | m.wg.Add(1) 40 | go run(m) 41 | } 42 | } 43 | 44 | func Destroy() { 45 | for i := len(mods) - 1; i >= 0; i-- { 46 | m := mods[i] 47 | m.closeSig <- true 48 | m.wg.Wait() 49 | destroy(m) 50 | } 51 | } 52 | 53 | func run(m *module) { 54 | m.mi.Run(m.closeSig) 55 | m.wg.Done() 56 | } 57 | 58 | func destroy(m *module) { 59 | defer func() { 60 | if r := recover(); r != nil { 61 | if conf.LenStackBuf > 0 { 62 | buf := make([]byte, conf.LenStackBuf) 63 | l := runtime.Stack(buf, false) 64 | log.Error("%v: %s", r, buf[:l]) 65 | } else { 66 | log.Error("%v", r) 67 | } 68 | } 69 | }() 70 | 71 | m.mi.OnDestroy() 72 | } 73 | -------------------------------------------------------------------------------- /cluster/cluster.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "github.com/name5566/leaf/conf" 5 | "github.com/name5566/leaf/network" 6 | "math" 7 | "time" 8 | ) 9 | 10 | var ( 11 | server *network.TCPServer 12 | clients []*network.TCPClient 13 | ) 14 | 15 | func Init() { 16 | if conf.ListenAddr != "" { 17 | server = new(network.TCPServer) 18 | server.Addr = conf.ListenAddr 19 | server.MaxConnNum = int(math.MaxInt32) 20 | server.PendingWriteNum = conf.PendingWriteNum 21 | server.LenMsgLen = 4 22 | server.MaxMsgLen = math.MaxUint32 23 | server.NewAgent = newAgent 24 | 25 | server.Start() 26 | } 27 | 28 | for _, addr := range conf.ConnAddrs { 29 | client := new(network.TCPClient) 30 | client.Addr = addr 31 | client.ConnNum = 1 32 | client.ConnectInterval = 3 * time.Second 33 | client.PendingWriteNum = conf.PendingWriteNum 34 | client.LenMsgLen = 4 35 | client.MaxMsgLen = math.MaxUint32 36 | client.NewAgent = newAgent 37 | 38 | client.Start() 39 | clients = append(clients, client) 40 | } 41 | } 42 | 43 | func Destroy() { 44 | if server != nil { 45 | server.Close() 46 | } 47 | 48 | for _, client := range clients { 49 | client.Close() 50 | } 51 | } 52 | 53 | type Agent struct { 54 | conn *network.TCPConn 55 | } 56 | 57 | func newAgent(conn *network.TCPConn) network.Agent { 58 | a := new(Agent) 59 | a.conn = conn 60 | return a 61 | } 62 | 63 | func (a *Agent) Run() {} 64 | 65 | func (a *Agent) OnClose() {} 66 | -------------------------------------------------------------------------------- /util/example_test.go: -------------------------------------------------------------------------------- 1 | package util_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/name5566/leaf/util" 6 | ) 7 | 8 | func ExampleMap() { 9 | m := new(util.Map) 10 | 11 | fmt.Println(m.Get("key")) 12 | m.Set("key", "value") 13 | fmt.Println(m.Get("key")) 14 | m.Del("key") 15 | fmt.Println(m.Get("key")) 16 | 17 | m.Set(1, "1") 18 | m.Set(2, 2) 19 | m.Set("3", 3) 20 | 21 | fmt.Println(m.Len()) 22 | 23 | // Output: 24 | // 25 | // value 26 | // 27 | // 3 28 | } 29 | 30 | func ExampleRandGroup() { 31 | i := util.RandGroup(0, 0, 50, 50) 32 | switch i { 33 | case 2, 3: 34 | fmt.Println("ok") 35 | } 36 | 37 | // Output: 38 | // ok 39 | } 40 | 41 | func ExampleRandInterval() { 42 | v := util.RandInterval(-1, 1) 43 | switch v { 44 | case -1, 0, 1: 45 | fmt.Println("ok") 46 | } 47 | 48 | // Output: 49 | // ok 50 | } 51 | 52 | func ExampleRandIntervalN() { 53 | r := util.RandIntervalN(-1, 0, 2) 54 | if r[0] == -1 && r[1] == 0 || 55 | r[0] == 0 && r[1] == -1 { 56 | fmt.Println("ok") 57 | } 58 | 59 | // Output: 60 | // ok 61 | } 62 | 63 | func ExampleDeepCopy() { 64 | src := []int{1, 2, 3} 65 | 66 | var dst []int 67 | util.DeepCopy(&dst, &src) 68 | 69 | for _, v := range dst { 70 | fmt.Println(v) 71 | } 72 | 73 | // Output: 74 | // 1 75 | // 2 76 | // 3 77 | } 78 | 79 | func ExampleDeepClone() { 80 | src := []int{1, 2, 3} 81 | 82 | dst := util.DeepClone(src).([]int) 83 | 84 | for _, v := range dst { 85 | fmt.Println(v) 86 | } 87 | 88 | // Output: 89 | // 1 90 | // 2 91 | // 3 92 | } 93 | -------------------------------------------------------------------------------- /util/rand.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | func init() { 9 | rand.Seed(time.Now().UnixNano()) 10 | } 11 | 12 | func RandGroup(p ...uint32) int { 13 | if p == nil { 14 | panic("args not found") 15 | } 16 | 17 | r := make([]uint32, len(p)) 18 | for i := 0; i < len(p); i++ { 19 | if i == 0 { 20 | r[0] = p[0] 21 | } else { 22 | r[i] = r[i-1] + p[i] 23 | } 24 | } 25 | 26 | rl := r[len(r)-1] 27 | if rl == 0 { 28 | return 0 29 | } 30 | 31 | rn := uint32(rand.Int63n(int64(rl))) 32 | for i := 0; i < len(r); i++ { 33 | if rn < r[i] { 34 | return i 35 | } 36 | } 37 | 38 | panic("bug") 39 | } 40 | 41 | func RandInterval(b1, b2 int32) int32 { 42 | if b1 == b2 { 43 | return b1 44 | } 45 | 46 | min, max := int64(b1), int64(b2) 47 | if min > max { 48 | min, max = max, min 49 | } 50 | return int32(rand.Int63n(max-min+1) + min) 51 | } 52 | 53 | func RandIntervalN(b1, b2 int32, n uint32) []int32 { 54 | if b1 == b2 { 55 | return []int32{b1} 56 | } 57 | 58 | min, max := int64(b1), int64(b2) 59 | if min > max { 60 | min, max = max, min 61 | } 62 | l := max - min + 1 63 | if int64(n) > l { 64 | n = uint32(l) 65 | } 66 | 67 | r := make([]int32, n) 68 | m := make(map[int32]int32) 69 | for i := uint32(0); i < n; i++ { 70 | v := int32(rand.Int63n(l) + min) 71 | 72 | if mv, ok := m[v]; ok { 73 | r[i] = mv 74 | } else { 75 | r[i] = v 76 | } 77 | 78 | lv := int32(l - 1 + min) 79 | if v != lv { 80 | if mv, ok := m[lv]; ok { 81 | m[v] = mv 82 | } else { 83 | m[v] = lv 84 | } 85 | } 86 | 87 | l-- 88 | } 89 | 90 | return r 91 | } 92 | -------------------------------------------------------------------------------- /console/console.go: -------------------------------------------------------------------------------- 1 | package console 2 | 3 | import ( 4 | "bufio" 5 | "github.com/name5566/leaf/conf" 6 | "github.com/name5566/leaf/network" 7 | "math" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | var server *network.TCPServer 13 | 14 | func Init() { 15 | if conf.ConsolePort == 0 { 16 | return 17 | } 18 | 19 | server = new(network.TCPServer) 20 | server.Addr = "localhost:" + strconv.Itoa(conf.ConsolePort) 21 | server.MaxConnNum = int(math.MaxInt32) 22 | server.PendingWriteNum = 100 23 | server.NewAgent = newAgent 24 | 25 | server.Start() 26 | } 27 | 28 | func Destroy() { 29 | if server != nil { 30 | server.Close() 31 | } 32 | } 33 | 34 | type Agent struct { 35 | conn *network.TCPConn 36 | reader *bufio.Reader 37 | } 38 | 39 | func newAgent(conn *network.TCPConn) network.Agent { 40 | a := new(Agent) 41 | a.conn = conn 42 | a.reader = bufio.NewReader(conn) 43 | return a 44 | } 45 | 46 | func (a *Agent) Run() { 47 | for { 48 | if conf.ConsolePrompt != "" { 49 | a.conn.Write([]byte(conf.ConsolePrompt)) 50 | } 51 | 52 | line, err := a.reader.ReadString('\n') 53 | if err != nil { 54 | break 55 | } 56 | line = strings.TrimSuffix(line[:len(line)-1], "\r") 57 | 58 | args := strings.Fields(line) 59 | if len(args) == 0 { 60 | continue 61 | } 62 | if args[0] == "quit" { 63 | break 64 | } 65 | var c Command 66 | for _, _c := range commands { 67 | if _c.name() == args[0] { 68 | c = _c 69 | break 70 | } 71 | } 72 | if c == nil { 73 | a.conn.Write([]byte("command not found, try `help` for help\r\n")) 74 | continue 75 | } 76 | output := c.run(args[1:]) 77 | if output != "" { 78 | a.conn.Write([]byte(output + "\r\n")) 79 | } 80 | } 81 | } 82 | 83 | func (a *Agent) OnClose() {} 84 | -------------------------------------------------------------------------------- /timer/timer.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | import ( 4 | "github.com/name5566/leaf/conf" 5 | "github.com/name5566/leaf/log" 6 | "runtime" 7 | "time" 8 | ) 9 | 10 | // one dispatcher per goroutine (goroutine not safe) 11 | type Dispatcher struct { 12 | ChanTimer chan *Timer 13 | } 14 | 15 | func NewDispatcher(l int) *Dispatcher { 16 | disp := new(Dispatcher) 17 | disp.ChanTimer = make(chan *Timer, l) 18 | return disp 19 | } 20 | 21 | // Timer 22 | type Timer struct { 23 | t *time.Timer 24 | cb func() 25 | } 26 | 27 | func (t *Timer) Stop() { 28 | t.t.Stop() 29 | t.cb = nil 30 | } 31 | 32 | func (t *Timer) Cb() { 33 | defer func() { 34 | t.cb = nil 35 | if r := recover(); r != nil { 36 | if conf.LenStackBuf > 0 { 37 | buf := make([]byte, conf.LenStackBuf) 38 | l := runtime.Stack(buf, false) 39 | log.Error("%v: %s", r, buf[:l]) 40 | } else { 41 | log.Error("%v", r) 42 | } 43 | } 44 | }() 45 | 46 | if t.cb != nil { 47 | t.cb() 48 | } 49 | } 50 | 51 | func (disp *Dispatcher) AfterFunc(d time.Duration, cb func()) *Timer { 52 | t := new(Timer) 53 | t.cb = cb 54 | t.t = time.AfterFunc(d, func() { 55 | disp.ChanTimer <- t 56 | }) 57 | return t 58 | } 59 | 60 | // Cron 61 | type Cron struct { 62 | t *Timer 63 | } 64 | 65 | func (c *Cron) Stop() { 66 | if c.t != nil { 67 | c.t.Stop() 68 | } 69 | } 70 | 71 | func (disp *Dispatcher) CronFunc(cronExpr *CronExpr, _cb func()) *Cron { 72 | c := new(Cron) 73 | 74 | now := time.Now() 75 | nextTime := cronExpr.Next(now) 76 | if nextTime.IsZero() { 77 | return c 78 | } 79 | 80 | // callback 81 | var cb func() 82 | cb = func() { 83 | defer _cb() 84 | 85 | now := time.Now() 86 | nextTime := cronExpr.Next(now) 87 | if nextTime.IsZero() { 88 | return 89 | } 90 | c.t = disp.AfterFunc(nextTime.Sub(now), cb) 91 | } 92 | 93 | c.t = disp.AfterFunc(nextTime.Sub(now), cb) 94 | return c 95 | } 96 | -------------------------------------------------------------------------------- /util/map.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type Map struct { 8 | sync.RWMutex 9 | m map[interface{}]interface{} 10 | } 11 | 12 | func (m *Map) init() { 13 | if m.m == nil { 14 | m.m = make(map[interface{}]interface{}) 15 | } 16 | } 17 | 18 | func (m *Map) UnsafeGet(key interface{}) interface{} { 19 | if m.m == nil { 20 | return nil 21 | } else { 22 | return m.m[key] 23 | } 24 | } 25 | 26 | func (m *Map) Get(key interface{}) interface{} { 27 | m.RLock() 28 | defer m.RUnlock() 29 | return m.UnsafeGet(key) 30 | } 31 | 32 | func (m *Map) UnsafeSet(key interface{}, value interface{}) { 33 | m.init() 34 | m.m[key] = value 35 | } 36 | 37 | func (m *Map) Set(key interface{}, value interface{}) { 38 | m.Lock() 39 | defer m.Unlock() 40 | m.UnsafeSet(key, value) 41 | } 42 | 43 | func (m *Map) TestAndSet(key interface{}, value interface{}) interface{} { 44 | m.Lock() 45 | defer m.Unlock() 46 | 47 | m.init() 48 | 49 | if v, ok := m.m[key]; ok { 50 | return v 51 | } else { 52 | m.m[key] = value 53 | return nil 54 | } 55 | } 56 | 57 | func (m *Map) UnsafeDel(key interface{}) { 58 | m.init() 59 | delete(m.m, key) 60 | } 61 | 62 | func (m *Map) Del(key interface{}) { 63 | m.Lock() 64 | defer m.Unlock() 65 | m.UnsafeDel(key) 66 | } 67 | 68 | func (m *Map) UnsafeLen() int { 69 | if m.m == nil { 70 | return 0 71 | } else { 72 | return len(m.m) 73 | } 74 | } 75 | 76 | func (m *Map) Len() int { 77 | m.RLock() 78 | defer m.RUnlock() 79 | return m.UnsafeLen() 80 | } 81 | 82 | func (m *Map) UnsafeRange(f func(interface{}, interface{})) { 83 | if m.m == nil { 84 | return 85 | } 86 | for k, v := range m.m { 87 | f(k, v) 88 | } 89 | } 90 | 91 | func (m *Map) RLockRange(f func(interface{}, interface{})) { 92 | m.RLock() 93 | defer m.RUnlock() 94 | m.UnsafeRange(f) 95 | } 96 | 97 | func (m *Map) LockRange(f func(interface{}, interface{})) { 98 | m.Lock() 99 | defer m.Unlock() 100 | m.UnsafeRange(f) 101 | } 102 | -------------------------------------------------------------------------------- /util/deepcopy.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | // reference: https://github.com/mohae/deepcopy 4 | import ( 5 | "reflect" 6 | ) 7 | 8 | func deepCopy(dst, src reflect.Value) { 9 | switch src.Kind() { 10 | case reflect.Interface: 11 | value := src.Elem() 12 | if !value.IsValid() { 13 | return 14 | } 15 | newValue := reflect.New(value.Type()).Elem() 16 | deepCopy(newValue, value) 17 | dst.Set(newValue) 18 | case reflect.Ptr: 19 | value := src.Elem() 20 | if !value.IsValid() { 21 | return 22 | } 23 | dst.Set(reflect.New(value.Type())) 24 | deepCopy(dst.Elem(), value) 25 | case reflect.Map: 26 | dst.Set(reflect.MakeMap(src.Type())) 27 | keys := src.MapKeys() 28 | for _, key := range keys { 29 | value := src.MapIndex(key) 30 | newValue := reflect.New(value.Type()).Elem() 31 | deepCopy(newValue, value) 32 | dst.SetMapIndex(key, newValue) 33 | } 34 | case reflect.Slice: 35 | dst.Set(reflect.MakeSlice(src.Type(), src.Len(), src.Cap())) 36 | for i := 0; i < src.Len(); i++ { 37 | deepCopy(dst.Index(i), src.Index(i)) 38 | } 39 | case reflect.Struct: 40 | typeSrc := src.Type() 41 | for i := 0; i < src.NumField(); i++ { 42 | value := src.Field(i) 43 | tag := typeSrc.Field(i).Tag 44 | if value.CanSet() && tag.Get("deepcopy") != "-" { 45 | deepCopy(dst.Field(i), value) 46 | } 47 | } 48 | default: 49 | dst.Set(src) 50 | } 51 | } 52 | 53 | func DeepCopy(dst, src interface{}) { 54 | typeDst := reflect.TypeOf(dst) 55 | typeSrc := reflect.TypeOf(src) 56 | if typeDst != typeSrc { 57 | panic("DeepCopy: " + typeDst.String() + " != " + typeSrc.String()) 58 | } 59 | if typeSrc.Kind() != reflect.Ptr { 60 | panic("DeepCopy: pass arguments by address") 61 | } 62 | 63 | valueDst := reflect.ValueOf(dst).Elem() 64 | valueSrc := reflect.ValueOf(src).Elem() 65 | if !valueDst.IsValid() || !valueSrc.IsValid() { 66 | panic("DeepCopy: invalid arguments") 67 | } 68 | 69 | deepCopy(valueDst, valueSrc) 70 | } 71 | 72 | func DeepClone(v interface{}) interface{} { 73 | dst := reflect.New(reflect.TypeOf(v)).Elem() 74 | deepCopy(dst, reflect.ValueOf(v)) 75 | return dst.Interface() 76 | } 77 | -------------------------------------------------------------------------------- /chanrpc/example_test.go: -------------------------------------------------------------------------------- 1 | package chanrpc_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/name5566/leaf/chanrpc" 6 | "sync" 7 | ) 8 | 9 | func Example() { 10 | s := chanrpc.NewServer(10) 11 | 12 | var wg sync.WaitGroup 13 | wg.Add(1) 14 | 15 | // goroutine 1 16 | go func() { 17 | s.Register("f0", func(args []interface{}) { 18 | 19 | }) 20 | 21 | s.Register("f1", func(args []interface{}) interface{} { 22 | return 1 23 | }) 24 | 25 | s.Register("fn", func(args []interface{}) []interface{} { 26 | return []interface{}{1, 2, 3} 27 | }) 28 | 29 | s.Register("add", func(args []interface{}) interface{} { 30 | n1 := args[0].(int) 31 | n2 := args[1].(int) 32 | return n1 + n2 33 | }) 34 | 35 | wg.Done() 36 | 37 | for { 38 | s.Exec(<-s.ChanCall) 39 | } 40 | }() 41 | 42 | wg.Wait() 43 | wg.Add(1) 44 | 45 | // goroutine 2 46 | go func() { 47 | c := s.Open(10) 48 | 49 | // sync 50 | err := c.Call0("f0") 51 | if err != nil { 52 | fmt.Println(err) 53 | } 54 | 55 | r1, err := c.Call1("f1") 56 | if err != nil { 57 | fmt.Println(err) 58 | } else { 59 | fmt.Println(r1) 60 | } 61 | 62 | rn, err := c.CallN("fn") 63 | if err != nil { 64 | fmt.Println(err) 65 | } else { 66 | fmt.Println(rn[0], rn[1], rn[2]) 67 | } 68 | 69 | ra, err := c.Call1("add", 1, 2) 70 | if err != nil { 71 | fmt.Println(err) 72 | } else { 73 | fmt.Println(ra) 74 | } 75 | 76 | // asyn 77 | c.AsynCall("f0", func(err error) { 78 | if err != nil { 79 | fmt.Println(err) 80 | } 81 | }) 82 | 83 | c.AsynCall("f1", func(ret interface{}, err error) { 84 | if err != nil { 85 | fmt.Println(err) 86 | } else { 87 | fmt.Println(ret) 88 | } 89 | }) 90 | 91 | c.AsynCall("fn", func(ret []interface{}, err error) { 92 | if err != nil { 93 | fmt.Println(err) 94 | } else { 95 | fmt.Println(ret[0], ret[1], ret[2]) 96 | } 97 | }) 98 | 99 | c.AsynCall("add", 1, 2, func(ret interface{}, err error) { 100 | if err != nil { 101 | fmt.Println(err) 102 | } else { 103 | fmt.Println(ret) 104 | } 105 | }) 106 | 107 | c.Cb(<-c.ChanAsynRet) 108 | c.Cb(<-c.ChanAsynRet) 109 | c.Cb(<-c.ChanAsynRet) 110 | c.Cb(<-c.ChanAsynRet) 111 | 112 | // go 113 | s.Go("f0") 114 | 115 | wg.Done() 116 | }() 117 | 118 | wg.Wait() 119 | 120 | // Output: 121 | // 1 122 | // 1 2 3 123 | // 3 124 | // 1 125 | // 1 2 3 126 | // 3 127 | } 128 | -------------------------------------------------------------------------------- /network/tcp_conn.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "github.com/name5566/leaf/log" 5 | "net" 6 | "sync" 7 | ) 8 | 9 | type ConnSet map[net.Conn]struct{} 10 | 11 | type TCPConn struct { 12 | sync.Mutex 13 | conn net.Conn 14 | writeChan chan []byte 15 | closeFlag bool 16 | msgParser *MsgParser 17 | } 18 | 19 | func newTCPConn(conn net.Conn, pendingWriteNum int, msgParser *MsgParser) *TCPConn { 20 | tcpConn := new(TCPConn) 21 | tcpConn.conn = conn 22 | tcpConn.writeChan = make(chan []byte, pendingWriteNum) 23 | tcpConn.msgParser = msgParser 24 | 25 | go func() { 26 | for b := range tcpConn.writeChan { 27 | if b == nil { 28 | break 29 | } 30 | 31 | _, err := conn.Write(b) 32 | if err != nil { 33 | break 34 | } 35 | } 36 | 37 | conn.Close() 38 | tcpConn.Lock() 39 | tcpConn.closeFlag = true 40 | tcpConn.Unlock() 41 | }() 42 | 43 | return tcpConn 44 | } 45 | 46 | func (tcpConn *TCPConn) doDestroy() { 47 | tcpConn.conn.(*net.TCPConn).SetLinger(0) 48 | tcpConn.conn.Close() 49 | 50 | if !tcpConn.closeFlag { 51 | close(tcpConn.writeChan) 52 | tcpConn.closeFlag = true 53 | } 54 | } 55 | 56 | func (tcpConn *TCPConn) Destroy() { 57 | tcpConn.Lock() 58 | defer tcpConn.Unlock() 59 | 60 | tcpConn.doDestroy() 61 | } 62 | 63 | func (tcpConn *TCPConn) Close() { 64 | tcpConn.Lock() 65 | defer tcpConn.Unlock() 66 | if tcpConn.closeFlag { 67 | return 68 | } 69 | 70 | tcpConn.doWrite(nil) 71 | tcpConn.closeFlag = true 72 | } 73 | 74 | func (tcpConn *TCPConn) doWrite(b []byte) { 75 | if len(tcpConn.writeChan) == cap(tcpConn.writeChan) { 76 | log.Debug("close conn: channel full") 77 | tcpConn.doDestroy() 78 | return 79 | } 80 | 81 | tcpConn.writeChan <- b 82 | } 83 | 84 | // b must not be modified by the others goroutines 85 | func (tcpConn *TCPConn) Write(b []byte) { 86 | tcpConn.Lock() 87 | defer tcpConn.Unlock() 88 | if tcpConn.closeFlag || b == nil { 89 | return 90 | } 91 | 92 | tcpConn.doWrite(b) 93 | } 94 | 95 | func (tcpConn *TCPConn) Read(b []byte) (int, error) { 96 | return tcpConn.conn.Read(b) 97 | } 98 | 99 | func (tcpConn *TCPConn) LocalAddr() net.Addr { 100 | return tcpConn.conn.LocalAddr() 101 | } 102 | 103 | func (tcpConn *TCPConn) RemoteAddr() net.Addr { 104 | return tcpConn.conn.RemoteAddr() 105 | } 106 | 107 | func (tcpConn *TCPConn) ReadMsg() ([]byte, error) { 108 | return tcpConn.msgParser.Read(tcpConn) 109 | } 110 | 111 | func (tcpConn *TCPConn) WriteMsg(args ...[]byte) error { 112 | return tcpConn.msgParser.Write(tcpConn, args...) 113 | } 114 | -------------------------------------------------------------------------------- /go/go.go: -------------------------------------------------------------------------------- 1 | package g 2 | 3 | import ( 4 | "container/list" 5 | "github.com/name5566/leaf/conf" 6 | "github.com/name5566/leaf/log" 7 | "runtime" 8 | "sync" 9 | ) 10 | 11 | // one Go per goroutine (goroutine not safe) 12 | type Go struct { 13 | ChanCb chan func() 14 | pendingGo int 15 | } 16 | 17 | type LinearGo struct { 18 | f func() 19 | cb func() 20 | } 21 | 22 | type LinearContext struct { 23 | g *Go 24 | linearGo *list.List 25 | mutexLinearGo sync.Mutex 26 | mutexExecution sync.Mutex 27 | } 28 | 29 | func New(l int) *Go { 30 | g := new(Go) 31 | g.ChanCb = make(chan func(), l) 32 | return g 33 | } 34 | 35 | func (g *Go) Go(f func(), cb func()) { 36 | g.pendingGo++ 37 | 38 | go func() { 39 | defer func() { 40 | g.ChanCb <- cb 41 | if r := recover(); r != nil { 42 | if conf.LenStackBuf > 0 { 43 | buf := make([]byte, conf.LenStackBuf) 44 | l := runtime.Stack(buf, false) 45 | log.Error("%v: %s", r, buf[:l]) 46 | } else { 47 | log.Error("%v", r) 48 | } 49 | } 50 | }() 51 | 52 | f() 53 | }() 54 | } 55 | 56 | func (g *Go) Cb(cb func()) { 57 | defer func() { 58 | g.pendingGo-- 59 | if r := recover(); r != nil { 60 | if conf.LenStackBuf > 0 { 61 | buf := make([]byte, conf.LenStackBuf) 62 | l := runtime.Stack(buf, false) 63 | log.Error("%v: %s", r, buf[:l]) 64 | } else { 65 | log.Error("%v", r) 66 | } 67 | } 68 | }() 69 | 70 | if cb != nil { 71 | cb() 72 | } 73 | } 74 | 75 | func (g *Go) Close() { 76 | for g.pendingGo > 0 { 77 | g.Cb(<-g.ChanCb) 78 | } 79 | } 80 | 81 | func (g *Go) Idle() bool { 82 | return g.pendingGo == 0 83 | } 84 | 85 | func (g *Go) NewLinearContext() *LinearContext { 86 | c := new(LinearContext) 87 | c.g = g 88 | c.linearGo = list.New() 89 | return c 90 | } 91 | 92 | func (c *LinearContext) Go(f func(), cb func()) { 93 | c.g.pendingGo++ 94 | 95 | c.mutexLinearGo.Lock() 96 | c.linearGo.PushBack(&LinearGo{f: f, cb: cb}) 97 | c.mutexLinearGo.Unlock() 98 | 99 | go func() { 100 | c.mutexExecution.Lock() 101 | defer c.mutexExecution.Unlock() 102 | 103 | c.mutexLinearGo.Lock() 104 | e := c.linearGo.Remove(c.linearGo.Front()).(*LinearGo) 105 | c.mutexLinearGo.Unlock() 106 | 107 | defer func() { 108 | c.g.ChanCb <- e.cb 109 | if r := recover(); r != nil { 110 | if conf.LenStackBuf > 0 { 111 | buf := make([]byte, conf.LenStackBuf) 112 | l := runtime.Stack(buf, false) 113 | log.Error("%v: %s", r, buf[:l]) 114 | } else { 115 | log.Error("%v", r) 116 | } 117 | } 118 | }() 119 | 120 | e.f() 121 | }() 122 | } 123 | -------------------------------------------------------------------------------- /network/ws_conn.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "errors" 5 | "github.com/gorilla/websocket" 6 | "github.com/name5566/leaf/log" 7 | "net" 8 | "sync" 9 | ) 10 | 11 | type WebsocketConnSet map[*websocket.Conn]struct{} 12 | 13 | type WSConn struct { 14 | sync.Mutex 15 | conn *websocket.Conn 16 | writeChan chan []byte 17 | maxMsgLen uint32 18 | closeFlag bool 19 | } 20 | 21 | func newWSConn(conn *websocket.Conn, pendingWriteNum int, maxMsgLen uint32) *WSConn { 22 | wsConn := new(WSConn) 23 | wsConn.conn = conn 24 | wsConn.writeChan = make(chan []byte, pendingWriteNum) 25 | wsConn.maxMsgLen = maxMsgLen 26 | 27 | go func() { 28 | for b := range wsConn.writeChan { 29 | if b == nil { 30 | break 31 | } 32 | 33 | err := conn.WriteMessage(websocket.BinaryMessage, b) 34 | if err != nil { 35 | break 36 | } 37 | } 38 | 39 | conn.Close() 40 | wsConn.Lock() 41 | wsConn.closeFlag = true 42 | wsConn.Unlock() 43 | }() 44 | 45 | return wsConn 46 | } 47 | 48 | func (wsConn *WSConn) doDestroy() { 49 | wsConn.conn.UnderlyingConn().(*net.TCPConn).SetLinger(0) 50 | wsConn.conn.Close() 51 | 52 | if !wsConn.closeFlag { 53 | close(wsConn.writeChan) 54 | wsConn.closeFlag = true 55 | } 56 | } 57 | 58 | func (wsConn *WSConn) Destroy() { 59 | wsConn.Lock() 60 | defer wsConn.Unlock() 61 | 62 | wsConn.doDestroy() 63 | } 64 | 65 | func (wsConn *WSConn) Close() { 66 | wsConn.Lock() 67 | defer wsConn.Unlock() 68 | if wsConn.closeFlag { 69 | return 70 | } 71 | 72 | wsConn.doWrite(nil) 73 | wsConn.closeFlag = true 74 | } 75 | 76 | func (wsConn *WSConn) doWrite(b []byte) { 77 | if len(wsConn.writeChan) == cap(wsConn.writeChan) { 78 | log.Debug("close conn: channel full") 79 | wsConn.doDestroy() 80 | return 81 | } 82 | 83 | wsConn.writeChan <- b 84 | } 85 | 86 | func (wsConn *WSConn) LocalAddr() net.Addr { 87 | return wsConn.conn.LocalAddr() 88 | } 89 | 90 | func (wsConn *WSConn) RemoteAddr() net.Addr { 91 | return wsConn.conn.RemoteAddr() 92 | } 93 | 94 | // goroutine not safe 95 | func (wsConn *WSConn) ReadMsg() ([]byte, error) { 96 | _, b, err := wsConn.conn.ReadMessage() 97 | return b, err 98 | } 99 | 100 | // args must not be modified by the others goroutines 101 | func (wsConn *WSConn) WriteMsg(args ...[]byte) error { 102 | wsConn.Lock() 103 | defer wsConn.Unlock() 104 | if wsConn.closeFlag { 105 | return nil 106 | } 107 | 108 | // get len 109 | var msgLen uint32 110 | for i := 0; i < len(args); i++ { 111 | msgLen += uint32(len(args[i])) 112 | } 113 | 114 | // check len 115 | if msgLen > wsConn.maxMsgLen { 116 | return errors.New("message too long") 117 | } else if msgLen < 1 { 118 | return errors.New("message too short") 119 | } 120 | 121 | // don't copy 122 | if len(args) == 1 { 123 | wsConn.doWrite(args[0]) 124 | return nil 125 | } 126 | 127 | // merge the args 128 | msg := make([]byte, msgLen) 129 | l := 0 130 | for i := 0; i < len(args); i++ { 131 | copy(msg[l:], args[i]) 132 | l += len(args[i]) 133 | } 134 | 135 | wsConn.doWrite(msg) 136 | 137 | return nil 138 | } 139 | -------------------------------------------------------------------------------- /module/skeleton.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "github.com/name5566/leaf/chanrpc" 5 | "github.com/name5566/leaf/console" 6 | "github.com/name5566/leaf/go" 7 | "github.com/name5566/leaf/timer" 8 | "time" 9 | ) 10 | 11 | type Skeleton struct { 12 | GoLen int 13 | TimerDispatcherLen int 14 | AsynCallLen int 15 | ChanRPCServer *chanrpc.Server 16 | g *g.Go 17 | dispatcher *timer.Dispatcher 18 | client *chanrpc.Client 19 | server *chanrpc.Server 20 | commandServer *chanrpc.Server 21 | } 22 | 23 | func (s *Skeleton) Init() { 24 | if s.GoLen <= 0 { 25 | s.GoLen = 0 26 | } 27 | if s.TimerDispatcherLen <= 0 { 28 | s.TimerDispatcherLen = 0 29 | } 30 | if s.AsynCallLen <= 0 { 31 | s.AsynCallLen = 0 32 | } 33 | 34 | s.g = g.New(s.GoLen) 35 | s.dispatcher = timer.NewDispatcher(s.TimerDispatcherLen) 36 | s.client = chanrpc.NewClient(s.AsynCallLen) 37 | s.server = s.ChanRPCServer 38 | 39 | if s.server == nil { 40 | s.server = chanrpc.NewServer(0) 41 | } 42 | s.commandServer = chanrpc.NewServer(0) 43 | } 44 | 45 | func (s *Skeleton) Run(closeSig chan bool) { 46 | for { 47 | select { 48 | case <-closeSig: 49 | s.commandServer.Close() 50 | s.server.Close() 51 | for !s.g.Idle() || !s.client.Idle() { 52 | s.g.Close() 53 | s.client.Close() 54 | } 55 | return 56 | case ri := <-s.client.ChanAsynRet: 57 | s.client.Cb(ri) 58 | case ci := <-s.server.ChanCall: 59 | s.server.Exec(ci) 60 | case ci := <-s.commandServer.ChanCall: 61 | s.commandServer.Exec(ci) 62 | case cb := <-s.g.ChanCb: 63 | s.g.Cb(cb) 64 | case t := <-s.dispatcher.ChanTimer: 65 | t.Cb() 66 | } 67 | } 68 | } 69 | 70 | func (s *Skeleton) AfterFunc(d time.Duration, cb func()) *timer.Timer { 71 | if s.TimerDispatcherLen == 0 { 72 | panic("invalid TimerDispatcherLen") 73 | } 74 | 75 | return s.dispatcher.AfterFunc(d, cb) 76 | } 77 | 78 | func (s *Skeleton) CronFunc(cronExpr *timer.CronExpr, cb func()) *timer.Cron { 79 | if s.TimerDispatcherLen == 0 { 80 | panic("invalid TimerDispatcherLen") 81 | } 82 | 83 | return s.dispatcher.CronFunc(cronExpr, cb) 84 | } 85 | 86 | func (s *Skeleton) Go(f func(), cb func()) { 87 | if s.GoLen == 0 { 88 | panic("invalid GoLen") 89 | } 90 | 91 | s.g.Go(f, cb) 92 | } 93 | 94 | func (s *Skeleton) NewLinearContext() *g.LinearContext { 95 | if s.GoLen == 0 { 96 | panic("invalid GoLen") 97 | } 98 | 99 | return s.g.NewLinearContext() 100 | } 101 | 102 | func (s *Skeleton) AsynCall(server *chanrpc.Server, id interface{}, args ...interface{}) { 103 | if s.AsynCallLen == 0 { 104 | panic("invalid AsynCallLen") 105 | } 106 | 107 | s.client.Attach(server) 108 | s.client.AsynCall(id, args...) 109 | } 110 | 111 | func (s *Skeleton) RegisterChanRPC(id interface{}, f interface{}) { 112 | if s.ChanRPCServer == nil { 113 | panic("invalid ChanRPCServer") 114 | } 115 | 116 | s.server.Register(id, f) 117 | } 118 | 119 | func (s *Skeleton) RegisterCommand(name string, help string, f interface{}) { 120 | console.Register(name, help, f, s.commandServer) 121 | } 122 | -------------------------------------------------------------------------------- /network/tcp_client.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "github.com/name5566/leaf/log" 5 | "net" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type TCPClient struct { 11 | sync.Mutex 12 | Addr string 13 | ConnNum int 14 | ConnectInterval time.Duration 15 | PendingWriteNum int 16 | AutoReconnect bool 17 | NewAgent func(*TCPConn) Agent 18 | conns ConnSet 19 | wg sync.WaitGroup 20 | closeFlag bool 21 | 22 | // msg parser 23 | LenMsgLen int 24 | MinMsgLen uint32 25 | MaxMsgLen uint32 26 | LittleEndian bool 27 | msgParser *MsgParser 28 | } 29 | 30 | func (client *TCPClient) Start() { 31 | client.init() 32 | 33 | for i := 0; i < client.ConnNum; i++ { 34 | client.wg.Add(1) 35 | go client.connect() 36 | } 37 | } 38 | 39 | func (client *TCPClient) init() { 40 | client.Lock() 41 | defer client.Unlock() 42 | 43 | if client.ConnNum <= 0 { 44 | client.ConnNum = 1 45 | log.Release("invalid ConnNum, reset to %v", client.ConnNum) 46 | } 47 | if client.ConnectInterval <= 0 { 48 | client.ConnectInterval = 3 * time.Second 49 | log.Release("invalid ConnectInterval, reset to %v", client.ConnectInterval) 50 | } 51 | if client.PendingWriteNum <= 0 { 52 | client.PendingWriteNum = 100 53 | log.Release("invalid PendingWriteNum, reset to %v", client.PendingWriteNum) 54 | } 55 | if client.NewAgent == nil { 56 | log.Fatal("NewAgent must not be nil") 57 | } 58 | if client.conns != nil { 59 | log.Fatal("client is running") 60 | } 61 | 62 | client.conns = make(ConnSet) 63 | client.closeFlag = false 64 | 65 | // msg parser 66 | msgParser := NewMsgParser() 67 | msgParser.SetMsgLen(client.LenMsgLen, client.MinMsgLen, client.MaxMsgLen) 68 | msgParser.SetByteOrder(client.LittleEndian) 69 | client.msgParser = msgParser 70 | } 71 | 72 | func (client *TCPClient) dial() net.Conn { 73 | for { 74 | conn, err := net.Dial("tcp", client.Addr) 75 | if err == nil || client.closeFlag { 76 | return conn 77 | } 78 | 79 | log.Release("connect to %v error: %v", client.Addr, err) 80 | time.Sleep(client.ConnectInterval) 81 | continue 82 | } 83 | } 84 | 85 | func (client *TCPClient) connect() { 86 | defer client.wg.Done() 87 | 88 | reconnect: 89 | conn := client.dial() 90 | if conn == nil { 91 | return 92 | } 93 | 94 | client.Lock() 95 | if client.closeFlag { 96 | client.Unlock() 97 | conn.Close() 98 | return 99 | } 100 | client.conns[conn] = struct{}{} 101 | client.Unlock() 102 | 103 | tcpConn := newTCPConn(conn, client.PendingWriteNum, client.msgParser) 104 | agent := client.NewAgent(tcpConn) 105 | agent.Run() 106 | 107 | // cleanup 108 | tcpConn.Close() 109 | client.Lock() 110 | delete(client.conns, conn) 111 | client.Unlock() 112 | agent.OnClose() 113 | 114 | if client.AutoReconnect { 115 | time.Sleep(client.ConnectInterval) 116 | goto reconnect 117 | } 118 | } 119 | 120 | func (client *TCPClient) Close() { 121 | client.Lock() 122 | client.closeFlag = true 123 | for conn := range client.conns { 124 | conn.Close() 125 | } 126 | client.conns = nil 127 | client.Unlock() 128 | 129 | client.wg.Wait() 130 | } 131 | -------------------------------------------------------------------------------- /network/tcp_server.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "github.com/name5566/leaf/log" 5 | "net" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type TCPServer struct { 11 | Addr string 12 | MaxConnNum int 13 | PendingWriteNum int 14 | NewAgent func(*TCPConn) Agent 15 | ln net.Listener 16 | conns ConnSet 17 | mutexConns sync.Mutex 18 | wgLn sync.WaitGroup 19 | wgConns sync.WaitGroup 20 | 21 | // msg parser 22 | LenMsgLen int 23 | MinMsgLen uint32 24 | MaxMsgLen uint32 25 | LittleEndian bool 26 | msgParser *MsgParser 27 | } 28 | 29 | func (server *TCPServer) Start() { 30 | server.init() 31 | go server.run() 32 | } 33 | 34 | func (server *TCPServer) init() { 35 | ln, err := net.Listen("tcp", server.Addr) 36 | if err != nil { 37 | log.Fatal("%v", err) 38 | } 39 | 40 | if server.MaxConnNum <= 0 { 41 | server.MaxConnNum = 100 42 | log.Release("invalid MaxConnNum, reset to %v", server.MaxConnNum) 43 | } 44 | if server.PendingWriteNum <= 0 { 45 | server.PendingWriteNum = 100 46 | log.Release("invalid PendingWriteNum, reset to %v", server.PendingWriteNum) 47 | } 48 | if server.NewAgent == nil { 49 | log.Fatal("NewAgent must not be nil") 50 | } 51 | 52 | server.ln = ln 53 | server.conns = make(ConnSet) 54 | 55 | // msg parser 56 | msgParser := NewMsgParser() 57 | msgParser.SetMsgLen(server.LenMsgLen, server.MinMsgLen, server.MaxMsgLen) 58 | msgParser.SetByteOrder(server.LittleEndian) 59 | server.msgParser = msgParser 60 | } 61 | 62 | func (server *TCPServer) run() { 63 | server.wgLn.Add(1) 64 | defer server.wgLn.Done() 65 | 66 | var tempDelay time.Duration 67 | for { 68 | conn, err := server.ln.Accept() 69 | if err != nil { 70 | if ne, ok := err.(net.Error); ok && ne.Temporary() { 71 | if tempDelay == 0 { 72 | tempDelay = 5 * time.Millisecond 73 | } else { 74 | tempDelay *= 2 75 | } 76 | if max := 1 * time.Second; tempDelay > max { 77 | tempDelay = max 78 | } 79 | log.Release("accept error: %v; retrying in %v", err, tempDelay) 80 | time.Sleep(tempDelay) 81 | continue 82 | } 83 | return 84 | } 85 | tempDelay = 0 86 | 87 | server.mutexConns.Lock() 88 | if len(server.conns) >= server.MaxConnNum { 89 | server.mutexConns.Unlock() 90 | conn.Close() 91 | log.Debug("too many connections") 92 | continue 93 | } 94 | server.conns[conn] = struct{}{} 95 | server.mutexConns.Unlock() 96 | 97 | server.wgConns.Add(1) 98 | 99 | tcpConn := newTCPConn(conn, server.PendingWriteNum, server.msgParser) 100 | agent := server.NewAgent(tcpConn) 101 | go func() { 102 | agent.Run() 103 | 104 | // cleanup 105 | tcpConn.Close() 106 | server.mutexConns.Lock() 107 | delete(server.conns, conn) 108 | server.mutexConns.Unlock() 109 | agent.OnClose() 110 | 111 | server.wgConns.Done() 112 | }() 113 | } 114 | } 115 | 116 | func (server *TCPServer) Close() { 117 | server.ln.Close() 118 | server.wgLn.Wait() 119 | 120 | server.mutexConns.Lock() 121 | for conn := range server.conns { 122 | conn.Close() 123 | } 124 | server.conns = nil 125 | server.mutexConns.Unlock() 126 | server.wgConns.Wait() 127 | } 128 | -------------------------------------------------------------------------------- /network/ws_client.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "github.com/gorilla/websocket" 5 | "github.com/name5566/leaf/log" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type WSClient struct { 11 | sync.Mutex 12 | Addr string 13 | ConnNum int 14 | ConnectInterval time.Duration 15 | PendingWriteNum int 16 | MaxMsgLen uint32 17 | HandshakeTimeout time.Duration 18 | AutoReconnect bool 19 | NewAgent func(*WSConn) Agent 20 | dialer websocket.Dialer 21 | conns WebsocketConnSet 22 | wg sync.WaitGroup 23 | closeFlag bool 24 | } 25 | 26 | func (client *WSClient) Start() { 27 | client.init() 28 | 29 | for i := 0; i < client.ConnNum; i++ { 30 | client.wg.Add(1) 31 | go client.connect() 32 | } 33 | } 34 | 35 | func (client *WSClient) init() { 36 | client.Lock() 37 | defer client.Unlock() 38 | 39 | if client.ConnNum <= 0 { 40 | client.ConnNum = 1 41 | log.Release("invalid ConnNum, reset to %v", client.ConnNum) 42 | } 43 | if client.ConnectInterval <= 0 { 44 | client.ConnectInterval = 3 * time.Second 45 | log.Release("invalid ConnectInterval, reset to %v", client.ConnectInterval) 46 | } 47 | if client.PendingWriteNum <= 0 { 48 | client.PendingWriteNum = 100 49 | log.Release("invalid PendingWriteNum, reset to %v", client.PendingWriteNum) 50 | } 51 | if client.MaxMsgLen <= 0 { 52 | client.MaxMsgLen = 4096 53 | log.Release("invalid MaxMsgLen, reset to %v", client.MaxMsgLen) 54 | } 55 | if client.HandshakeTimeout <= 0 { 56 | client.HandshakeTimeout = 10 * time.Second 57 | log.Release("invalid HandshakeTimeout, reset to %v", client.HandshakeTimeout) 58 | } 59 | if client.NewAgent == nil { 60 | log.Fatal("NewAgent must not be nil") 61 | } 62 | if client.conns != nil { 63 | log.Fatal("client is running") 64 | } 65 | 66 | client.conns = make(WebsocketConnSet) 67 | client.closeFlag = false 68 | client.dialer = websocket.Dialer{ 69 | HandshakeTimeout: client.HandshakeTimeout, 70 | } 71 | } 72 | 73 | func (client *WSClient) dial() *websocket.Conn { 74 | for { 75 | conn, _, err := client.dialer.Dial(client.Addr, nil) 76 | if err == nil || client.closeFlag { 77 | return conn 78 | } 79 | 80 | log.Release("connect to %v error: %v", client.Addr, err) 81 | time.Sleep(client.ConnectInterval) 82 | continue 83 | } 84 | } 85 | 86 | func (client *WSClient) connect() { 87 | defer client.wg.Done() 88 | 89 | reconnect: 90 | conn := client.dial() 91 | if conn == nil { 92 | return 93 | } 94 | conn.SetReadLimit(int64(client.MaxMsgLen)) 95 | 96 | client.Lock() 97 | if client.closeFlag { 98 | client.Unlock() 99 | conn.Close() 100 | return 101 | } 102 | client.conns[conn] = struct{}{} 103 | client.Unlock() 104 | 105 | wsConn := newWSConn(conn, client.PendingWriteNum, client.MaxMsgLen) 106 | agent := client.NewAgent(wsConn) 107 | agent.Run() 108 | 109 | // cleanup 110 | wsConn.Close() 111 | client.Lock() 112 | delete(client.conns, conn) 113 | client.Unlock() 114 | agent.OnClose() 115 | 116 | if client.AutoReconnect { 117 | time.Sleep(client.ConnectInterval) 118 | goto reconnect 119 | } 120 | } 121 | 122 | func (client *WSClient) Close() { 123 | client.Lock() 124 | client.closeFlag = true 125 | for conn := range client.conns { 126 | conn.Close() 127 | } 128 | client.conns = nil 129 | client.Unlock() 130 | 131 | client.wg.Wait() 132 | } 133 | -------------------------------------------------------------------------------- /network/tcp_msg.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | "math" 8 | ) 9 | 10 | // -------------- 11 | // | len | data | 12 | // -------------- 13 | type MsgParser struct { 14 | lenMsgLen int 15 | minMsgLen uint32 16 | maxMsgLen uint32 17 | littleEndian bool 18 | } 19 | 20 | func NewMsgParser() *MsgParser { 21 | p := new(MsgParser) 22 | p.lenMsgLen = 2 23 | p.minMsgLen = 1 24 | p.maxMsgLen = 4096 25 | p.littleEndian = false 26 | 27 | return p 28 | } 29 | 30 | // It's dangerous to call the method on reading or writing 31 | func (p *MsgParser) SetMsgLen(lenMsgLen int, minMsgLen uint32, maxMsgLen uint32) { 32 | if lenMsgLen == 1 || lenMsgLen == 2 || lenMsgLen == 4 { 33 | p.lenMsgLen = lenMsgLen 34 | } 35 | if minMsgLen != 0 { 36 | p.minMsgLen = minMsgLen 37 | } 38 | if maxMsgLen != 0 { 39 | p.maxMsgLen = maxMsgLen 40 | } 41 | 42 | var max uint32 43 | switch p.lenMsgLen { 44 | case 1: 45 | max = math.MaxUint8 46 | case 2: 47 | max = math.MaxUint16 48 | case 4: 49 | max = math.MaxUint32 50 | } 51 | if p.minMsgLen > max { 52 | p.minMsgLen = max 53 | } 54 | if p.maxMsgLen > max { 55 | p.maxMsgLen = max 56 | } 57 | } 58 | 59 | // It's dangerous to call the method on reading or writing 60 | func (p *MsgParser) SetByteOrder(littleEndian bool) { 61 | p.littleEndian = littleEndian 62 | } 63 | 64 | // goroutine safe 65 | func (p *MsgParser) Read(conn *TCPConn) ([]byte, error) { 66 | var b [4]byte 67 | bufMsgLen := b[:p.lenMsgLen] 68 | 69 | // read len 70 | if _, err := io.ReadFull(conn, bufMsgLen); err != nil { 71 | return nil, err 72 | } 73 | 74 | // parse len 75 | var msgLen uint32 76 | switch p.lenMsgLen { 77 | case 1: 78 | msgLen = uint32(bufMsgLen[0]) 79 | case 2: 80 | if p.littleEndian { 81 | msgLen = uint32(binary.LittleEndian.Uint16(bufMsgLen)) 82 | } else { 83 | msgLen = uint32(binary.BigEndian.Uint16(bufMsgLen)) 84 | } 85 | case 4: 86 | if p.littleEndian { 87 | msgLen = binary.LittleEndian.Uint32(bufMsgLen) 88 | } else { 89 | msgLen = binary.BigEndian.Uint32(bufMsgLen) 90 | } 91 | } 92 | 93 | // check len 94 | if msgLen > p.maxMsgLen { 95 | return nil, errors.New("message too long") 96 | } else if msgLen < p.minMsgLen { 97 | return nil, errors.New("message too short") 98 | } 99 | 100 | // data 101 | msgData := make([]byte, msgLen) 102 | if _, err := io.ReadFull(conn, msgData); err != nil { 103 | return nil, err 104 | } 105 | 106 | return msgData, nil 107 | } 108 | 109 | // goroutine safe 110 | func (p *MsgParser) Write(conn *TCPConn, args ...[]byte) error { 111 | // get len 112 | var msgLen uint32 113 | for i := 0; i < len(args); i++ { 114 | msgLen += uint32(len(args[i])) 115 | } 116 | 117 | // check len 118 | if msgLen > p.maxMsgLen { 119 | return errors.New("message too long") 120 | } else if msgLen < p.minMsgLen { 121 | return errors.New("message too short") 122 | } 123 | 124 | msg := make([]byte, uint32(p.lenMsgLen)+msgLen) 125 | 126 | // write len 127 | switch p.lenMsgLen { 128 | case 1: 129 | msg[0] = byte(msgLen) 130 | case 2: 131 | if p.littleEndian { 132 | binary.LittleEndian.PutUint16(msg, uint16(msgLen)) 133 | } else { 134 | binary.BigEndian.PutUint16(msg, uint16(msgLen)) 135 | } 136 | case 4: 137 | if p.littleEndian { 138 | binary.LittleEndian.PutUint32(msg, msgLen) 139 | } else { 140 | binary.BigEndian.PutUint32(msg, msgLen) 141 | } 142 | } 143 | 144 | // write data 145 | l := p.lenMsgLen 146 | for i := 0; i < len(args); i++ { 147 | copy(msg[l:], args[i]) 148 | l += len(args[i]) 149 | } 150 | 151 | conn.Write(msg) 152 | 153 | return nil 154 | } 155 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "os" 8 | "path" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | // levels 14 | const ( 15 | debugLevel = 0 16 | releaseLevel = 1 17 | errorLevel = 2 18 | fatalLevel = 3 19 | ) 20 | 21 | const ( 22 | printDebugLevel = "[debug ] " 23 | printReleaseLevel = "[release] " 24 | printErrorLevel = "[error ] " 25 | printFatalLevel = "[fatal ] " 26 | ) 27 | 28 | type Logger struct { 29 | level int 30 | baseLogger *log.Logger 31 | baseFile *os.File 32 | } 33 | 34 | func New(strLevel string, pathname string, flag int) (*Logger, error) { 35 | // level 36 | var level int 37 | switch strings.ToLower(strLevel) { 38 | case "debug": 39 | level = debugLevel 40 | case "release": 41 | level = releaseLevel 42 | case "error": 43 | level = errorLevel 44 | case "fatal": 45 | level = fatalLevel 46 | default: 47 | return nil, errors.New("unknown level: " + strLevel) 48 | } 49 | 50 | // logger 51 | var baseLogger *log.Logger 52 | var baseFile *os.File 53 | if pathname != "" { 54 | now := time.Now() 55 | 56 | filename := fmt.Sprintf("%d%02d%02d_%02d_%02d_%02d.log", 57 | now.Year(), 58 | now.Month(), 59 | now.Day(), 60 | now.Hour(), 61 | now.Minute(), 62 | now.Second()) 63 | 64 | file, err := os.Create(path.Join(pathname, filename)) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | baseLogger = log.New(file, "", flag) 70 | baseFile = file 71 | } else { 72 | baseLogger = log.New(os.Stdout, "", flag) 73 | } 74 | 75 | // new 76 | logger := new(Logger) 77 | logger.level = level 78 | logger.baseLogger = baseLogger 79 | logger.baseFile = baseFile 80 | 81 | return logger, nil 82 | } 83 | 84 | // It's dangerous to call the method on logging 85 | func (logger *Logger) Close() { 86 | if logger.baseFile != nil { 87 | logger.baseFile.Close() 88 | } 89 | 90 | logger.baseLogger = nil 91 | logger.baseFile = nil 92 | } 93 | 94 | func (logger *Logger) doPrintf(level int, printLevel string, format string, a ...interface{}) { 95 | if level < logger.level { 96 | return 97 | } 98 | if logger.baseLogger == nil { 99 | panic("logger closed") 100 | } 101 | 102 | format = printLevel + format 103 | logger.baseLogger.Output(3, fmt.Sprintf(format, a...)) 104 | 105 | if level == fatalLevel { 106 | os.Exit(1) 107 | } 108 | } 109 | 110 | func (logger *Logger) Debug(format string, a ...interface{}) { 111 | logger.doPrintf(debugLevel, printDebugLevel, format, a...) 112 | } 113 | 114 | func (logger *Logger) Release(format string, a ...interface{}) { 115 | logger.doPrintf(releaseLevel, printReleaseLevel, format, a...) 116 | } 117 | 118 | func (logger *Logger) Error(format string, a ...interface{}) { 119 | logger.doPrintf(errorLevel, printErrorLevel, format, a...) 120 | } 121 | 122 | func (logger *Logger) Fatal(format string, a ...interface{}) { 123 | logger.doPrintf(fatalLevel, printFatalLevel, format, a...) 124 | } 125 | 126 | var gLogger, _ = New("debug", "", log.LstdFlags) 127 | 128 | // It's dangerous to call the method on logging 129 | func Export(logger *Logger) { 130 | if logger != nil { 131 | gLogger = logger 132 | } 133 | } 134 | 135 | func Debug(format string, a ...interface{}) { 136 | gLogger.doPrintf(debugLevel, printDebugLevel, format, a...) 137 | } 138 | 139 | func Release(format string, a ...interface{}) { 140 | gLogger.doPrintf(releaseLevel, printReleaseLevel, format, a...) 141 | } 142 | 143 | func Error(format string, a ...interface{}) { 144 | gLogger.doPrintf(errorLevel, printErrorLevel, format, a...) 145 | } 146 | 147 | func Fatal(format string, a ...interface{}) { 148 | gLogger.doPrintf(fatalLevel, printFatalLevel, format, a...) 149 | } 150 | 151 | func Close() { 152 | gLogger.Close() 153 | } 154 | -------------------------------------------------------------------------------- /db/mongodb/mongodb.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "container/heap" 5 | "github.com/name5566/leaf/log" 6 | "gopkg.in/mgo.v2" 7 | "gopkg.in/mgo.v2/bson" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | // session 13 | type Session struct { 14 | *mgo.Session 15 | ref int 16 | index int 17 | } 18 | 19 | // session heap 20 | type SessionHeap []*Session 21 | 22 | func (h SessionHeap) Len() int { 23 | return len(h) 24 | } 25 | 26 | func (h SessionHeap) Less(i, j int) bool { 27 | return h[i].ref < h[j].ref 28 | } 29 | 30 | func (h SessionHeap) Swap(i, j int) { 31 | h[i], h[j] = h[j], h[i] 32 | h[i].index = i 33 | h[j].index = j 34 | } 35 | 36 | func (h *SessionHeap) Push(s interface{}) { 37 | s.(*Session).index = len(*h) 38 | *h = append(*h, s.(*Session)) 39 | } 40 | 41 | func (h *SessionHeap) Pop() interface{} { 42 | l := len(*h) 43 | s := (*h)[l-1] 44 | s.index = -1 45 | *h = (*h)[:l-1] 46 | return s 47 | } 48 | 49 | type DialContext struct { 50 | sync.Mutex 51 | sessions SessionHeap 52 | } 53 | 54 | // goroutine safe 55 | func Dial(url string, sessionNum int) (*DialContext, error) { 56 | c, err := DialWithTimeout(url, sessionNum, 10*time.Second, 5*time.Minute) 57 | return c, err 58 | } 59 | 60 | // goroutine safe 61 | func DialWithTimeout(url string, sessionNum int, dialTimeout time.Duration, timeout time.Duration) (*DialContext, error) { 62 | if sessionNum <= 0 { 63 | sessionNum = 100 64 | log.Release("invalid sessionNum, reset to %v", sessionNum) 65 | } 66 | 67 | s, err := mgo.DialWithTimeout(url, dialTimeout) 68 | if err != nil { 69 | return nil, err 70 | } 71 | s.SetSyncTimeout(timeout) 72 | s.SetSocketTimeout(timeout) 73 | 74 | c := new(DialContext) 75 | 76 | // sessions 77 | c.sessions = make(SessionHeap, sessionNum) 78 | c.sessions[0] = &Session{s, 0, 0} 79 | for i := 1; i < sessionNum; i++ { 80 | c.sessions[i] = &Session{s.New(), 0, i} 81 | } 82 | heap.Init(&c.sessions) 83 | 84 | return c, nil 85 | } 86 | 87 | // goroutine safe 88 | func (c *DialContext) Close() { 89 | c.Lock() 90 | for _, s := range c.sessions { 91 | s.Close() 92 | if s.ref != 0 { 93 | log.Error("session ref = %v", s.ref) 94 | } 95 | } 96 | c.Unlock() 97 | } 98 | 99 | // goroutine safe 100 | func (c *DialContext) Ref() *Session { 101 | c.Lock() 102 | s := c.sessions[0] 103 | if s.ref == 0 { 104 | s.Refresh() 105 | } 106 | s.ref++ 107 | heap.Fix(&c.sessions, 0) 108 | c.Unlock() 109 | 110 | return s 111 | } 112 | 113 | // goroutine safe 114 | func (c *DialContext) UnRef(s *Session) { 115 | c.Lock() 116 | s.ref-- 117 | heap.Fix(&c.sessions, s.index) 118 | c.Unlock() 119 | } 120 | 121 | // goroutine safe 122 | func (c *DialContext) EnsureCounter(db string, collection string, id string) error { 123 | s := c.Ref() 124 | defer c.UnRef(s) 125 | 126 | err := s.DB(db).C(collection).Insert(bson.M{ 127 | "_id": id, 128 | "seq": 0, 129 | }) 130 | if mgo.IsDup(err) { 131 | return nil 132 | } else { 133 | return err 134 | } 135 | } 136 | 137 | // goroutine safe 138 | func (c *DialContext) NextSeq(db string, collection string, id string) (int, error) { 139 | s := c.Ref() 140 | defer c.UnRef(s) 141 | 142 | var res struct { 143 | Seq int 144 | } 145 | _, err := s.DB(db).C(collection).FindId(id).Apply(mgo.Change{ 146 | Update: bson.M{"$inc": bson.M{"seq": 1}}, 147 | ReturnNew: true, 148 | }, &res) 149 | 150 | return res.Seq, err 151 | } 152 | 153 | // goroutine safe 154 | func (c *DialContext) EnsureIndex(db string, collection string, key []string) error { 155 | s := c.Ref() 156 | defer c.UnRef(s) 157 | 158 | return s.DB(db).C(collection).EnsureIndex(mgo.Index{ 159 | Key: key, 160 | Unique: false, 161 | Sparse: true, 162 | }) 163 | } 164 | 165 | // goroutine safe 166 | func (c *DialContext) EnsureUniqueIndex(db string, collection string, key []string) error { 167 | s := c.Ref() 168 | defer c.UnRef(s) 169 | 170 | return s.DB(db).C(collection).EnsureIndex(mgo.Index{ 171 | Key: key, 172 | Unique: true, 173 | Sparse: true, 174 | }) 175 | } 176 | -------------------------------------------------------------------------------- /gate/gate.go: -------------------------------------------------------------------------------- 1 | package gate 2 | 3 | import ( 4 | "github.com/name5566/leaf/chanrpc" 5 | "github.com/name5566/leaf/log" 6 | "github.com/name5566/leaf/network" 7 | "net" 8 | "reflect" 9 | "time" 10 | ) 11 | 12 | type Gate struct { 13 | MaxConnNum int 14 | PendingWriteNum int 15 | MaxMsgLen uint32 16 | Processor network.Processor 17 | AgentChanRPC *chanrpc.Server 18 | 19 | // websocket 20 | WSAddr string 21 | HTTPTimeout time.Duration 22 | CertFile string 23 | KeyFile string 24 | 25 | // tcp 26 | TCPAddr string 27 | LenMsgLen int 28 | LittleEndian bool 29 | } 30 | 31 | func (gate *Gate) Run(closeSig chan bool) { 32 | var wsServer *network.WSServer 33 | if gate.WSAddr != "" { 34 | wsServer = new(network.WSServer) 35 | wsServer.Addr = gate.WSAddr 36 | wsServer.MaxConnNum = gate.MaxConnNum 37 | wsServer.PendingWriteNum = gate.PendingWriteNum 38 | wsServer.MaxMsgLen = gate.MaxMsgLen 39 | wsServer.HTTPTimeout = gate.HTTPTimeout 40 | wsServer.CertFile = gate.CertFile 41 | wsServer.KeyFile = gate.KeyFile 42 | wsServer.NewAgent = func(conn *network.WSConn) network.Agent { 43 | a := &agent{conn: conn, gate: gate} 44 | if gate.AgentChanRPC != nil { 45 | gate.AgentChanRPC.Go("NewAgent", a) 46 | } 47 | return a 48 | } 49 | } 50 | 51 | var tcpServer *network.TCPServer 52 | if gate.TCPAddr != "" { 53 | tcpServer = new(network.TCPServer) 54 | tcpServer.Addr = gate.TCPAddr 55 | tcpServer.MaxConnNum = gate.MaxConnNum 56 | tcpServer.PendingWriteNum = gate.PendingWriteNum 57 | tcpServer.LenMsgLen = gate.LenMsgLen 58 | tcpServer.MaxMsgLen = gate.MaxMsgLen 59 | tcpServer.LittleEndian = gate.LittleEndian 60 | tcpServer.NewAgent = func(conn *network.TCPConn) network.Agent { 61 | a := &agent{conn: conn, gate: gate} 62 | if gate.AgentChanRPC != nil { 63 | gate.AgentChanRPC.Go("NewAgent", a) 64 | } 65 | return a 66 | } 67 | } 68 | 69 | if wsServer != nil { 70 | wsServer.Start() 71 | } 72 | if tcpServer != nil { 73 | tcpServer.Start() 74 | } 75 | <-closeSig 76 | if wsServer != nil { 77 | wsServer.Close() 78 | } 79 | if tcpServer != nil { 80 | tcpServer.Close() 81 | } 82 | } 83 | 84 | func (gate *Gate) OnDestroy() {} 85 | 86 | type agent struct { 87 | conn network.Conn 88 | gate *Gate 89 | userData interface{} 90 | } 91 | 92 | func (a *agent) Run() { 93 | for { 94 | data, err := a.conn.ReadMsg() 95 | if err != nil { 96 | log.Debug("read message: %v", err) 97 | break 98 | } 99 | 100 | if a.gate.Processor != nil { 101 | msg, err := a.gate.Processor.Unmarshal(data) 102 | if err != nil { 103 | log.Debug("unmarshal message error: %v", err) 104 | break 105 | } 106 | err = a.gate.Processor.Route(msg, a) 107 | if err != nil { 108 | log.Debug("route message error: %v", err) 109 | break 110 | } 111 | } 112 | } 113 | } 114 | 115 | func (a *agent) OnClose() { 116 | if a.gate.AgentChanRPC != nil { 117 | err := a.gate.AgentChanRPC.Call0("CloseAgent", a) 118 | if err != nil { 119 | log.Error("chanrpc error: %v", err) 120 | } 121 | } 122 | } 123 | 124 | func (a *agent) WriteMsg(msg interface{}) { 125 | if a.gate.Processor != nil { 126 | data, err := a.gate.Processor.Marshal(msg) 127 | if err != nil { 128 | log.Error("marshal message %v error: %v", reflect.TypeOf(msg), err) 129 | return 130 | } 131 | err = a.conn.WriteMsg(data...) 132 | if err != nil { 133 | log.Error("write message %v error: %v", reflect.TypeOf(msg), err) 134 | } 135 | } 136 | } 137 | 138 | func (a *agent) LocalAddr() net.Addr { 139 | return a.conn.LocalAddr() 140 | } 141 | 142 | func (a *agent) RemoteAddr() net.Addr { 143 | return a.conn.RemoteAddr() 144 | } 145 | 146 | func (a *agent) Close() { 147 | a.conn.Close() 148 | } 149 | 150 | func (a *agent) Destroy() { 151 | a.conn.Destroy() 152 | } 153 | 154 | func (a *agent) UserData() interface{} { 155 | return a.userData 156 | } 157 | 158 | func (a *agent) SetUserData(data interface{}) { 159 | a.userData = data 160 | } 161 | -------------------------------------------------------------------------------- /network/ws_server.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "crypto/tls" 5 | "github.com/gorilla/websocket" 6 | "github.com/name5566/leaf/log" 7 | "net" 8 | "net/http" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type WSServer struct { 14 | Addr string 15 | MaxConnNum int 16 | PendingWriteNum int 17 | MaxMsgLen uint32 18 | HTTPTimeout time.Duration 19 | CertFile string 20 | KeyFile string 21 | NewAgent func(*WSConn) Agent 22 | ln net.Listener 23 | handler *WSHandler 24 | } 25 | 26 | type WSHandler struct { 27 | maxConnNum int 28 | pendingWriteNum int 29 | maxMsgLen uint32 30 | newAgent func(*WSConn) Agent 31 | upgrader websocket.Upgrader 32 | conns WebsocketConnSet 33 | mutexConns sync.Mutex 34 | wg sync.WaitGroup 35 | } 36 | 37 | func (handler *WSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 38 | if r.Method != "GET" { 39 | http.Error(w, "Method not allowed", 405) 40 | return 41 | } 42 | conn, err := handler.upgrader.Upgrade(w, r, nil) 43 | if err != nil { 44 | log.Debug("upgrade error: %v", err) 45 | return 46 | } 47 | conn.SetReadLimit(int64(handler.maxMsgLen)) 48 | 49 | handler.wg.Add(1) 50 | defer handler.wg.Done() 51 | 52 | handler.mutexConns.Lock() 53 | if handler.conns == nil { 54 | handler.mutexConns.Unlock() 55 | conn.Close() 56 | return 57 | } 58 | if len(handler.conns) >= handler.maxConnNum { 59 | handler.mutexConns.Unlock() 60 | conn.Close() 61 | log.Debug("too many connections") 62 | return 63 | } 64 | handler.conns[conn] = struct{}{} 65 | handler.mutexConns.Unlock() 66 | 67 | wsConn := newWSConn(conn, handler.pendingWriteNum, handler.maxMsgLen) 68 | agent := handler.newAgent(wsConn) 69 | agent.Run() 70 | 71 | // cleanup 72 | wsConn.Close() 73 | handler.mutexConns.Lock() 74 | delete(handler.conns, conn) 75 | handler.mutexConns.Unlock() 76 | agent.OnClose() 77 | } 78 | 79 | func (server *WSServer) Start() { 80 | ln, err := net.Listen("tcp", server.Addr) 81 | if err != nil { 82 | log.Fatal("%v", err) 83 | } 84 | 85 | if server.MaxConnNum <= 0 { 86 | server.MaxConnNum = 100 87 | log.Release("invalid MaxConnNum, reset to %v", server.MaxConnNum) 88 | } 89 | if server.PendingWriteNum <= 0 { 90 | server.PendingWriteNum = 100 91 | log.Release("invalid PendingWriteNum, reset to %v", server.PendingWriteNum) 92 | } 93 | if server.MaxMsgLen <= 0 { 94 | server.MaxMsgLen = 4096 95 | log.Release("invalid MaxMsgLen, reset to %v", server.MaxMsgLen) 96 | } 97 | if server.HTTPTimeout <= 0 { 98 | server.HTTPTimeout = 10 * time.Second 99 | log.Release("invalid HTTPTimeout, reset to %v", server.HTTPTimeout) 100 | } 101 | if server.NewAgent == nil { 102 | log.Fatal("NewAgent must not be nil") 103 | } 104 | 105 | if server.CertFile != "" || server.KeyFile != "" { 106 | config := &tls.Config{} 107 | config.NextProtos = []string{"http/1.1"} 108 | 109 | var err error 110 | config.Certificates = make([]tls.Certificate, 1) 111 | config.Certificates[0], err = tls.LoadX509KeyPair(server.CertFile, server.KeyFile) 112 | if err != nil { 113 | log.Fatal("%v", err) 114 | } 115 | 116 | ln = tls.NewListener(ln, config) 117 | } 118 | 119 | server.ln = ln 120 | server.handler = &WSHandler{ 121 | maxConnNum: server.MaxConnNum, 122 | pendingWriteNum: server.PendingWriteNum, 123 | maxMsgLen: server.MaxMsgLen, 124 | newAgent: server.NewAgent, 125 | conns: make(WebsocketConnSet), 126 | upgrader: websocket.Upgrader{ 127 | HandshakeTimeout: server.HTTPTimeout, 128 | CheckOrigin: func(_ *http.Request) bool { return true }, 129 | }, 130 | } 131 | 132 | httpServer := &http.Server{ 133 | Addr: server.Addr, 134 | Handler: server.handler, 135 | ReadTimeout: server.HTTPTimeout, 136 | WriteTimeout: server.HTTPTimeout, 137 | MaxHeaderBytes: 1024, 138 | } 139 | 140 | go httpServer.Serve(ln) 141 | } 142 | 143 | func (server *WSServer) Close() { 144 | server.ln.Close() 145 | 146 | server.handler.mutexConns.Lock() 147 | for conn := range server.handler.conns { 148 | conn.Close() 149 | } 150 | server.handler.conns = nil 151 | server.handler.mutexConns.Unlock() 152 | 153 | server.handler.wg.Wait() 154 | } 155 | -------------------------------------------------------------------------------- /network/json/json.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/name5566/leaf/chanrpc" 8 | "github.com/name5566/leaf/log" 9 | "reflect" 10 | ) 11 | 12 | type Processor struct { 13 | msgInfo map[string]*MsgInfo 14 | } 15 | 16 | type MsgInfo struct { 17 | msgType reflect.Type 18 | msgRouter *chanrpc.Server 19 | msgHandler MsgHandler 20 | msgRawHandler MsgHandler 21 | } 22 | 23 | type MsgHandler func([]interface{}) 24 | 25 | type MsgRaw struct { 26 | msgID string 27 | msgRawData json.RawMessage 28 | } 29 | 30 | func NewProcessor() *Processor { 31 | p := new(Processor) 32 | p.msgInfo = make(map[string]*MsgInfo) 33 | return p 34 | } 35 | 36 | // It's dangerous to call the method on routing or marshaling (unmarshaling) 37 | func (p *Processor) Register(msg interface{}) string { 38 | msgType := reflect.TypeOf(msg) 39 | if msgType == nil || msgType.Kind() != reflect.Ptr { 40 | log.Fatal("json message pointer required") 41 | } 42 | msgID := msgType.Elem().Name() 43 | if msgID == "" { 44 | log.Fatal("unnamed json message") 45 | } 46 | if _, ok := p.msgInfo[msgID]; ok { 47 | log.Fatal("message %v is already registered", msgID) 48 | } 49 | 50 | i := new(MsgInfo) 51 | i.msgType = msgType 52 | p.msgInfo[msgID] = i 53 | return msgID 54 | } 55 | 56 | // It's dangerous to call the method on routing or marshaling (unmarshaling) 57 | func (p *Processor) SetRouter(msg interface{}, msgRouter *chanrpc.Server) { 58 | msgType := reflect.TypeOf(msg) 59 | if msgType == nil || msgType.Kind() != reflect.Ptr { 60 | log.Fatal("json message pointer required") 61 | } 62 | msgID := msgType.Elem().Name() 63 | i, ok := p.msgInfo[msgID] 64 | if !ok { 65 | log.Fatal("message %v not registered", msgID) 66 | } 67 | 68 | i.msgRouter = msgRouter 69 | } 70 | 71 | // It's dangerous to call the method on routing or marshaling (unmarshaling) 72 | func (p *Processor) SetHandler(msg interface{}, msgHandler MsgHandler) { 73 | msgType := reflect.TypeOf(msg) 74 | if msgType == nil || msgType.Kind() != reflect.Ptr { 75 | log.Fatal("json message pointer required") 76 | } 77 | msgID := msgType.Elem().Name() 78 | i, ok := p.msgInfo[msgID] 79 | if !ok { 80 | log.Fatal("message %v not registered", msgID) 81 | } 82 | 83 | i.msgHandler = msgHandler 84 | } 85 | 86 | // It's dangerous to call the method on routing or marshaling (unmarshaling) 87 | func (p *Processor) SetRawHandler(msgID string, msgRawHandler MsgHandler) { 88 | i, ok := p.msgInfo[msgID] 89 | if !ok { 90 | log.Fatal("message %v not registered", msgID) 91 | } 92 | 93 | i.msgRawHandler = msgRawHandler 94 | } 95 | 96 | // goroutine safe 97 | func (p *Processor) Route(msg interface{}, userData interface{}) error { 98 | // raw 99 | if msgRaw, ok := msg.(MsgRaw); ok { 100 | i, ok := p.msgInfo[msgRaw.msgID] 101 | if !ok { 102 | return fmt.Errorf("message %v not registered", msgRaw.msgID) 103 | } 104 | if i.msgRawHandler != nil { 105 | i.msgRawHandler([]interface{}{msgRaw.msgID, msgRaw.msgRawData, userData}) 106 | } 107 | return nil 108 | } 109 | 110 | // json 111 | msgType := reflect.TypeOf(msg) 112 | if msgType == nil || msgType.Kind() != reflect.Ptr { 113 | return errors.New("json message pointer required") 114 | } 115 | msgID := msgType.Elem().Name() 116 | i, ok := p.msgInfo[msgID] 117 | if !ok { 118 | return fmt.Errorf("message %v not registered", msgID) 119 | } 120 | if i.msgHandler != nil { 121 | i.msgHandler([]interface{}{msg, userData}) 122 | } 123 | if i.msgRouter != nil { 124 | i.msgRouter.Go(msgType, msg, userData) 125 | } 126 | return nil 127 | } 128 | 129 | // goroutine safe 130 | func (p *Processor) Unmarshal(data []byte) (interface{}, error) { 131 | var m map[string]json.RawMessage 132 | err := json.Unmarshal(data, &m) 133 | if err != nil { 134 | return nil, err 135 | } 136 | if len(m) != 1 { 137 | return nil, errors.New("invalid json data") 138 | } 139 | 140 | for msgID, data := range m { 141 | i, ok := p.msgInfo[msgID] 142 | if !ok { 143 | return nil, fmt.Errorf("message %v not registered", msgID) 144 | } 145 | 146 | // msg 147 | if i.msgRawHandler != nil { 148 | return MsgRaw{msgID, data}, nil 149 | } else { 150 | msg := reflect.New(i.msgType.Elem()).Interface() 151 | return msg, json.Unmarshal(data, msg) 152 | } 153 | } 154 | 155 | panic("bug") 156 | } 157 | 158 | // goroutine safe 159 | func (p *Processor) Marshal(msg interface{}) ([][]byte, error) { 160 | msgType := reflect.TypeOf(msg) 161 | if msgType == nil || msgType.Kind() != reflect.Ptr { 162 | return nil, errors.New("json message pointer required") 163 | } 164 | msgID := msgType.Elem().Name() 165 | if _, ok := p.msgInfo[msgID]; !ok { 166 | return nil, fmt.Errorf("message %v not registered", msgID) 167 | } 168 | 169 | // data 170 | m := map[string]interface{}{msgID: msg} 171 | data, err := json.Marshal(m) 172 | return [][]byte{data}, err 173 | } 174 | -------------------------------------------------------------------------------- /console/command.go: -------------------------------------------------------------------------------- 1 | package console 2 | 3 | import ( 4 | "fmt" 5 | "github.com/name5566/leaf/chanrpc" 6 | "github.com/name5566/leaf/conf" 7 | "github.com/name5566/leaf/log" 8 | "os" 9 | "path" 10 | "runtime/pprof" 11 | "time" 12 | ) 13 | 14 | var commands = []Command{ 15 | new(CommandHelp), 16 | new(CommandCPUProf), 17 | new(CommandProf), 18 | } 19 | 20 | type Command interface { 21 | // must goroutine safe 22 | name() string 23 | // must goroutine safe 24 | help() string 25 | // must goroutine safe 26 | run(args []string) string 27 | } 28 | 29 | type ExternalCommand struct { 30 | _name string 31 | _help string 32 | server *chanrpc.Server 33 | } 34 | 35 | func (c *ExternalCommand) name() string { 36 | return c._name 37 | } 38 | 39 | func (c *ExternalCommand) help() string { 40 | return c._help 41 | } 42 | 43 | func (c *ExternalCommand) run(_args []string) string { 44 | args := make([]interface{}, len(_args)) 45 | for i, v := range _args { 46 | args[i] = v 47 | } 48 | 49 | ret, err := c.server.Call1(c._name, args...) 50 | if err != nil { 51 | return err.Error() 52 | } 53 | output, ok := ret.(string) 54 | if !ok { 55 | return "invalid output type" 56 | } 57 | 58 | return output 59 | } 60 | 61 | // you must call the function before calling console.Init 62 | // goroutine not safe 63 | func Register(name string, help string, f interface{}, server *chanrpc.Server) { 64 | for _, c := range commands { 65 | if c.name() == name { 66 | log.Fatal("command %v is already registered", name) 67 | } 68 | } 69 | 70 | server.Register(name, f) 71 | 72 | c := new(ExternalCommand) 73 | c._name = name 74 | c._help = help 75 | c.server = server 76 | commands = append(commands, c) 77 | } 78 | 79 | // help 80 | type CommandHelp struct{} 81 | 82 | func (c *CommandHelp) name() string { 83 | return "help" 84 | } 85 | 86 | func (c *CommandHelp) help() string { 87 | return "this help text" 88 | } 89 | 90 | func (c *CommandHelp) run([]string) string { 91 | output := "Commands:\r\n" 92 | for _, c := range commands { 93 | output += c.name() + " - " + c.help() + "\r\n" 94 | } 95 | output += "quit - exit console" 96 | 97 | return output 98 | } 99 | 100 | // cpuprof 101 | type CommandCPUProf struct{} 102 | 103 | func (c *CommandCPUProf) name() string { 104 | return "cpuprof" 105 | } 106 | 107 | func (c *CommandCPUProf) help() string { 108 | return "CPU profiling for the current process" 109 | } 110 | 111 | func (c *CommandCPUProf) usage() string { 112 | return "cpuprof writes runtime profiling data in the format expected by \r\n" + 113 | "the pprof visualization tool\r\n\r\n" + 114 | "Usage: cpuprof start|stop\r\n" + 115 | " start - enables CPU profiling\r\n" + 116 | " stop - stops the current CPU profile" 117 | } 118 | 119 | func (c *CommandCPUProf) run(args []string) string { 120 | if len(args) == 0 { 121 | return c.usage() 122 | } 123 | 124 | switch args[0] { 125 | case "start": 126 | fn := profileName() + ".cpuprof" 127 | f, err := os.Create(fn) 128 | if err != nil { 129 | return err.Error() 130 | } 131 | err = pprof.StartCPUProfile(f) 132 | if err != nil { 133 | f.Close() 134 | return err.Error() 135 | } 136 | return fn 137 | case "stop": 138 | pprof.StopCPUProfile() 139 | return "" 140 | default: 141 | return c.usage() 142 | } 143 | } 144 | 145 | func profileName() string { 146 | now := time.Now() 147 | return path.Join(conf.ProfilePath, 148 | fmt.Sprintf("%d%02d%02d_%02d_%02d_%02d", 149 | now.Year(), 150 | now.Month(), 151 | now.Day(), 152 | now.Hour(), 153 | now.Minute(), 154 | now.Second())) 155 | } 156 | 157 | // prof 158 | type CommandProf struct{} 159 | 160 | func (c *CommandProf) name() string { 161 | return "prof" 162 | } 163 | 164 | func (c *CommandProf) help() string { 165 | return "writes a pprof-formatted snapshot" 166 | } 167 | 168 | func (c *CommandProf) usage() string { 169 | return "prof writes runtime profiling data in the format expected by \r\n" + 170 | "the pprof visualization tool\r\n\r\n" + 171 | "Usage: prof goroutine|heap|thread|block\r\n" + 172 | " goroutine - stack traces of all current goroutines\r\n" + 173 | " heap - a sampling of all heap allocations\r\n" + 174 | " thread - stack traces that led to the creation of new OS threads\r\n" + 175 | " block - stack traces that led to blocking on synchronization primitives" 176 | } 177 | 178 | func (c *CommandProf) run(args []string) string { 179 | if len(args) == 0 { 180 | return c.usage() 181 | } 182 | 183 | var ( 184 | p *pprof.Profile 185 | fn string 186 | ) 187 | switch args[0] { 188 | case "goroutine": 189 | p = pprof.Lookup("goroutine") 190 | fn = profileName() + ".gprof" 191 | case "heap": 192 | p = pprof.Lookup("heap") 193 | fn = profileName() + ".hprof" 194 | case "thread": 195 | p = pprof.Lookup("threadcreate") 196 | fn = profileName() + ".tprof" 197 | case "block": 198 | p = pprof.Lookup("block") 199 | fn = profileName() + ".bprof" 200 | default: 201 | return c.usage() 202 | } 203 | 204 | f, err := os.Create(fn) 205 | if err != nil { 206 | return err.Error() 207 | } 208 | defer f.Close() 209 | err = p.WriteTo(f, 0) 210 | if err != nil { 211 | return err.Error() 212 | } 213 | 214 | return fn 215 | } 216 | -------------------------------------------------------------------------------- /network/protobuf/protobuf.go: -------------------------------------------------------------------------------- 1 | package protobuf 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "github.com/golang/protobuf/proto" 8 | "github.com/name5566/leaf/chanrpc" 9 | "github.com/name5566/leaf/log" 10 | "math" 11 | "reflect" 12 | ) 13 | 14 | // ------------------------- 15 | // | id | protobuf message | 16 | // ------------------------- 17 | type Processor struct { 18 | littleEndian bool 19 | msgInfo []*MsgInfo 20 | msgID map[reflect.Type]uint16 21 | } 22 | 23 | type MsgInfo struct { 24 | msgType reflect.Type 25 | msgRouter *chanrpc.Server 26 | msgHandler MsgHandler 27 | msgRawHandler MsgHandler 28 | } 29 | 30 | type MsgHandler func([]interface{}) 31 | 32 | type MsgRaw struct { 33 | msgID uint16 34 | msgRawData []byte 35 | } 36 | 37 | func NewProcessor() *Processor { 38 | p := new(Processor) 39 | p.littleEndian = false 40 | p.msgID = make(map[reflect.Type]uint16) 41 | return p 42 | } 43 | 44 | // It's dangerous to call the method on routing or marshaling (unmarshaling) 45 | func (p *Processor) SetByteOrder(littleEndian bool) { 46 | p.littleEndian = littleEndian 47 | } 48 | 49 | // It's dangerous to call the method on routing or marshaling (unmarshaling) 50 | func (p *Processor) Register(msg proto.Message) uint16 { 51 | msgType := reflect.TypeOf(msg) 52 | if msgType == nil || msgType.Kind() != reflect.Ptr { 53 | log.Fatal("protobuf message pointer required") 54 | } 55 | if _, ok := p.msgID[msgType]; ok { 56 | log.Fatal("message %s is already registered", msgType) 57 | } 58 | if len(p.msgInfo) >= math.MaxUint16 { 59 | log.Fatal("too many protobuf messages (max = %v)", math.MaxUint16) 60 | } 61 | 62 | i := new(MsgInfo) 63 | i.msgType = msgType 64 | p.msgInfo = append(p.msgInfo, i) 65 | id := uint16(len(p.msgInfo) - 1) 66 | p.msgID[msgType] = id 67 | return id 68 | } 69 | 70 | // It's dangerous to call the method on routing or marshaling (unmarshaling) 71 | func (p *Processor) SetRouter(msg proto.Message, msgRouter *chanrpc.Server) { 72 | msgType := reflect.TypeOf(msg) 73 | id, ok := p.msgID[msgType] 74 | if !ok { 75 | log.Fatal("message %s not registered", msgType) 76 | } 77 | 78 | p.msgInfo[id].msgRouter = msgRouter 79 | } 80 | 81 | // It's dangerous to call the method on routing or marshaling (unmarshaling) 82 | func (p *Processor) SetHandler(msg proto.Message, msgHandler MsgHandler) { 83 | msgType := reflect.TypeOf(msg) 84 | id, ok := p.msgID[msgType] 85 | if !ok { 86 | log.Fatal("message %s not registered", msgType) 87 | } 88 | 89 | p.msgInfo[id].msgHandler = msgHandler 90 | } 91 | 92 | // It's dangerous to call the method on routing or marshaling (unmarshaling) 93 | func (p *Processor) SetRawHandler(id uint16, msgRawHandler MsgHandler) { 94 | if id >= uint16(len(p.msgInfo)) { 95 | log.Fatal("message id %v not registered", id) 96 | } 97 | 98 | p.msgInfo[id].msgRawHandler = msgRawHandler 99 | } 100 | 101 | // goroutine safe 102 | func (p *Processor) Route(msg interface{}, userData interface{}) error { 103 | // raw 104 | if msgRaw, ok := msg.(MsgRaw); ok { 105 | if msgRaw.msgID >= uint16(len(p.msgInfo)) { 106 | return fmt.Errorf("message id %v not registered", msgRaw.msgID) 107 | } 108 | i := p.msgInfo[msgRaw.msgID] 109 | if i.msgRawHandler != nil { 110 | i.msgRawHandler([]interface{}{msgRaw.msgID, msgRaw.msgRawData, userData}) 111 | } 112 | return nil 113 | } 114 | 115 | // protobuf 116 | msgType := reflect.TypeOf(msg) 117 | id, ok := p.msgID[msgType] 118 | if !ok { 119 | return fmt.Errorf("message %s not registered", msgType) 120 | } 121 | i := p.msgInfo[id] 122 | if i.msgHandler != nil { 123 | i.msgHandler([]interface{}{msg, userData}) 124 | } 125 | if i.msgRouter != nil { 126 | i.msgRouter.Go(msgType, msg, userData) 127 | } 128 | return nil 129 | } 130 | 131 | // goroutine safe 132 | func (p *Processor) Unmarshal(data []byte) (interface{}, error) { 133 | if len(data) < 2 { 134 | return nil, errors.New("protobuf data too short") 135 | } 136 | 137 | // id 138 | var id uint16 139 | if p.littleEndian { 140 | id = binary.LittleEndian.Uint16(data) 141 | } else { 142 | id = binary.BigEndian.Uint16(data) 143 | } 144 | if id >= uint16(len(p.msgInfo)) { 145 | return nil, fmt.Errorf("message id %v not registered", id) 146 | } 147 | 148 | // msg 149 | i := p.msgInfo[id] 150 | if i.msgRawHandler != nil { 151 | return MsgRaw{id, data[2:]}, nil 152 | } else { 153 | msg := reflect.New(i.msgType.Elem()).Interface() 154 | return msg, proto.UnmarshalMerge(data[2:], msg.(proto.Message)) 155 | } 156 | } 157 | 158 | // goroutine safe 159 | func (p *Processor) Marshal(msg interface{}) ([][]byte, error) { 160 | msgType := reflect.TypeOf(msg) 161 | 162 | // id 163 | _id, ok := p.msgID[msgType] 164 | if !ok { 165 | err := fmt.Errorf("message %s not registered", msgType) 166 | return nil, err 167 | } 168 | 169 | id := make([]byte, 2) 170 | if p.littleEndian { 171 | binary.LittleEndian.PutUint16(id, _id) 172 | } else { 173 | binary.BigEndian.PutUint16(id, _id) 174 | } 175 | 176 | // data 177 | data, err := proto.Marshal(msg.(proto.Message)) 178 | return [][]byte{id, data}, err 179 | } 180 | 181 | // goroutine safe 182 | func (p *Processor) Range(f func(id uint16, t reflect.Type)) { 183 | for id, i := range p.msgInfo { 184 | f(uint16(id), i.msgType) 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /recordfile/recordfile.go: -------------------------------------------------------------------------------- 1 | package recordfile 2 | 3 | import ( 4 | "encoding/csv" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "os" 9 | "reflect" 10 | "strconv" 11 | ) 12 | 13 | var Comma = '\t' 14 | var Comment = '#' 15 | 16 | type Index map[interface{}]interface{} 17 | 18 | type RecordFile struct { 19 | Comma rune 20 | Comment rune 21 | typeRecord reflect.Type 22 | records []interface{} 23 | indexes []Index 24 | } 25 | 26 | func New(st interface{}) (*RecordFile, error) { 27 | typeRecord := reflect.TypeOf(st) 28 | if typeRecord == nil || typeRecord.Kind() != reflect.Struct { 29 | return nil, errors.New("st must be a struct") 30 | } 31 | 32 | for i := 0; i < typeRecord.NumField(); i++ { 33 | f := typeRecord.Field(i) 34 | 35 | kind := f.Type.Kind() 36 | switch kind { 37 | case reflect.Bool: 38 | case reflect.Int: 39 | case reflect.Int8: 40 | case reflect.Int16: 41 | case reflect.Int32: 42 | case reflect.Int64: 43 | case reflect.Uint: 44 | case reflect.Uint8: 45 | case reflect.Uint16: 46 | case reflect.Uint32: 47 | case reflect.Uint64: 48 | case reflect.Float32: 49 | case reflect.Float64: 50 | case reflect.String: 51 | case reflect.Struct: 52 | case reflect.Array: 53 | case reflect.Slice: 54 | case reflect.Map: 55 | default: 56 | return nil, fmt.Errorf("invalid type: %v %s", 57 | f.Name, kind) 58 | } 59 | 60 | tag := f.Tag 61 | if tag == "index" { 62 | switch kind { 63 | case reflect.Struct, reflect.Slice, reflect.Map: 64 | return nil, fmt.Errorf("could not index %s field %v %v", 65 | kind, i, f.Name) 66 | } 67 | } 68 | } 69 | 70 | rf := new(RecordFile) 71 | rf.typeRecord = typeRecord 72 | 73 | return rf, nil 74 | } 75 | 76 | func (rf *RecordFile) Read(name string) error { 77 | file, err := os.Open(name) 78 | if err != nil { 79 | return err 80 | } 81 | defer file.Close() 82 | 83 | if rf.Comma == 0 { 84 | rf.Comma = Comma 85 | } 86 | if rf.Comment == 0 { 87 | rf.Comment = Comment 88 | } 89 | reader := csv.NewReader(file) 90 | reader.Comma = rf.Comma 91 | reader.Comment = rf.Comment 92 | lines, err := reader.ReadAll() 93 | if err != nil { 94 | return err 95 | } 96 | 97 | typeRecord := rf.typeRecord 98 | 99 | // make records 100 | records := make([]interface{}, len(lines)-1) 101 | 102 | // make indexes 103 | indexes := []Index{} 104 | for i := 0; i < typeRecord.NumField(); i++ { 105 | tag := typeRecord.Field(i).Tag 106 | if tag == "index" { 107 | indexes = append(indexes, make(Index)) 108 | } 109 | } 110 | 111 | for n := 1; n < len(lines); n++ { 112 | value := reflect.New(typeRecord) 113 | records[n-1] = value.Interface() 114 | record := value.Elem() 115 | 116 | line := lines[n] 117 | if len(line) != typeRecord.NumField() { 118 | return fmt.Errorf("line %v, field count mismatch: %v (file) %v (st)", 119 | n, len(line), typeRecord.NumField()) 120 | } 121 | 122 | iIndex := 0 123 | 124 | for i := 0; i < typeRecord.NumField(); i++ { 125 | f := typeRecord.Field(i) 126 | 127 | // records 128 | strField := line[i] 129 | field := record.Field(i) 130 | if !field.CanSet() { 131 | continue 132 | } 133 | 134 | var err error 135 | 136 | kind := f.Type.Kind() 137 | if kind == reflect.Bool { 138 | var v bool 139 | v, err = strconv.ParseBool(strField) 140 | if err == nil { 141 | field.SetBool(v) 142 | } 143 | } else if kind == reflect.Int || 144 | kind == reflect.Int8 || 145 | kind == reflect.Int16 || 146 | kind == reflect.Int32 || 147 | kind == reflect.Int64 { 148 | var v int64 149 | v, err = strconv.ParseInt(strField, 0, f.Type.Bits()) 150 | if err == nil { 151 | field.SetInt(v) 152 | } 153 | } else if kind == reflect.Uint || 154 | kind == reflect.Uint8 || 155 | kind == reflect.Uint16 || 156 | kind == reflect.Uint32 || 157 | kind == reflect.Uint64 { 158 | var v uint64 159 | v, err = strconv.ParseUint(strField, 0, f.Type.Bits()) 160 | if err == nil { 161 | field.SetUint(v) 162 | } 163 | } else if kind == reflect.Float32 || 164 | kind == reflect.Float64 { 165 | var v float64 166 | v, err = strconv.ParseFloat(strField, f.Type.Bits()) 167 | if err == nil { 168 | field.SetFloat(v) 169 | } 170 | } else if kind == reflect.String { 171 | field.SetString(strField) 172 | } else if kind == reflect.Struct || 173 | kind == reflect.Array || 174 | kind == reflect.Slice || 175 | kind == reflect.Map { 176 | err = json.Unmarshal([]byte(strField), field.Addr().Interface()) 177 | } 178 | 179 | if err != nil { 180 | return fmt.Errorf("parse field (row=%v, col=%v) error: %v", 181 | n, i, err) 182 | } 183 | 184 | // indexes 185 | if f.Tag == "index" { 186 | index := indexes[iIndex] 187 | iIndex++ 188 | if _, ok := index[field.Interface()]; ok { 189 | return fmt.Errorf("index error: duplicate at (row=%v, col=%v)", 190 | n, i) 191 | } 192 | index[field.Interface()] = records[n-1] 193 | } 194 | } 195 | } 196 | 197 | rf.records = records 198 | rf.indexes = indexes 199 | 200 | return nil 201 | } 202 | 203 | func (rf *RecordFile) Record(i int) interface{} { 204 | return rf.records[i] 205 | } 206 | 207 | func (rf *RecordFile) NumRecord() int { 208 | return len(rf.records) 209 | } 210 | 211 | func (rf *RecordFile) Indexes(i int) Index { 212 | if i >= len(rf.indexes) { 213 | return nil 214 | } 215 | return rf.indexes[i] 216 | } 217 | 218 | func (rf *RecordFile) Index(i interface{}) interface{} { 219 | index := rf.Indexes(0) 220 | if index == nil { 221 | return nil 222 | } 223 | return index[i] 224 | } 225 | -------------------------------------------------------------------------------- /timer/cronexpr.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | // reference: https://github.com/robfig/cron 4 | import ( 5 | "fmt" 6 | "math" 7 | "strconv" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | // Field name | Mandatory? | Allowed values | Allowed special characters 13 | // ---------- | ---------- | -------------- | -------------------------- 14 | // Seconds | No | 0-59 | * / , - 15 | // Minutes | Yes | 0-59 | * / , - 16 | // Hours | Yes | 0-23 | * / , - 17 | // Day of month | Yes | 1-31 | * / , - 18 | // Month | Yes | 1-12 | * / , - 19 | // Day of week | Yes | 0-6 | * / , - 20 | type CronExpr struct { 21 | sec uint64 22 | min uint64 23 | hour uint64 24 | dom uint64 25 | month uint64 26 | dow uint64 27 | } 28 | 29 | // goroutine safe 30 | func NewCronExpr(expr string) (cronExpr *CronExpr, err error) { 31 | fields := strings.Fields(expr) 32 | if len(fields) != 5 && len(fields) != 6 { 33 | err = fmt.Errorf("invalid expr %v: expected 5 or 6 fields, got %v", expr, len(fields)) 34 | return 35 | } 36 | 37 | if len(fields) == 5 { 38 | fields = append([]string{"0"}, fields...) 39 | } 40 | 41 | cronExpr = new(CronExpr) 42 | // Seconds 43 | cronExpr.sec, err = parseCronField(fields[0], 0, 59) 44 | if err != nil { 45 | goto onError 46 | } 47 | // Minutes 48 | cronExpr.min, err = parseCronField(fields[1], 0, 59) 49 | if err != nil { 50 | goto onError 51 | } 52 | // Hours 53 | cronExpr.hour, err = parseCronField(fields[2], 0, 23) 54 | if err != nil { 55 | goto onError 56 | } 57 | // Day of month 58 | cronExpr.dom, err = parseCronField(fields[3], 1, 31) 59 | if err != nil { 60 | goto onError 61 | } 62 | // Month 63 | cronExpr.month, err = parseCronField(fields[4], 1, 12) 64 | if err != nil { 65 | goto onError 66 | } 67 | // Day of week 68 | cronExpr.dow, err = parseCronField(fields[5], 0, 6) 69 | if err != nil { 70 | goto onError 71 | } 72 | return 73 | 74 | onError: 75 | err = fmt.Errorf("invalid expr %v: %v", expr, err) 76 | return 77 | } 78 | 79 | // 1. * 80 | // 2. num 81 | // 3. num-num 82 | // 4. */num 83 | // 5. num/num (means num-max/num) 84 | // 6. num-num/num 85 | func parseCronField(field string, min int, max int) (cronField uint64, err error) { 86 | fields := strings.Split(field, ",") 87 | for _, field := range fields { 88 | rangeAndIncr := strings.Split(field, "/") 89 | if len(rangeAndIncr) > 2 { 90 | err = fmt.Errorf("too many slashes: %v", field) 91 | return 92 | } 93 | 94 | // range 95 | startAndEnd := strings.Split(rangeAndIncr[0], "-") 96 | if len(startAndEnd) > 2 { 97 | err = fmt.Errorf("too many hyphens: %v", rangeAndIncr[0]) 98 | return 99 | } 100 | 101 | var start, end int 102 | if startAndEnd[0] == "*" { 103 | if len(startAndEnd) != 1 { 104 | err = fmt.Errorf("invalid range: %v", rangeAndIncr[0]) 105 | return 106 | } 107 | start = min 108 | end = max 109 | } else { 110 | // start 111 | start, err = strconv.Atoi(startAndEnd[0]) 112 | if err != nil { 113 | err = fmt.Errorf("invalid range: %v", rangeAndIncr[0]) 114 | return 115 | } 116 | // end 117 | if len(startAndEnd) == 1 { 118 | if len(rangeAndIncr) == 2 { 119 | end = max 120 | } else { 121 | end = start 122 | } 123 | } else { 124 | end, err = strconv.Atoi(startAndEnd[1]) 125 | if err != nil { 126 | err = fmt.Errorf("invalid range: %v", rangeAndIncr[0]) 127 | return 128 | } 129 | } 130 | } 131 | 132 | if start > end { 133 | err = fmt.Errorf("invalid range: %v", rangeAndIncr[0]) 134 | return 135 | } 136 | if start < min { 137 | err = fmt.Errorf("out of range [%v, %v]: %v", min, max, rangeAndIncr[0]) 138 | return 139 | } 140 | if end > max { 141 | err = fmt.Errorf("out of range [%v, %v]: %v", min, max, rangeAndIncr[0]) 142 | return 143 | } 144 | 145 | // increment 146 | var incr int 147 | if len(rangeAndIncr) == 1 { 148 | incr = 1 149 | } else { 150 | incr, err = strconv.Atoi(rangeAndIncr[1]) 151 | if err != nil { 152 | err = fmt.Errorf("invalid increment: %v", rangeAndIncr[1]) 153 | return 154 | } 155 | if incr <= 0 { 156 | err = fmt.Errorf("invalid increment: %v", rangeAndIncr[1]) 157 | return 158 | } 159 | } 160 | 161 | // cronField 162 | if incr == 1 { 163 | cronField |= ^(math.MaxUint64 << uint(end+1)) & (math.MaxUint64 << uint(start)) 164 | } else { 165 | for i := start; i <= end; i += incr { 166 | cronField |= 1 << uint(i) 167 | } 168 | } 169 | } 170 | 171 | return 172 | } 173 | 174 | func (e *CronExpr) matchDay(t time.Time) bool { 175 | // day-of-month blank 176 | if e.dom == 0xfffffffe { 177 | return 1< year+1 { 200 | return time.Time{} 201 | } 202 | 203 | // Month 204 | for 1< function 15 | // 16 | // function: 17 | // func(args []interface{}) 18 | // func(args []interface{}) interface{} 19 | // func(args []interface{}) []interface{} 20 | functions map[interface{}]interface{} 21 | ChanCall chan *CallInfo 22 | } 23 | 24 | type CallInfo struct { 25 | f interface{} 26 | args []interface{} 27 | chanRet chan *RetInfo 28 | cb interface{} 29 | } 30 | 31 | type RetInfo struct { 32 | // nil 33 | // interface{} 34 | // []interface{} 35 | ret interface{} 36 | err error 37 | // callback: 38 | // func(err error) 39 | // func(ret interface{}, err error) 40 | // func(ret []interface{}, err error) 41 | cb interface{} 42 | } 43 | 44 | type Client struct { 45 | s *Server 46 | chanSyncRet chan *RetInfo 47 | ChanAsynRet chan *RetInfo 48 | pendingAsynCall int 49 | } 50 | 51 | func NewServer(l int) *Server { 52 | s := new(Server) 53 | s.functions = make(map[interface{}]interface{}) 54 | s.ChanCall = make(chan *CallInfo, l) 55 | return s 56 | } 57 | 58 | func assert(i interface{}) []interface{} { 59 | if i == nil { 60 | return nil 61 | } else { 62 | return i.([]interface{}) 63 | } 64 | } 65 | 66 | // you must call the function before calling Open and Go 67 | func (s *Server) Register(id interface{}, f interface{}) { 68 | switch f.(type) { 69 | case func([]interface{}): 70 | case func([]interface{}) interface{}: 71 | case func([]interface{}) []interface{}: 72 | default: 73 | panic(fmt.Sprintf("function id %v: definition of function is invalid", id)) 74 | } 75 | 76 | if _, ok := s.functions[id]; ok { 77 | panic(fmt.Sprintf("function id %v: already registered", id)) 78 | } 79 | 80 | s.functions[id] = f 81 | } 82 | 83 | func (s *Server) ret(ci *CallInfo, ri *RetInfo) (err error) { 84 | if ci.chanRet == nil { 85 | return 86 | } 87 | 88 | defer func() { 89 | if r := recover(); r != nil { 90 | err = r.(error) 91 | } 92 | }() 93 | 94 | ri.cb = ci.cb 95 | ci.chanRet <- ri 96 | return 97 | } 98 | 99 | func (s *Server) exec(ci *CallInfo) (err error) { 100 | defer func() { 101 | if r := recover(); r != nil { 102 | if conf.LenStackBuf > 0 { 103 | buf := make([]byte, conf.LenStackBuf) 104 | l := runtime.Stack(buf, false) 105 | err = fmt.Errorf("%v: %s", r, buf[:l]) 106 | } else { 107 | err = fmt.Errorf("%v", r) 108 | } 109 | 110 | s.ret(ci, &RetInfo{err: fmt.Errorf("%v", r)}) 111 | } 112 | }() 113 | 114 | // execute 115 | switch ci.f.(type) { 116 | case func([]interface{}): 117 | ci.f.(func([]interface{}))(ci.args) 118 | return s.ret(ci, &RetInfo{}) 119 | case func([]interface{}) interface{}: 120 | ret := ci.f.(func([]interface{}) interface{})(ci.args) 121 | return s.ret(ci, &RetInfo{ret: ret}) 122 | case func([]interface{}) []interface{}: 123 | ret := ci.f.(func([]interface{}) []interface{})(ci.args) 124 | return s.ret(ci, &RetInfo{ret: ret}) 125 | } 126 | 127 | panic("bug") 128 | } 129 | 130 | func (s *Server) Exec(ci *CallInfo) { 131 | err := s.exec(ci) 132 | if err != nil { 133 | log.Error("%v", err) 134 | } 135 | } 136 | 137 | // goroutine safe 138 | func (s *Server) Go(id interface{}, args ...interface{}) { 139 | f := s.functions[id] 140 | if f == nil { 141 | return 142 | } 143 | 144 | defer func() { 145 | recover() 146 | }() 147 | 148 | s.ChanCall <- &CallInfo{ 149 | f: f, 150 | args: args, 151 | } 152 | } 153 | 154 | // goroutine safe 155 | func (s *Server) Call0(id interface{}, args ...interface{}) error { 156 | return s.Open(0).Call0(id, args...) 157 | } 158 | 159 | // goroutine safe 160 | func (s *Server) Call1(id interface{}, args ...interface{}) (interface{}, error) { 161 | return s.Open(0).Call1(id, args...) 162 | } 163 | 164 | // goroutine safe 165 | func (s *Server) CallN(id interface{}, args ...interface{}) ([]interface{}, error) { 166 | return s.Open(0).CallN(id, args...) 167 | } 168 | 169 | func (s *Server) Close() { 170 | close(s.ChanCall) 171 | 172 | for ci := range s.ChanCall { 173 | s.ret(ci, &RetInfo{ 174 | err: errors.New("chanrpc server closed"), 175 | }) 176 | } 177 | } 178 | 179 | // goroutine safe 180 | func (s *Server) Open(l int) *Client { 181 | c := NewClient(l) 182 | c.Attach(s) 183 | return c 184 | } 185 | 186 | func NewClient(l int) *Client { 187 | c := new(Client) 188 | c.chanSyncRet = make(chan *RetInfo, 1) 189 | c.ChanAsynRet = make(chan *RetInfo, l) 190 | return c 191 | } 192 | 193 | func (c *Client) Attach(s *Server) { 194 | c.s = s 195 | } 196 | 197 | func (c *Client) call(ci *CallInfo, block bool) (err error) { 198 | defer func() { 199 | if r := recover(); r != nil { 200 | err = r.(error) 201 | } 202 | }() 203 | 204 | if block { 205 | c.s.ChanCall <- ci 206 | } else { 207 | select { 208 | case c.s.ChanCall <- ci: 209 | default: 210 | err = errors.New("chanrpc channel full") 211 | } 212 | } 213 | return 214 | } 215 | 216 | func (c *Client) f(id interface{}, n int) (f interface{}, err error) { 217 | if c.s == nil { 218 | err = errors.New("server not attached") 219 | return 220 | } 221 | 222 | f = c.s.functions[id] 223 | if f == nil { 224 | err = fmt.Errorf("function id %v: function not registered", id) 225 | return 226 | } 227 | 228 | var ok bool 229 | switch n { 230 | case 0: 231 | _, ok = f.(func([]interface{})) 232 | case 1: 233 | _, ok = f.(func([]interface{}) interface{}) 234 | case 2: 235 | _, ok = f.(func([]interface{}) []interface{}) 236 | default: 237 | panic("bug") 238 | } 239 | 240 | if !ok { 241 | err = fmt.Errorf("function id %v: return type mismatch", id) 242 | } 243 | return 244 | } 245 | 246 | func (c *Client) Call0(id interface{}, args ...interface{}) error { 247 | f, err := c.f(id, 0) 248 | if err != nil { 249 | return err 250 | } 251 | 252 | err = c.call(&CallInfo{ 253 | f: f, 254 | args: args, 255 | chanRet: c.chanSyncRet, 256 | }, true) 257 | if err != nil { 258 | return err 259 | } 260 | 261 | ri := <-c.chanSyncRet 262 | return ri.err 263 | } 264 | 265 | func (c *Client) Call1(id interface{}, args ...interface{}) (interface{}, error) { 266 | f, err := c.f(id, 1) 267 | if err != nil { 268 | return nil, err 269 | } 270 | 271 | err = c.call(&CallInfo{ 272 | f: f, 273 | args: args, 274 | chanRet: c.chanSyncRet, 275 | }, true) 276 | if err != nil { 277 | return nil, err 278 | } 279 | 280 | ri := <-c.chanSyncRet 281 | return ri.ret, ri.err 282 | } 283 | 284 | func (c *Client) CallN(id interface{}, args ...interface{}) ([]interface{}, error) { 285 | f, err := c.f(id, 2) 286 | if err != nil { 287 | return nil, err 288 | } 289 | 290 | err = c.call(&CallInfo{ 291 | f: f, 292 | args: args, 293 | chanRet: c.chanSyncRet, 294 | }, true) 295 | if err != nil { 296 | return nil, err 297 | } 298 | 299 | ri := <-c.chanSyncRet 300 | return assert(ri.ret), ri.err 301 | } 302 | 303 | func (c *Client) asynCall(id interface{}, args []interface{}, cb interface{}, n int) { 304 | f, err := c.f(id, n) 305 | if err != nil { 306 | c.ChanAsynRet <- &RetInfo{err: err, cb: cb} 307 | return 308 | } 309 | 310 | err = c.call(&CallInfo{ 311 | f: f, 312 | args: args, 313 | chanRet: c.ChanAsynRet, 314 | cb: cb, 315 | }, false) 316 | if err != nil { 317 | c.ChanAsynRet <- &RetInfo{err: err, cb: cb} 318 | return 319 | } 320 | } 321 | 322 | func (c *Client) AsynCall(id interface{}, _args ...interface{}) { 323 | if len(_args) < 1 { 324 | panic("callback function not found") 325 | } 326 | 327 | args := _args[:len(_args)-1] 328 | cb := _args[len(_args)-1] 329 | 330 | var n int 331 | switch cb.(type) { 332 | case func(error): 333 | n = 0 334 | case func(interface{}, error): 335 | n = 1 336 | case func([]interface{}, error): 337 | n = 2 338 | default: 339 | panic("definition of callback function is invalid") 340 | } 341 | 342 | // too many calls 343 | if c.pendingAsynCall >= cap(c.ChanAsynRet) { 344 | execCb(&RetInfo{err: errors.New("too many calls"), cb: cb}) 345 | return 346 | } 347 | 348 | c.asynCall(id, args, cb, n) 349 | c.pendingAsynCall++ 350 | } 351 | 352 | func execCb(ri *RetInfo) { 353 | defer func() { 354 | if r := recover(); r != nil { 355 | if conf.LenStackBuf > 0 { 356 | buf := make([]byte, conf.LenStackBuf) 357 | l := runtime.Stack(buf, false) 358 | log.Error("%v: %s", r, buf[:l]) 359 | } else { 360 | log.Error("%v", r) 361 | } 362 | } 363 | }() 364 | 365 | // execute 366 | switch ri.cb.(type) { 367 | case func(error): 368 | ri.cb.(func(error))(ri.err) 369 | case func(interface{}, error): 370 | ri.cb.(func(interface{}, error))(ri.ret, ri.err) 371 | case func([]interface{}, error): 372 | ri.cb.(func([]interface{}, error))(assert(ri.ret), ri.err) 373 | default: 374 | panic("bug") 375 | } 376 | return 377 | } 378 | 379 | func (c *Client) Cb(ri *RetInfo) { 380 | c.pendingAsynCall-- 381 | execCb(ri) 382 | } 383 | 384 | func (c *Client) Close() { 385 | for c.pendingAsynCall > 0 { 386 | c.Cb(<-c.ChanAsynRet) 387 | } 388 | } 389 | 390 | func (c *Client) Idle() bool { 391 | return c.pendingAsynCall == 0 392 | } 393 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | Copyright 2014-2020 Name5566. 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /TUTORIAL_ZH.md: -------------------------------------------------------------------------------- 1 | Leaf 游戏服务器框架简介 2 | ================== 3 | 4 | Leaf 是一个由 Go 语言(golang)编写的开发效率和执行效率并重的开源游戏服务器框架。Leaf 适用于各类游戏服务器的开发,包括 H5(HTML5)游戏服务器。 5 | 6 | Leaf 的关注点: 7 | 8 | * 良好的使用体验。Leaf 总是尽可能的提供简洁和易用的接口,尽可能的提升开发的效率 9 | * 稳定性。Leaf 总是尽可能的恢复运行过程中的错误,避免崩溃 10 | * 多核支持。Leaf 通过模块机制和 [leaf/go](https://github.com/name5566/leaf/tree/master/go) 尽可能的利用多核资源,同时又尽量避免各种副作用 11 | * 模块机制。 12 | 13 | Leaf 的模块机制 14 | --------------- 15 | 16 | 一个 Leaf 开发的游戏服务器由多个模块组成(例如 [LeafServer](https://github.com/name5566/leafserver)),模块有以下特点: 17 | 18 | * 每个模块运行在一个单独的 goroutine 中 19 | * 模块间通过一套轻量的 RPC 机制通讯([leaf/chanrpc](https://github.com/name5566/leaf/tree/master/chanrpc)) 20 | 21 | Leaf 不建议在游戏服务器中设计过多的模块。 22 | 23 | 游戏服务器在启动时进行模块的注册,例如: 24 | 25 | ```go 26 | leaf.Run( 27 | game.Module, 28 | gate.Module, 29 | login.Module, 30 | ) 31 | ``` 32 | 33 | 这里按顺序注册了 game、gate、login 三个模块。每个模块都需要实现接口: 34 | 35 | ```go 36 | type Module interface { 37 | OnInit() 38 | OnDestroy() 39 | Run(closeSig chan bool) 40 | } 41 | ``` 42 | 43 | Leaf 首先会在同一个 goroutine 中按模块注册顺序执行模块的 OnInit 方法,等到所有模块 OnInit 方法执行完成后则为每一个模块启动一个 goroutine 并执行模块的 Run 方法。最后,游戏服务器关闭时(Ctrl + C 关闭游戏服务器)将按模块注册相反顺序在同一个 goroutine 中执行模块的 OnDestroy 方法。 44 | 45 | Leaf 源码概览 46 | --------------- 47 | 48 | * leaf/chanrpc 提供了一套基于 channel 的 RPC 机制,用于游戏服务器模块间通讯 49 | * leaf/db 数据库相关,目前支持 [MongoDB](https://www.mongodb.org/) 50 | * leaf/gate 网关模块,负责游戏客户端的接入 51 | * leaf/go 用于创建能够被 Leaf 管理的 goroutine 52 | * leaf/log 日志相关 53 | * leaf/network 网络相关,使用 TCP 和 WebSocket 协议,可自定义消息格式,默认 Leaf 提供了基于 [protobuf](https://developers.google.com/protocol-buffers) 和 JSON 的消息格式 54 | * leaf/recordfile 用于管理游戏数据 55 | * leaf/timer 定时器相关 56 | * leaf/util 辅助库 57 | 58 | 使用 Leaf 开发游戏服务器 59 | --------------- 60 | 61 | [LeafServer](https://github.com/name5566/leafserver) 是一个基于 Leaf 开发的游戏服务器,我们以 LeafServer 作为起点。 62 | 63 | 获取 LeafServer: 64 | 65 | ``` 66 | git clone https://github.com/name5566/leafserver 67 | ``` 68 | 69 | 设置 leafserver 目录到 GOPATH 环境变量后获取 Leaf: 70 | 71 | ``` 72 | go get github.com/name5566/leaf 73 | ``` 74 | 75 | 编译 LeafServer: 76 | 77 | ``` 78 | go install server 79 | ``` 80 | 81 | 如果一切顺利,运行 server 你可以获得以下输出: 82 | 83 | ``` 84 | 2015/08/26 22:11:27 [release] Leaf 1.1.2 starting up 85 | ``` 86 | 87 | 敲击 Ctrl + C 关闭游戏服务器,服务器正常关闭输出: 88 | 89 | ``` 90 | 2015/08/26 22:12:30 [release] Leaf closing down (signal: interrupt) 91 | ``` 92 | 93 | ### Hello Leaf 94 | 95 | 现在,在 LeafServer 的基础上,我们来看看游戏服务器如何接收和处理网络消息。 96 | 97 | 首先定义一个 JSON 格式的消息(protobuf 类似)。打开 LeafServer msg/msg.go 文件可以看到如下代码: 98 | 99 | ```go 100 | package msg 101 | 102 | import ( 103 | "github.com/name5566/leaf/network" 104 | ) 105 | 106 | var Processor network.Processor 107 | 108 | func init() { 109 | 110 | } 111 | ``` 112 | 113 | Processor 为消息的处理器(可由用户自定义),这里我们使用 Leaf 默认提供的 JSON 消息处理器并尝试添加一个名字为 Hello 的消息: 114 | 115 | ```go 116 | package msg 117 | 118 | import ( 119 | "github.com/name5566/leaf/network/json" 120 | ) 121 | 122 | // 使用默认的 JSON 消息处理器(默认还提供了 protobuf 消息处理器) 123 | var Processor = json.NewProcessor() 124 | 125 | func init() { 126 | // 这里我们注册了一个 JSON 消息 Hello 127 | Processor.Register(&Hello{}) 128 | } 129 | 130 | // 一个结构体定义了一个 JSON 消息的格式 131 | // 消息名为 Hello 132 | type Hello struct { 133 | Name string 134 | } 135 | ``` 136 | 137 | 客户端发送到游戏服务器的消息需要通过 gate 模块路由,简而言之,gate 模块决定了某个消息具体交给内部的哪个模块来处理。这里,我们将 Hello 消息路由到 game 模块中。打开 LeafServer gate/router.go,敲入如下代码: 138 | 139 | ```go 140 | package gate 141 | 142 | import ( 143 | "server/game" 144 | "server/msg" 145 | ) 146 | 147 | func init() { 148 | // 这里指定消息 Hello 路由到 game 模块 149 | // 模块间使用 ChanRPC 通讯,消息路由也不例外 150 | msg.Processor.SetRouter(&msg.Hello{}, game.ChanRPC) 151 | } 152 | ``` 153 | 154 | 一切就绪,我们现在可以在 game 模块中处理 Hello 消息了。打开 LeafServer game/internal/handler.go,敲入如下代码: 155 | 156 | ```go 157 | package internal 158 | 159 | import ( 160 | "github.com/name5566/leaf/log" 161 | "github.com/name5566/leaf/gate" 162 | "reflect" 163 | "server/msg" 164 | ) 165 | 166 | func init() { 167 | // 向当前模块(game 模块)注册 Hello 消息的消息处理函数 handleHello 168 | handler(&msg.Hello{}, handleHello) 169 | } 170 | 171 | func handler(m interface{}, h interface{}) { 172 | skeleton.RegisterChanRPC(reflect.TypeOf(m), h) 173 | } 174 | 175 | func handleHello(args []interface{}) { 176 | // 收到的 Hello 消息 177 | m := args[0].(*msg.Hello) 178 | // 消息的发送者 179 | a := args[1].(gate.Agent) 180 | 181 | // 输出收到的消息的内容 182 | log.Debug("hello %v", m.Name) 183 | 184 | // 给发送者回应一个 Hello 消息 185 | a.WriteMsg(&msg.Hello{ 186 | Name: "client", 187 | }) 188 | } 189 | ``` 190 | 191 | 到这里,一个简单的范例就完成了。为了更加清楚的了解消息的格式,我们从 0 编写一个最简单的测试客户端。 192 | 193 | Leaf 中,当选择使用 TCP 协议时,在网络中传输的消息都会使用以下格式: 194 | 195 | ``` 196 | -------------- 197 | | len | data | 198 | -------------- 199 | ``` 200 | 201 | 其中: 202 | 203 | 1. len 表示了 data 部分的长度(字节数)。len 本身也有长度,默认为 2 字节(可配置),len 本身的长度决定了单个消息的最大大小 204 | 2. data 部分使用 JSON 或者 protobuf 编码(也可自定义其他编码方式) 205 | 206 | 测试客户端同样使用 Go 语言编写: 207 | ```go 208 | package main 209 | 210 | import ( 211 | "encoding/binary" 212 | "net" 213 | ) 214 | 215 | func main() { 216 | conn, err := net.Dial("tcp", "127.0.0.1:3563") 217 | if err != nil { 218 | panic(err) 219 | } 220 | 221 | // Hello 消息(JSON 格式) 222 | // 对应游戏服务器 Hello 消息结构体 223 | data := []byte(`{ 224 | "Hello": { 225 | "Name": "leaf" 226 | } 227 | }`) 228 | 229 | // len + data 230 | m := make([]byte, 2+len(data)) 231 | 232 | // 默认使用大端序 233 | binary.BigEndian.PutUint16(m, uint16(len(data))) 234 | 235 | copy(m[2:], data) 236 | 237 | // 发送消息 238 | conn.Write(m) 239 | } 240 | ``` 241 | 242 | 执行此测试客户端,游戏服务器输出: 243 | 244 | ``` 245 | 2015/09/25 07:41:03 [debug ] hello leaf 246 | 2015/09/25 07:41:03 [debug ] read message: read tcp 127.0.0.1:3563->127.0.0.1:54599: wsarecv: An existing connection was forcibly closed by the remote host. 247 | ``` 248 | 249 | 测试客户端发送完消息以后就退出了,此时和游戏服务器的连接断开,相应的,游戏服务器输出连接断开的提示日志(第二条日志,日志的具体内容和 Go 语言版本有关)。 250 | 251 | 除了使用 TCP 协议外,还可以选择使用 WebSocket 协议(例如开发 H5 游戏)。Leaf 可以单独使用 TCP 协议或 WebSocket 协议,也可以同时使用两者,换而言之,服务器可以同时接受 TCP 连接和 WebSocket 连接,对开发者而言消息来自 TCP 还是 WebSocket 是完全透明的。现在,我们来编写一个对应上例的使用 WebSocket 协议的客户端: 252 | ```html 253 | 263 | ``` 264 | 265 | 保存上述代码到某 HTML 文件中并使用(任意支持 WebSocket 协议的)浏览器打开。在打开此 HTML 文件前,首先需要配置一下 LeafServer 的 bin/conf/server.json 文件,增加 WebSocket 监听地址(WSAddr): 266 | ```json 267 | { 268 | "LogLevel": "debug", 269 | "LogPath": "", 270 | "TCPAddr": "127.0.0.1:3563", 271 | "WSAddr": "127.0.0.1:3653", 272 | "MaxConnNum": 20000 273 | } 274 | ``` 275 | 276 | 重启游戏服务器后,方可接受 WebSocket 消息: 277 | 278 | ``` 279 | 2015/09/25 07:50:03 [debug ] hello leaf 280 | ``` 281 | 282 | 在 Leaf 中使用 WebSocket 需要注意的一点是:Leaf 总是发送二进制消息而非文本消息。 283 | 284 | ### Leaf 模块详解 285 | 286 | LeafServer 中包含了 3 个模块,它们分别是: 287 | 288 | * gate 模块,负责游戏客户端的接入 289 | * login 模块,负责登录流程 290 | * game 模块,负责游戏主逻辑 291 | 292 | 一般来说(而非强制规定),从代码结构上,一个 Leaf 模块: 293 | 294 | 1. 放置于一个目录中(例如 game 模块放置于 game 目录中) 295 | 2. 模块的具体实现放置于 internal 包中(例如 game 模块的具体实现放置于 game/internal 包中) 296 | 297 | 每个模块下一般有一个 external.go 的文件,顾名思义表示模块对外暴露的接口,这里以 game 模块的 external.go 文件为例: 298 | 299 | ```go 300 | package game 301 | 302 | import ( 303 | "server/game/internal" 304 | ) 305 | 306 | var ( 307 | // 实例化 game 模块 308 | Module = new(internal.Module) 309 | // 暴露 ChanRPC 310 | ChanRPC = internal.ChanRPC 311 | ) 312 | ``` 313 | 314 | 首先,模块会被实例化,这样才能注册到 Leaf 框架中(详见 LeafServer main.go),另外,模块暴露的 ChanRPC 被用于模块间通讯。 315 | 316 | 进入 game 模块的内部(LeafServer game/internal/module.go): 317 | 318 | ```go 319 | package internal 320 | 321 | import ( 322 | "github.com/name5566/leaf/module" 323 | "server/base" 324 | ) 325 | 326 | var ( 327 | skeleton = base.NewSkeleton() 328 | ChanRPC = skeleton.ChanRPCServer 329 | ) 330 | 331 | type Module struct { 332 | *module.Skeleton 333 | } 334 | 335 | func (m *Module) OnInit() { 336 | m.Skeleton = skeleton 337 | } 338 | 339 | func (m *Module) OnDestroy() { 340 | 341 | } 342 | ``` 343 | 344 | 模块中最关键的就是 skeleton(骨架),skeleton 实现了 Module 接口的 Run 方法并提供了: 345 | 346 | * ChanRPC 347 | * goroutine 348 | * 定时器 349 | 350 | ### Leaf ChanRPC 351 | 352 | 由于 Leaf 中,每个模块跑在独立的 goroutine 上,为了模块间方便的相互调用就有了基于 channel 的 RPC 机制。一个 ChanRPC 需要在游戏服务器初始化的时候进行注册(注册过程不是 goroutine 安全的),例如 LeafServer 中 game 模块注册了 NewAgent 和 CloseAgent 两个 ChanRPC: 353 | 354 | ```go 355 | package internal 356 | 357 | import ( 358 | "github.com/name5566/leaf/gate" 359 | ) 360 | 361 | func init() { 362 | skeleton.RegisterChanRPC("NewAgent", rpcNewAgent) 363 | skeleton.RegisterChanRPC("CloseAgent", rpcCloseAgent) 364 | } 365 | 366 | func rpcNewAgent(args []interface{}) { 367 | 368 | } 369 | 370 | func rpcCloseAgent(args []interface{}) { 371 | 372 | } 373 | ``` 374 | 375 | 使用 skeleton 来注册 ChanRPC。RegisterChanRPC 的第一个参数是 ChanRPC 的名字,第二个参数是 ChanRPC 的实现。这里的 NewAgent 和 CloseAgent 会被 LeafServer 的 gate 模块在连接建立和连接中断时调用。ChanRPC 的调用方有 3 种调用模式: 376 | 377 | 1. 同步模式,调用并等待 ChanRPC 返回 378 | 2. 异步模式,调用并提供回调函数,回调函数会在 ChanRPC 返回后被调用 379 | 3. Go 模式,调用并立即返回,忽略任何返回值和错误 380 | 381 | gate 模块这样调用 game 模块的 NewAgent ChanRPC(这仅仅是一个示例,实际的代码细节复杂的多): 382 | 383 | ```go 384 | game.ChanRPC.Go("NewAgent", a) 385 | ``` 386 | 387 | 这里调用 NewAgent 并传递参数 a,我们在 rpcNewAgent 的参数 args[0] 中可以取到 a(args[1] 表示第二个参数,以此类推)。 388 | 389 | 更加详细的用法可以参考 [leaf/chanrpc](https://github.com/name5566/leaf/blob/master/chanrpc)。需要注意的是,无论封装多么精巧,跨 goroutine 的调用总不能像直接的函数调用那样简单直接,因此除非必要我们不要构建太多的模块,模块间不要太频繁的交互。模块在 Leaf 中被设计出来最主要是用于划分功能而非利用多核,Leaf 认为在模块内按需使用 goroutine 才是多核利用率问题的解决之道。 390 | 391 | ### Leaf Go 392 | 393 | 善用 goroutine 能够充分利用多核资源,Leaf 提供的 Go 机制解决了原生 goroutine 存在的一些问题: 394 | 395 | * 能够恢复 goroutine 运行过程中的错误 396 | * 游戏服务器会等待所有 goroutine 执行结束后才关闭 397 | * 非常方便的获取 goroutine 执行的结果数据 398 | * 在一些特殊场合保证 goroutine 按创建顺序执行 399 | 400 | 我们来看一个例子(可以在 LeafServer 的模块的 OnInit 方法中测试): 401 | 402 | ```go 403 | log.Debug("1") 404 | 405 | // 定义变量 res 接收结果 406 | var res string 407 | 408 | skeleton.Go(func() { 409 | // 这里使用 Sleep 来模拟一个很慢的操作 410 | time.Sleep(1 * time.Second) 411 | 412 | // 假定得到结果 413 | res = "3" 414 | }, func() { 415 | log.Debug(res) 416 | }) 417 | 418 | log.Debug("2") 419 | ``` 420 | 421 | 上面代码执行结果如下: 422 | 423 | ```go 424 | 2015/08/27 20:37:17 [debug ] 1 425 | 2015/08/27 20:37:17 [debug ] 2 426 | 2015/08/27 20:37:18 [debug ] 3 427 | ``` 428 | 429 | 这里的 Go 方法接收 2 个函数作为参数,第一个函数会被放置在一个新创建的 goroutine 中执行,在其执行完成之后,第二个函数会在当前 goroutine 中被执行。由此,我们可以看到变量 res 同一时刻总是只被一个 goroutine 访问,这就避免了同步机制的使用。Go 的设计使得 CPU 得到充分利用,避免操作阻塞当前 goroutine,同时又无需为共享资源同步而忧心。 430 | 431 | 更加详细的用法可以参考 [leaf/go](https://github.com/name5566/leaf/blob/master/go)。 432 | 433 | ### Leaf timer 434 | 435 | Go 语言标准库提供了定时器的支持: 436 | 437 | ```go 438 | func AfterFunc(d Duration, f func()) *Timer 439 | ``` 440 | 441 | AfterFunc 会等待 d 时长后调用 f 函数,这里的 f 函数将在另外一个 goroutine 中执行。Leaf 提供了一个相同的 AfterFunc 函数,相比之下,f 函数在 AfterFunc 的调用 goroutine 中执行,这样就避免了同步机制的使用: 442 | 443 | ```go 444 | skeleton.AfterFunc(5 * time.Second, func() { 445 | // ... 446 | }) 447 | ``` 448 | 449 | 另外,Leaf timer 还支持 [cron 表达式](https://en.wikipedia.org/wiki/Cron),用于实现诸如“每天 9 点执行”、“每周末 6 点执行”的逻辑。 450 | 451 | 更加详细的用法可以参考 [leaf/timer](https://github.com/name5566/leaf/blob/master/timer)。 452 | 453 | ### Leaf log 454 | 455 | Leaf 的 log 系统支持多种日志级别: 456 | 457 | 1. Debug 日志,非关键日志 458 | 2. Release 日志,关键日志 459 | 3. Error 日志,错误日志 460 | 4. Fatal 日志,致命错误日志 461 | 462 | Debug < Release < Error < Fatal(日志级别高低) 463 | 464 | 在 LeafServer 中,bin/conf/server.json 可以配置日志级别,低于配置的日志级别的日志将不会输出。Fatal 日志比较特殊,每次输出 Fatal 日志之后游戏服务器进程就会结束,通常来说,只在游戏服务器初始化失败时使用 Fatal 日志。 465 | 466 | 我们还可以通过配置 LeafServer conf/conf.go 的 LogFlag 来在日志中输出文件名和行号: 467 | 468 | ``` 469 | LogFlag = log.Lshortfile 470 | ``` 471 | 472 | 可用的 LogFlag 见:[https://golang.org/pkg/log/#pkg-constants](https://golang.org/pkg/log/#pkg-constants) 473 | 474 | 475 | 更加详细的用法可以参考 [leaf/log](https://github.com/name5566/leaf/blob/master/log)。 476 | 477 | ### Leaf recordfile 478 | 479 | Leaf 的 recordfile 是基于 CSV 格式(范例见[这里](https://github.com/name5566/leaf/blob/master/recordfile/test.txt))。recordfile 用于管理游戏配置数据。在 LeafServer 中使用 recordfile 非常简单: 480 | 481 | 1. 将 CSV 文件放置于 bin/gamedata 目录中 482 | 2. 在 gamedata 模块中调用函数 readRf 读取 CSV 文件 483 | 484 | 范例: 485 | 486 | ```go 487 | // 确保 bin/gamedata 目录中存在 Test.txt 文件 488 | // 文件名必须和此结构体名称相同(大小写敏感) 489 | // 结构体的一个实例映射 recordfile 中的一行 490 | type Test struct { 491 | // 将第一列按 int 类型解析 492 | // "index" 表明在此列上建立唯一索引 493 | Id int "index" 494 | // 将第二列解析为长度为 4 的整型数组 495 | Arr [4]int 496 | // 将第三列解析为字符串 497 | Str string 498 | } 499 | 500 | // 读取 recordfile Test.txt 到内存中 501 | // RfTest 即为 Test.txt 的内存镜像 502 | var RfTest = readRf(Test{}) 503 | 504 | func init() { 505 | // 按索引查找 506 | // 获取 Test.txt 中 Id 为 1 的那一行 507 | r := RfTest.Index(1) 508 | 509 | if r != nil { 510 | row := r.(*Test) 511 | 512 | // 输出此行的所有列的数据 513 | log.Debug("%v %v %v", row.Id, row.Arr, row.Str) 514 | } 515 | } 516 | ``` 517 | 518 | 更加详细的用法可以参考 [leaf/recordfile](https://github.com/name5566/leaf/blob/master/recordfile)。 519 | 520 | 了解更多 521 | --------------- 522 | 523 | 阅读 Wiki 获取更多的帮助:[https://github.com/name5566/leaf/wiki](https://github.com/name5566/leaf/wiki) 524 | -------------------------------------------------------------------------------- /TUTORIAL_EN.md: -------------------------------------------------------------------------------- 1 | Brief introduction to Leaf 2 | ========================== 3 | 4 | Leaf, written in Go, is a open source game server framework aiming to boost the efficiency both in development and runtime. 5 | 6 | Leaf champions below philosophies: 7 | 8 | * Simple APIs. Leaf tends to provide simple and plain interfaces which are always best for use. 9 | * Self-healing. Leaf always tries to salvage the process from runtime errors instead of leaving it to crash. 10 | * Multi-core support. Leaf utilize its modules and [leaf/go](https://github.com/name5566/leaf/tree/master/go) to make use of CPU resouces at maximum while avoiding varieties of side effects may be caused. 11 | 12 | * Module-based. 13 | 14 | Leaf's Modules 15 | -------------- 16 | 17 | A game server implemented with Leaf may include many modules (e.g. [LeafServer](https://github.com/name5566/leafserver)) which all share below traits: 18 | 19 | * Each module runs inside a separate goroutine 20 | * Modules communicate with one another via a light weight RPC channel([leaf/chanrpc](https://github.com/name5566/leaf/tree/master/chanrpc)) 21 | 22 | Leaf suggests not to take in too many modules in your game server implementation. 23 | 24 | Modules are registered at the beginning of program as below 25 | 26 | ```go 27 | leaf.Run( 28 | game.Module, 29 | gate.Module, 30 | login.Module, 31 | ) 32 | ``` 33 | 34 | The modules of `game`, `gate` and `login` are registered consecutively. They are required to implement a `Module` interface. 35 | 36 | ```go 37 | type Module interface { 38 | OnInit() 39 | OnDestroy() 40 | Run(closeSig chan bool) 41 | } 42 | ``` 43 | 44 | Leaf follows below steps to manage modules: 45 | 46 | 1. Takes turns (FIFO) to register the given modules by calling `OnInit()`' in a parent goroutine 47 | 2. Starts a new goroutine for each module to run `Run()` 48 | 3. When the parent goroutine is being closed (like by a SIGINT), the modules will be unregistered by calling `OnDestroy()` in the reverse order when they get registered. 49 | 50 | Leaf source code directories 51 | ---------------------------- 52 | 53 | * leaf/chanrpc : RPC channel for inter-modules communication 54 | * leaf/db : Database Utilities with [MongoDB](https://www.mongodb.org/) support 55 | * leaf/gate : Gate module that connects to client 56 | * leaf/go : Factory of goroutine that manageable for Leaf 57 | * leaf/log : Logging 58 | * leaf/network : Networking through TCP or WebSocket with a customized message encoding. There are two built-in encodings, [protobuf](https://developers.google.com/protocol-buffers) and JSON. 59 | * leaf/recordfile : To manage game related data. 60 | * leaf/timer : Timer 61 | * leaf/util : Utilities 62 | 63 | How to use Leaf 64 | --------------- 65 | 66 | [LeafServer](https://github.com/name5566/leafserver) is a game server developped with Leaf. Let's start with it. 67 | 68 | Download the source code of LeafServer: 69 | 70 | ``` 71 | git clone https://github.com/name5566/leafserver 72 | ``` 73 | 74 | Download and install leafserver to GOPATH: 75 | 76 | ``` 77 | go get github.com/name5566/leaf 78 | ``` 79 | 80 | Compile LeafServer: 81 | 82 | ``` 83 | go install server 84 | ``` 85 | 86 | Run `server` you will get below screen output if everything is successful. 87 | 88 | ``` 89 | 2015/08/26 22:11:27 [release] Leaf 1.1.2 starting up 90 | ``` 91 | 92 | Press Ctrl + C to terminate the process, you'll see 93 | 94 | ``` 95 | 2015/08/26 22:12:30 [release] Leaf closing down (signal: interrupt) 96 | ``` 97 | 98 | ### Hello Leaf 99 | 100 | Now with the acknowledge of LeafServer, we come to see how server receives and handles messages. 101 | 102 | 103 | Firstly we define a JSON-encoded message(likely the protobuf). Open LeafServer msg/msg.go then you will see below: 104 | 105 | ```go 106 | package msg 107 | 108 | import ( 109 | "github.com/name5566/leaf/network" 110 | ) 111 | 112 | var Processor network.Processor 113 | 114 | func init() { 115 | 116 | } 117 | ``` 118 | 119 | Processor is the message handler. Here we use the handler of JSON, the default message encoding, and create a Hello message. 120 | 121 | ```go 122 | package msg 123 | 124 | import ( 125 | "github.com/name5566/leaf/network/json" 126 | ) 127 | 128 | // Create a JSON Processor(or protobuf if you like) 129 | var Processor = json.NewProcessor() 130 | 131 | func init() { 132 | // Register message Hello 133 | Processor.Register(&Hello{}) 134 | } 135 | 136 | // One struct for one message 137 | // Contains a string member 138 | type Hello struct { 139 | Name string 140 | } 141 | ``` 142 | 143 | Every message sent from client to server will be flown to `gate` module for routing. Just in brief, `gate` determines which message will be handled by which modules. We want to feed `game` module with `Hello` here, so open LeafServer gate/router.go and write below: 144 | 145 | ```go 146 | package gate 147 | 148 | import ( 149 | "server/game" 150 | "server/msg" 151 | ) 152 | 153 | func init() { 154 | // Route Hello to game 155 | // All communication are through ChanRPC including the management messages 156 | msg.Processor.SetRouter(&msg.Hello{}, game.ChanRPC) 157 | } 158 | ``` 159 | 160 | It is ready to handle `Hello` message in `game` module. Open LeafServer game/internal/handler.go and write: 161 | 162 | ```go 163 | package internal 164 | 165 | import ( 166 | "github.com/name5566/leaf/log" 167 | "github.com/name5566/leaf/gate" 168 | "reflect" 169 | "server/msg" 170 | ) 171 | 172 | func init() { 173 | // Register the handler of `Hello` message to `game` module handleHello 174 | handler(&msg.Hello{}, handleHello) 175 | } 176 | 177 | func handler(m interface{}, h interface{}) { 178 | skeleton.RegisterChanRPC(reflect.TypeOf(m), h) 179 | } 180 | 181 | func handleHello(args []interface{}) { 182 | // Send "Hello" 183 | m := args[0].(*msg.Hello) 184 | // The receiver 185 | a := args[1].(gate.Agent) 186 | 187 | // The content of the message 188 | log.Debug("hello %v", m.Name) 189 | 190 | // Reply with a `Hello` 191 | a.WriteMsg(&msg.Hello{ 192 | Name: "client", 193 | }) 194 | } 195 | ``` 196 | 197 | By here we've finished a simplest example for server. Now we will write a client from scratch for testing to understand the message structure better. 198 | 199 | When we choose TCP over the others, the message in transition will be all formated like below: 200 | 201 | ``` 202 | -------------- 203 | | len | data | 204 | -------------- 205 | ``` 206 | 207 | To be more specific: 208 | 209 | 1. len means the size of data by bytes. len itself takes space(2 bytes by default, configurable). The minimum size of len equals the the minimum size of a single message. 210 | 2. data part is encoded with JSON or protobuf (or a customized one) 211 | 212 | Write the client: 213 | ```go 214 | package main 215 | 216 | import ( 217 | "encoding/binary" 218 | "net" 219 | ) 220 | 221 | func main() { 222 | conn, err := net.Dial("tcp", "127.0.0.1:3563") 223 | if err != nil { 224 | panic(err) 225 | } 226 | 227 | // Hello message (JSON-encoded) 228 | // The structure of the message 229 | data := []byte(`{ 230 | "Hello": { 231 | "Name": "leaf" 232 | } 233 | }`) 234 | 235 | // len + data 236 | m := make([]byte, 2+len(data)) 237 | 238 | // BigEndian encoded 239 | binary.BigEndian.PutUint16(m, uint16(len(data))) 240 | 241 | copy(m[2:], data) 242 | 243 | // Send message 244 | conn.Write(m) 245 | } 246 | ``` 247 | 248 | Run the client to send the message, then server will display it as received 249 | 250 | ``` 251 | 2015/09/25 07:41:03 [debug ] hello leaf 252 | 2015/09/25 07:41:03 [debug ] read message: read tcp 127.0.0.1:3563->127.0.0.1:54599: wsarecv: An existing connection was forcibly closed by the remote host. 253 | ``` 254 | 255 | Client will exit after send out the message, and then disconnect with server. Thus server displays the event message of disconnection(the second, the event message might be dependant on the version of Go environment). 256 | 257 | Beside TCP, WebSocket is another choice of protocol and ideal for HTML5 web game. Leaf uses TCP or WebSocket separately or jointly. In other words, server can handle TCP messages and WebSocket messages at the same time. They are "transparent" for developers. From now on we will demonstrate how to use a client based on WebSocket: 258 | ```html 259 | 269 | ``` 270 | 271 | Save above to a HTML file and open it in a browser (with WebSocket support). Before that, we still have to update the configuration for LeafServer in bin/conf/server.json by adding WebSocket listenning address: 272 | ```json 273 | { 274 | "LogLevel": "debug", 275 | "LogPath": "", 276 | "TCPAddr": "127.0.0.1:3563", 277 | "WSAddr": "127.0.0.1:3653", 278 | "MaxConnNum": 20000 279 | } 280 | ``` 281 | 282 | Restart server then we get the first WebSocket message: 283 | 284 | ``` 285 | 2015/09/25 07:50:03 [debug ] hello leaf 286 | ``` 287 | 288 | Please to be noted: Within WebSocket, Leaf always send binary messages rather text messages. 289 | 290 | ### Leaf in details 291 | 292 | LeafServer includes three modules, they are: 293 | 294 | * gate module: for management of connection 295 | * login module: for the user authentication 296 | * game module: for the main business 297 | 298 | The structure of a Leaf module is suggested (but not forced) to: 299 | 300 | 1. Be located in a separate directory 301 | 2. Have its internal implementation located under `./internal` 302 | 3. Have a file external.go to expose its interfaces. For instance of external.go: 303 | 304 | ```go 305 | package game 306 | 307 | import ( 308 | "server/game/internal" 309 | ) 310 | 311 | var ( 312 | // Instantiate game module 313 | Module = new(internal.Module) 314 | // Expose ChanRPC 315 | ChanRPC = internal.ChanRPC 316 | ) 317 | ``` 318 | 319 | Instantiation of game module must be done before its registration to Leaf framework(detailed in LeafServer main.go). Besides ChanRPC needs to be exposed for inter-module communication. 320 | 321 | Enter into game module's internal(LeafServer game/internal/module.go): 322 | 323 | ```go 324 | package internal 325 | 326 | import ( 327 | "github.com/name5566/leaf/module" 328 | "server/base" 329 | ) 330 | 331 | var ( 332 | skeleton = base.NewSkeleton() 333 | ChanRPC = skeleton.ChanRPCServer 334 | ) 335 | 336 | type Module struct { 337 | *module.Skeleton 338 | } 339 | 340 | func (m *Module) OnInit() { 341 | m.Skeleton = skeleton 342 | } 343 | 344 | func (m *Module) OnDestroy() { 345 | 346 | } 347 | ``` 348 | 349 | skeleton is the key which implements `Run()` and provides: 350 | 351 | * ChanRPC 352 | * goroutine 353 | * Timer 354 | 355 | ### Leaf ChanRPC 356 | 357 | Since in Leaf, every module runs in a separate goroutine, a RPC channel is needed to support the communication between modules. The representing object ChanRPC needs to be registered when the game server is being started and actually it is not safe. For example, in LeafServer, game module registers two ChanRPC objects: NewAgent and CloseAgent. 358 | 359 | ```go 360 | package internal 361 | 362 | import ( 363 | "github.com/name5566/leaf/gate" 364 | ) 365 | 366 | func init() { 367 | skeleton.RegisterChanRPC("NewAgent", rpcNewAgent) 368 | skeleton.RegisterChanRPC("CloseAgent", rpcCloseAgent) 369 | } 370 | 371 | func rpcNewAgent(args []interface{}) { 372 | 373 | } 374 | 375 | func rpcCloseAgent(args []interface{}) { 376 | 377 | } 378 | ``` 379 | 380 | skeleton is used to register ChanRPC. RegisterChanRPC's first parameter is the string name of ChanRPC and the second is the function that implements ChanRPC. NewAgent and CloseAgent will be called by gate module respectively when connection is set up or broken. The calling of ChanRPC includes 3 modes: 381 | 382 | 1. Synchronous mode : called waiting for ChanRPC is yielded 383 | 2. Asynchronous mode : called with a callback function where you can handle the returned ChanRPC 384 | 3. "Go mode" : return immediately ignoring any return values or errors 385 | 386 | This is how gate module call game module's NewAgent ChanRPC (This snippet is simplified for demonstration): 387 | 388 | ```go 389 | game.ChanRPC.Go("NewAgent", a) 390 | ``` 391 | 392 | Here NewAgent will be called with a parameter a which can be retrieved from args[0], the rest can be done in the same manner. 393 | 394 | More references are at [leaf/chanrpc](https://github.com/name5566/leaf/blob/master/chanrpc). Please be noted, no matter how delicate the encapsulation is, calling function across goroutines cannot be that straight. Try not to create too many modules and interactions. Modules designed in Leaf are supposed to decouple the businesses from others rather make most use of CPU cores. The correct way to make most use of CPU cores is to use goroutine properly. 395 | 396 | ### Leaf Go 397 | 398 | Use goroutine properly can make better use of CPU cores. Leaf implements its own Go() for below reasons: 399 | 400 | * Errors within goroutine can be handled 401 | * Game server needs to wait for all goroutines' execution 402 | * The results of goroutine can be obtained more easily 403 | * goroutine will follow the order to be exercised. It is very important in some occasion 404 | 405 | Here is an example which can be tested in OnInit() in LeafServer's module. 406 | 407 | ```go 408 | log.Debug("1") 409 | 410 | // Define res to make the result watchable 411 | var res string 412 | 413 | skeleton.Go(func() { 414 | // Simulate a slow operation 415 | time.Sleep(1 * time.Second) 416 | 417 | // res is modified 418 | res = "3" 419 | }, func() { 420 | log.Debug(res) 421 | }) 422 | 423 | log.Debug("2") 424 | ``` 425 | 426 | The result are: 427 | 428 | ```go 429 | 2015/08/27 20:37:17 [debug ] 1 430 | 2015/08/27 20:37:17 [debug ] 2 431 | 2015/08/27 20:37:18 [debug ] 3 432 | ``` 433 | 434 | skeleton.Go() accepts two function parameters, first one will be exercised in a separate goroutine and afterwards the second be exercised within the same goroutine. And res can only be used by one goroutine at one moment so nothing more need to be done for synchronization. This implementation makes CPU can be fully used while no need to block goroutines. It is quite convenient when shared resources are used. 435 | 436 | More references are at [leaf/go](https://github.com/name5566/leaf/blob/master/go)。 437 | 438 | ### Leaf timer 439 | 440 | Go has a built-in implementation in its standard library: 441 | 442 | ```go 443 | func AfterFunc(d Duration, f func()) *Timer 444 | ``` 445 | 446 | AfterFunc() will wait for a duration of d then exercises f() in a separate goroutine. Leaf also implement AfterFunc(), and in this version f() will be exercised but within the same goroutine. It will prevent synchronization from happening. 447 | 448 | ```go 449 | skeleton.AfterFunc(5 * time.Second, func() { 450 | // ... 451 | }) 452 | ``` 453 | 454 | Besides, Leaf timer support [cron expressions](https://en.wikipedia.org/wiki/Cron) to support scheduled jobs like start at 9am daily or Sunday 6pm weekly. 455 | 456 | More references are at [leaf/timer](https://github.com/name5566/leaf/blob/master/timer)。 457 | 458 | ### Leaf log 459 | 460 | Leaf support below log level: 461 | 462 | 1. Debug level: Not critical 463 | 2. Release level: Critical 464 | 3. Error level: Errors 465 | 4. Fatal level: Fatal errors 466 | 467 | Debug < Release < Error < Fatal (In priority level) 468 | 469 | For LeafServer, bin/conf/server.json is used to configure log level which will filter out the lower level log information. Fatal level log is sort of different and comes only when the game server exit. Usually it records the information when the game server is failed to start up. 470 | 471 | Set LogFlag (LeafServer conf/conf.go) to output the file name and the line number: 472 | 473 | ``` 474 | LogFlag = log.Lshortfile 475 | ``` 476 | 477 | LogFlag:[https://golang.org/pkg/log/#pkg-constants](https://golang.org/pkg/log/#pkg-constants) 478 | 479 | 480 | More references are at [leaf/log](https://github.com/name5566/leaf/blob/master/log). 481 | 482 | ### Leaf recordfile 483 | 484 | Leaf recordfile is formatted in CSV([Example](https://github.com/name5566/leaf/blob/master/recordfile/test.txt)). recordfile is to manage the configuration for game. The usage of recordfile in LeafServer is quite simple: 485 | 486 | 1. Create a CSV file under bin/gamedata 487 | 2. Call readRf() to read it in gamedata module 488 | 489 | Samples: 490 | 491 | ```go 492 | // Make sure Test.txt is located in bin/gamedata 493 | // The file name must match the name of the struct, and all characters are case sensitive. 494 | // Every instance of defined struct maps to one specific row in recordfile 495 | type Test struct { 496 | // The type of first column is int 497 | // "index" means this column will be indexed(exclusively) 498 | Id int "index" 499 | // The type of second column is an array of int with a length of 4 500 | Arr [4]int 501 | // The type of third column is string 502 | Str string 503 | } 504 | 505 | // Load recordfile Test.txt into memory 506 | // RfTest is the object that represents Test.txt in memory 507 | var RfTest = readRf(Test{}) 508 | 509 | func init() { 510 | // Search in index 511 | // Fetch the row with id equals 1 in Test.txt 512 | r := RfTest.Index(1) 513 | 514 | if r != nil { 515 | row := r.(*Test) 516 | 517 | // Log this row 518 | log.Debug("%v %v %v", row.Id, row.Arr, row.Str) 519 | } 520 | } 521 | ``` 522 | 523 | Refer to [leaf/recordfile](https://github.com/name5566/leaf/blob/master/recordfile) for more details. 524 | 525 | Learn more 526 | ---------- 527 | 528 | More references are at Wiki [https://github.com/name5566/leaf/wiki](https://github.com/name5566/leaf/wiki) 529 | --------------------------------------------------------------------------------