├── .gitignore ├── LICENSE ├── README.md ├── cham ├── addr.go ├── const.go ├── master.go ├── mq.go ├── service.go ├── service_test.go ├── smain.go ├── timer.go └── timer_test.go ├── etc └── config.json ├── example └── question │ ├── db │ └── db.go │ ├── lobby │ └── lobby.go │ ├── main.go │ ├── model │ └── model.go │ ├── protocol │ ├── protocol.go │ └── protocol_test.go │ ├── room │ ├── room.go │ └── roommanger.go │ ├── test.html │ ├── test │ └── test.html │ ├── usermanager │ ├── user │ │ └── user.go │ └── userManager.go │ └── util │ └── util.go ├── lib ├── database │ ├── db.go │ └── db_test.go ├── filter │ ├── wordfilter.go │ └── wordfilter_test.go ├── helper │ ├── helper.go │ └── helper_test.go ├── lru │ ├── lru.go │ └── lru_test.go └── zset │ ├── skiplist.c │ ├── skiplist.h │ ├── zset.go │ └── zset_test.go └── service ├── debug ├── debug.go └── debug_test.go ├── gate ├── gate.go ├── gate_test.go ├── test.html └── websocket.go ├── log ├── log.go ├── log_test.go └── logservice.go └── multicast ├── channel.go ├── multicast.go └── mutlticase_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 skycrab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cham 2 | Golang lightweight online game framework 3 | 4 | skynet(https://github.com/cloudwu/skynet) 的Golang版本 5 | -------------------------------------------------------------------------------- /cham/addr.go: -------------------------------------------------------------------------------- 1 | package cham 2 | 3 | import ( 4 | "sync/atomic" 5 | ) 6 | 7 | type Address uint32 8 | 9 | var START_ADDR uint32 = 1 10 | 11 | func GenAddr() Address { 12 | return Address(atomic.AddUint32(&START_ADDR, 1)) 13 | } 14 | 15 | func (addr Address) GetService() *Service { 16 | return master.GetService(addr) 17 | } 18 | -------------------------------------------------------------------------------- /cham/const.go: -------------------------------------------------------------------------------- 1 | package cham 2 | 3 | type NULL struct{} 4 | 5 | const ( 6 | PTYPE_GO uint8 = iota //service -> service 7 | PTYPE_MULTICAST //multicast -> service 8 | PTYPE_CLIENT //client -> gate 9 | PTYPE_RESPONSE //gate -> client 10 | ) 11 | 12 | var ( 13 | NULLVALUE = NULL{} 14 | NORET []interface{} 15 | ) 16 | -------------------------------------------------------------------------------- /cham/master.go: -------------------------------------------------------------------------------- 1 | package cham 2 | 3 | import ( 4 | // "runtime" 5 | "strings" 6 | "sync" 7 | ) 8 | 9 | const ( 10 | DEFAULT_SERVICE_SIZE = 64 11 | ) 12 | 13 | var master *Master 14 | 15 | type Master struct { 16 | sync.RWMutex 17 | services map[Address]*Service 18 | nameservices map[string][]*Service 19 | } 20 | 21 | func NewMaster() *Master { 22 | master := new(Master) 23 | master.services = make(map[Address]*Service, DEFAULT_SERVICE_SIZE) 24 | master.nameservices = make(map[string][]*Service, DEFAULT_SERVICE_SIZE) 25 | return master 26 | } 27 | 28 | func (m *Master) Register(s *Service) bool { 29 | m.Lock() 30 | defer m.Unlock() 31 | m.services[s.Addr] = s 32 | if ns, ok := m.nameservices[s.Name]; ok { 33 | ns = append(ns, s) 34 | return false 35 | } else { 36 | ss := []*Service{s} 37 | m.nameservices[s.Name] = ss 38 | return true 39 | } 40 | } 41 | 42 | func (m *Master) Unregister(s *Service) bool { 43 | m.Lock() 44 | defer m.Unlock() 45 | delete(m.services, s.Addr) 46 | nss := m.nameservices[s.Name] 47 | var idx int = -1 48 | for i, ns := range nss { 49 | if ns.Addr == s.Addr { 50 | idx = i 51 | break 52 | } 53 | } 54 | if idx == -1 { 55 | return false 56 | } else { 57 | m.nameservices[s.Name] = append(nss[:idx], nss[idx+1:]...) 58 | return true 59 | } 60 | } 61 | 62 | func (m *Master) GetService(query interface{}) *Service { 63 | switch v := query.(type) { 64 | case Address: 65 | return m.services[v] 66 | case string: 67 | ss := m.nameservices[v] 68 | if len(ss) > 0 { 69 | return ss[0] 70 | } 71 | return nil 72 | case *Service: 73 | return v 74 | default: 75 | panic("not reachable") 76 | } 77 | } 78 | 79 | func (m *Master) UniqueService(name string) *Service { 80 | m.RLock() 81 | ns := m.nameservices[name] 82 | m.RUnlock() 83 | if len(ns) > 1 { 84 | panic("unique service duplicate") 85 | } else if len(ns) == 1 { 86 | return ns[0] 87 | } else { 88 | return nil 89 | } 90 | } 91 | 92 | func (m *Master) AllService() map[Address]*Service { 93 | m.RLock() 94 | defer m.RUnlock() 95 | return master.services 96 | } 97 | 98 | func DumpService() string { 99 | services := master.AllService() 100 | info := make([]string, 0, len(services)) 101 | for _, s := range services { 102 | info = append(info, s.String()) 103 | } 104 | return strings.Join(info, "\r\n") 105 | } 106 | 107 | func init() { 108 | master = NewMaster() 109 | } 110 | -------------------------------------------------------------------------------- /cham/mq.go: -------------------------------------------------------------------------------- 1 | package cham 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | const ( 9 | DEFAULT_QUEUE_SIZE = 64 10 | ) 11 | 12 | type Queue struct { 13 | sync.Mutex 14 | head int 15 | tail int 16 | buf []*Msg 17 | } 18 | 19 | func NewQueue() *Queue { 20 | q := new(Queue) 21 | q.buf = make([]*Msg, DEFAULT_QUEUE_SIZE) 22 | return q 23 | } 24 | 25 | func (q *Queue) String() string { 26 | return fmt.Sprintf("QUEUE[%d-%d]", q.head, q.tail) 27 | } 28 | 29 | func (q *Queue) Length() int { 30 | q.Lock() 31 | head := q.head 32 | tail := q.tail 33 | size := cap(q.buf) 34 | q.Unlock() 35 | 36 | if head <= tail { 37 | return tail - head 38 | } 39 | return tail + size - head 40 | } 41 | 42 | func (q *Queue) Push(msg *Msg) { 43 | q.Lock() 44 | q.buf[q.tail] = msg 45 | q.tail++ 46 | 47 | if q.tail >= cap(q.buf) { 48 | q.tail = 0 49 | } 50 | 51 | if q.head == q.tail { 52 | q.expand() 53 | } 54 | q.Unlock() 55 | } 56 | 57 | func (q *Queue) Pop() (msg *Msg) { 58 | q.Lock() 59 | if q.head != q.tail { 60 | msg = q.buf[q.head] 61 | q.head++ 62 | if q.head >= cap(q.buf) { 63 | q.head = 0 64 | } 65 | } 66 | q.Unlock() 67 | return 68 | } 69 | 70 | func (q *Queue) expand() { 71 | newbuf := make([]*Msg, cap(q.buf)*2) 72 | copy(newbuf, q.buf[q.head:cap(q.buf)]) 73 | q.head = 0 74 | q.tail = cap(q.buf) 75 | q.buf = newbuf 76 | } 77 | -------------------------------------------------------------------------------- /cham/service.go: -------------------------------------------------------------------------------- 1 | package cham 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "sync" 7 | "sync/atomic" 8 | ) 9 | 10 | const ( 11 | DEFAULT_SERVICE_WORKER int = 1 12 | ) 13 | 14 | var ( 15 | serviceMutex *sync.Mutex 16 | ) 17 | 18 | type Dispatch func(session int32, source Address, ptype uint8, args ...interface{}) []interface{} 19 | type Start func(service *Service, args ...interface{}) Dispatch 20 | 21 | type Msg struct { 22 | source Address 23 | session int32 24 | ptype uint8 25 | args interface{} 26 | } 27 | 28 | type Service struct { 29 | session int32 30 | Name string 31 | Addr Address 32 | queue *Queue 33 | closed bool 34 | quit chan struct{} 35 | rlock *sync.Mutex 36 | rcond *sync.Cond 37 | pending map[int32]chan *Msg 38 | dispatchs map[uint8]Dispatch 39 | } 40 | 41 | func Ret(args ...interface{}) []interface{} { 42 | return args 43 | } 44 | 45 | func NewMsg(source Address, session int32, ptype uint8, args interface{}) *Msg { 46 | return &Msg{source, session, ptype, args} 47 | } 48 | 49 | //args[0] is worker number, args[1:] will pass to start 50 | func NewService(name string, start Start, args ...interface{}) *Service { 51 | service := new(Service) 52 | service.session = 0 53 | service.Name = name 54 | service.Addr = GenAddr() 55 | service.queue = NewQueue() 56 | service.closed = false 57 | service.quit = make(chan struct{}) 58 | service.rlock = new(sync.Mutex) 59 | service.rcond = sync.NewCond(service.rlock) 60 | service.pending = make(map[int32]chan *Msg) 61 | 62 | var n int = 1 63 | if len(args) > 0 { 64 | n = args[0].(int) 65 | if n <= 0 { 66 | n = DEFAULT_SERVICE_WORKER 67 | } 68 | args = args[1:] 69 | } 70 | 71 | service.dispatchs = map[uint8]Dispatch{PTYPE_GO: start(service, args...)} 72 | 73 | // start may failed, user can invoke service.Stop(), so check service.closed flag 74 | if service.closed { 75 | return nil 76 | } 77 | master.Register(service) 78 | 79 | for i := 0; i < n; i++ { 80 | go service.Start(i) 81 | } 82 | 83 | return service 84 | } 85 | 86 | //create or return already name 87 | func UniqueService(name string, start Start, args ...interface{}) *Service { 88 | serviceMutex.Lock() 89 | defer serviceMutex.Unlock() 90 | 91 | s := master.UniqueService(name) 92 | if s == nil { 93 | s = NewService(name, start, args...) 94 | } 95 | return s 96 | } 97 | 98 | func (s *Service) Start(i int) { 99 | seq := strconv.Itoa(i) 100 | fmt.Println(s.String(), "-- ", seq, " --", "running") 101 | for { 102 | select { 103 | case <-s.quit: 104 | fmt.Println(s.String(), "-- ", seq, " --", "stop") 105 | return 106 | default: 107 | msg := s.queue.Pop() 108 | if msg == nil { 109 | s.rlock.Lock() 110 | s.rcond.Wait() 111 | s.rlock.Unlock() 112 | } else { 113 | s.dispatchMsg(msg) 114 | } 115 | } 116 | } 117 | } 118 | 119 | func (s *Service) dispatchMsg(msg *Msg) { 120 | if msg.session == 0 { 121 | s.dispatchs[msg.ptype](msg.session, msg.source, msg.ptype, msg.args.([]interface{})...) 122 | } else if msg.session > 0 { 123 | result := s.dispatchs[msg.ptype](msg.session, msg.source, msg.ptype, msg.args.([]interface{})...) 124 | resp := &Msg{s.Addr, -msg.session, msg.ptype, result} 125 | dest := msg.source.GetService() 126 | dest.Push(resp) 127 | } else { 128 | session := -msg.session 129 | done := s.pending[session] 130 | delete(s.pending, session) 131 | done <- msg 132 | } 133 | } 134 | 135 | func (s *Service) RegisterProtocol(ptype uint8, start Start, args ...interface{}) { 136 | if _, ok := s.dispatchs[ptype]; ok { 137 | panic(s.String() + "duplicate register protocol") 138 | } 139 | s.dispatchs[ptype] = start(s, args...) 140 | } 141 | 142 | func (s *Service) send(query interface{}, ptype uint8, session int32, args ...interface{}) chan *Msg { 143 | if session != 0 { 144 | session = atomic.AddInt32(&s.session, 1) 145 | } 146 | msg := &Msg{s.Addr, session, ptype, args} 147 | dest := master.GetService(query) 148 | dest.Push(msg) 149 | var done chan *Msg 150 | if session != 0 { // need reply 151 | done = make(chan *Msg, 1) 152 | s.pending[session] = done 153 | } 154 | 155 | return done 156 | } 157 | 158 | // wait response, query can service name/addr/service 159 | func (s *Service) Call(query interface{}, ptype uint8, args ...interface{}) []interface{} { 160 | m := <-s.send(query, ptype, 1, args...) 161 | return m.args.([]interface{}) 162 | } 163 | 164 | // no reply 165 | func (s *Service) Notify(query interface{}, ptype uint8, args ...interface{}) { 166 | s.send(query, ptype, 0, args...) 167 | } 168 | 169 | func (s *Service) NotifySelf(ptype uint8, args ...interface{}) { 170 | s.send(s, ptype, 0, args...) 171 | } 172 | 173 | // no wait response 174 | func (s *Service) Send(query interface{}, ptype uint8, args ...interface{}) chan *Msg { 175 | return s.send(query, ptype, 1, args...) 176 | } 177 | 178 | func (s *Service) Push(msg *Msg) { 179 | s.queue.Push(msg) 180 | s.rcond.Signal() 181 | } 182 | 183 | func (s *Service) Stop() bool { 184 | if !s.closed { 185 | s.closed = true 186 | close(s.quit) 187 | s.rcond.Signal() 188 | master.Unregister(s) 189 | return true 190 | } 191 | return false 192 | } 193 | 194 | func (s *Service) String() string { 195 | return fmt.Sprintf("SERVICE [addr->%d, name->%s] ", s.Addr, s.Name) 196 | } 197 | 198 | func (s *Service) Status() int { 199 | return s.queue.Length() 200 | } 201 | 202 | func Redirect(source Address, msg *Msg) { 203 | dest := source.GetService() 204 | dest.Push(msg) 205 | } 206 | 207 | func init() { 208 | serviceMutex = new(sync.Mutex) 209 | } 210 | -------------------------------------------------------------------------------- /cham/service_test.go: -------------------------------------------------------------------------------- 1 | package cham 2 | 3 | import ( 4 | "fmt" 5 | // "runtime" 6 | // "sync" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func helloStart(service *Service, args ...interface{}) Dispatch { 12 | return func(session int32, source Address, ptype uint8, args ...interface{}) []interface{} { 13 | fmt.Println(session, source, args) 14 | time.Sleep(time.Second * 4) 15 | cmd := args[0].(string) 16 | if cmd == "Hello" { 17 | return Ret("World") 18 | } else if cmd == "Notify" { 19 | fmt.Println("no reply") 20 | return Ret(nil) 21 | } else { 22 | return Ret("Error") 23 | } 24 | } 25 | } 26 | 27 | func init() { 28 | // runtime.GOMAXPROCS(4) 29 | } 30 | 31 | func worldStart(service *Service, args ...interface{}) Dispatch { 32 | other := args[0].(string) 33 | fmt.Println("worldStart:", other) 34 | return func(session int32, source Address, ptypt uint8, args ...interface{}) []interface{} { 35 | return Ret("999") 36 | } 37 | } 38 | 39 | func TestService(t *testing.T) { 40 | hello := NewService("Hello", helloStart, 4) // 4 worker of goroutine 41 | world := NewService("World", worldStart, 1, "other args") // 1 worker of goroutine, "other args" will pass to worldStart 42 | for i := 0; i < 5; i++ { 43 | // world.Call("Hello", "Hello") 44 | go func() { 45 | fmt.Println(world.Call(hello, PTYPE_GO, "Hello")) // worker < 5 : there is a request will wait 46 | }() 47 | // world.Send(hello, PTYPE_GO, "send") 48 | } 49 | time.Sleep(time.Second * 100) 50 | } 51 | -------------------------------------------------------------------------------- /cham/smain.go: -------------------------------------------------------------------------------- 1 | package cham 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | const ( 8 | CHAM_STOP uint8 = iota 9 | ) 10 | 11 | var Main *Service 12 | 13 | var DTimer *Timer 14 | 15 | var stop chan NULL 16 | 17 | func mainStart(service *Service, args ...interface{}) Dispatch { 18 | return func(session int32, source Address, ptype uint8, args ...interface{}) []interface{} { 19 | switch ptype { 20 | case CHAM_STOP: 21 | stop <- NULLVALUE 22 | } 23 | return NORET 24 | } 25 | } 26 | 27 | func Run() { 28 | <-stop 29 | } 30 | 31 | func init() { 32 | Main = NewService("main", mainStart) 33 | DTimer = NewWheelTimer(time.Millisecond * 10) 34 | go func() { DTimer.Start() }() 35 | } 36 | -------------------------------------------------------------------------------- /cham/timer.go: -------------------------------------------------------------------------------- 1 | package cham 2 | 3 | import ( 4 | "container/list" 5 | "fmt" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | //referer https://github.com/cloudwu/skynet/blob/master/skynet-src/skynet_timer.c 11 | 12 | const ( 13 | TIME_NEAR_SHIFT = 8 14 | TIME_NEAR = 1 << TIME_NEAR_SHIFT 15 | TIME_LEVEL_SHIFT = 6 16 | TIME_LEVEL = 1 << TIME_LEVEL_SHIFT 17 | TIME_NEAR_MASK = TIME_NEAR - 1 18 | TIME_LEVEL_MASK = TIME_LEVEL - 1 19 | ) 20 | 21 | type Timer struct { 22 | near [TIME_NEAR]*list.List 23 | t [4][TIME_LEVEL]*list.List 24 | sync.Mutex 25 | time uint32 26 | tick time.Duration 27 | quit chan struct{} 28 | } 29 | 30 | type Node struct { 31 | expire uint32 32 | period time.Duration 33 | C chan time.Time 34 | } 35 | 36 | func (n *Node) String() string { 37 | return fmt.Sprintf("Node:expire,%d", n.expire) 38 | } 39 | 40 | func NewWheelTimer(d time.Duration) *Timer { 41 | t := new(Timer) 42 | t.time = 0 43 | t.tick = d 44 | t.quit = make(chan struct{}) 45 | 46 | var i, j int 47 | for i = 0; i < TIME_NEAR; i++ { 48 | t.near[i] = list.New() 49 | } 50 | 51 | for i = 0; i < 4; i++ { 52 | for j = 0; j < TIME_LEVEL; j++ { 53 | t.t[i][j] = list.New() 54 | } 55 | } 56 | 57 | return t 58 | } 59 | 60 | func (t *Timer) addNode(n *Node) { 61 | expire := n.expire 62 | current := t.time 63 | if (expire | TIME_NEAR_MASK) == (current | TIME_NEAR_MASK) { 64 | t.near[expire&TIME_NEAR_MASK].PushBack(n) 65 | } else { 66 | var i uint32 67 | var mask uint32 = TIME_NEAR << TIME_LEVEL_SHIFT 68 | for i = 0; i < 3; i++ { 69 | if (expire | (mask - 1)) == (current | (mask - 1)) { 70 | break 71 | } 72 | mask <<= TIME_LEVEL_SHIFT 73 | } 74 | 75 | t.t[i][(expire>>(TIME_NEAR_SHIFT+i*TIME_LEVEL_SHIFT))&TIME_LEVEL_MASK].PushBack(n) 76 | } 77 | 78 | } 79 | 80 | func (t *Timer) NewTicker(d time.Duration) *Node { 81 | n := new(Node) 82 | n.C = make(chan time.Time, 1) 83 | n.period = d 84 | t.Lock() 85 | n.expire = uint32(d/t.tick) + t.time 86 | t.addNode(n) 87 | t.Unlock() 88 | return n 89 | } 90 | 91 | func (t *Timer) NewTimer(d time.Duration) *Node { 92 | n := new(Node) 93 | n.C = make(chan time.Time, 1) 94 | n.period = 0 95 | t.Lock() 96 | n.expire = uint32(d/t.tick) + t.time 97 | t.addNode(n) 98 | t.Unlock() 99 | return n 100 | } 101 | 102 | func (t *Timer) String() string { 103 | return fmt.Sprintf("Timer:time:%d, tick:%s", t.time, t.tick) 104 | } 105 | 106 | func (t *Timer) dispatchList(front *list.Element) { 107 | now := time.Now() 108 | for e := front; e != nil; e = e.Next() { 109 | node := e.Value.(*Node) 110 | select { 111 | case node.C <- now: 112 | default: 113 | } 114 | if node.period > 0 { 115 | t.Lock() 116 | node.expire = uint32(node.period/t.tick) + t.time 117 | t.addNode(node) 118 | t.Unlock() 119 | } 120 | } 121 | } 122 | 123 | func (t *Timer) moveList(level, idx int) { 124 | vec := t.t[level][idx] 125 | front := vec.Front() 126 | vec.Init() 127 | for e := front; e != nil; e = e.Next() { 128 | node := e.Value.(*Node) 129 | t.addNode(node) 130 | } 131 | } 132 | 133 | func (t *Timer) shift() { 134 | t.Lock() 135 | var mask uint32 = TIME_NEAR 136 | t.time++ 137 | ct := t.time 138 | if ct == 0 { 139 | t.moveList(3, 0) 140 | } else { 141 | time := ct >> TIME_NEAR_SHIFT 142 | var i int = 0 143 | for (ct & (mask - 1)) == 0 { 144 | idx := int(time & TIME_LEVEL_MASK) 145 | if idx != 0 { 146 | t.moveList(i, idx) 147 | break 148 | } 149 | mask <<= TIME_LEVEL_SHIFT 150 | time >>= TIME_LEVEL_SHIFT 151 | i++ 152 | } 153 | } 154 | t.Unlock() 155 | } 156 | 157 | func (t *Timer) execute() { 158 | t.Lock() 159 | idx := t.time & TIME_NEAR_MASK 160 | vec := t.near[idx] 161 | if vec.Len() > 0 { 162 | front := vec.Front() 163 | vec.Init() 164 | t.Unlock() 165 | // dispatch_list don't need lock 166 | t.dispatchList(front) 167 | return 168 | } 169 | 170 | t.Unlock() 171 | } 172 | 173 | func (t *Timer) update() { 174 | // try to dispatch timeout 0 (rare condition) 175 | t.execute() 176 | 177 | // shift time first, and then dispatch timer message 178 | t.shift() 179 | 180 | t.execute() 181 | 182 | } 183 | 184 | func (t *Timer) Start() { 185 | tick := time.NewTicker(t.tick) 186 | defer tick.Stop() 187 | for { 188 | select { 189 | case <-tick.C: 190 | t.update() 191 | case <-t.quit: 192 | return 193 | } 194 | } 195 | } 196 | 197 | func (t *Timer) Stop() { 198 | close(t.quit) 199 | } 200 | -------------------------------------------------------------------------------- /cham/timer_test.go: -------------------------------------------------------------------------------- 1 | package cham 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestTimer(t *testing.T) { 10 | timer := NewWheelTimer(time.Millisecond * 10) 11 | t1 := timer.NewTimer(time.Second * 10) 12 | t2 := timer.NewTicker(time.Second * 2) 13 | go timer.Start() 14 | // var now time.Time 15 | fmt.Println(time.Now()) 16 | for { 17 | select { 18 | case <-t1.C: 19 | fmt.Println("t1,", time.Now()) 20 | case <-t2.C: 21 | fmt.Println("t2", time.Now()) 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /etc/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "GOMAXPROCS":8 3 | } -------------------------------------------------------------------------------- /example/question/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "cham/lib/database" 5 | "cham/lib/lru" 6 | "database/sql" 7 | // "fmt" 8 | _ "github.com/go-sql-driver/mysql" 9 | ) 10 | 11 | var DbCache *QueryCache 12 | 13 | type QueryCache struct { 14 | conn *sql.DB 15 | Db *database.Database 16 | queryset map[string]*lru.Cache 17 | expires chan lru.Value 18 | updateModel chan database.Model 19 | } 20 | 21 | func New(conn *sql.DB) *QueryCache { 22 | q := &QueryCache{ 23 | conn: conn, 24 | Db: database.New(conn), 25 | queryset: make(map[string]*lru.Cache), 26 | expires: make(chan lru.Value, 1000), 27 | updateModel: make(chan database.Model, 1000), 28 | } 29 | go q.Run() 30 | return q 31 | } 32 | 33 | func (q *QueryCache) Run() { 34 | for { 35 | select { 36 | case m := <-q.updateModel: 37 | q.Db.UpdatePk(m) 38 | case v := <-q.expires: 39 | q.Db.UpdatePk(v.(database.Model)) 40 | } 41 | } 42 | } 43 | 44 | func (q *QueryCache) UpdateModel(m database.Model) { 45 | q.updateModel <- m 46 | } 47 | 48 | func (q *QueryCache) Register(m database.Model, maxEntries int) { 49 | name := m.TableName() 50 | if _, ok := q.queryset[name]; ok { 51 | panic("duplicate queryset table:" + name) 52 | } 53 | q.queryset[name] = lru.New(maxEntries, func(key lru.Key, value lru.Value) { 54 | q.expires <- value 55 | }) 56 | } 57 | 58 | func (q *QueryCache) GetPk(m database.Model) (database.Model, interface{}, error) { 59 | name := m.TableName() 60 | if _, ok := q.queryset[name]; !ok { 61 | panic("please Register first table:" + name) 62 | } 63 | _, value := database.GetPkValue(m) 64 | cache := q.queryset[name] 65 | v, ok := cache.Get(value) 66 | if !ok { 67 | err := q.Db.GetPk(m) 68 | if err != nil { 69 | return nil, nil, err 70 | } 71 | cache.Add(value, m) 72 | return m, value, nil 73 | } 74 | vv := v.(database.Model) 75 | return vv, value, nil 76 | } 77 | 78 | func init() { 79 | conn, err := sql.Open("mysql", "root:dajidan2bu2@tcp(10.9.28.162:3306)/brain") 80 | if err != nil { 81 | panic("mysql connect error," + err.Error()) 82 | } 83 | DbCache = New(conn) 84 | } 85 | -------------------------------------------------------------------------------- /example/question/lobby/lobby.go: -------------------------------------------------------------------------------- 1 | package lobby 2 | 3 | import ( 4 | "cham/cham" 5 | "cham/service/log" 6 | "fmt" 7 | "question/room" 8 | "sync" 9 | ) 10 | 11 | type Lobby struct { 12 | roomManager room.RoomManager 13 | rooms map[room.Roomid]*cham.Service 14 | } 15 | 16 | func newLobby() *Lobby { 17 | lobby := &Lobby{ 18 | roomManager: room.NewRoomManager(), 19 | rooms: make(map[room.Roomid]*cham.Service), 20 | } 21 | return lobby 22 | } 23 | 24 | func (lobby *Lobby) Allocate() { 25 | roomId := lobby.roomManager.NextRoom() 26 | roomName := fmt.Sprintf("room-%d", int(roomId)) 27 | lobby.rooms[roomId] = cham.NewService(roomName, room.Start, 1, roomId) 28 | } 29 | 30 | func Start(service *cham.Service, args ...interface{}) cham.Dispatch { 31 | log.Infoln("New Service ", lobby.String()) 32 | lobby := newLobby() 33 | return func(session int32, source cham.Address, ptype uint8, args ...interface{}) []interface{} { 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/question/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "cham/cham" 5 | "cham/service/debug" 6 | "cham/service/gate" 7 | "cham/service/log" 8 | "fmt" 9 | // "question/lobby" 10 | "question/protocol" 11 | "question/usermanager" 12 | "question/usermanager/user" 13 | ) 14 | 15 | func brokerStart(service *cham.Service, args ...interface{}) cham.Dispatch { 16 | log.Infoln("New Service ", service.String()) 17 | um := args[0].(*usermanager.UserManager) 18 | return func(session int32, source cham.Address, ptype uint8, args ...interface{}) []interface{} { 19 | cmd := args[0].(uint8) 20 | switch cmd { 21 | case user.DELETE_USER: 22 | openid := args[1].(string) 23 | um.Delete(openid) 24 | } 25 | return cham.NORET 26 | } 27 | } 28 | 29 | func brokerDispatch(service *cham.Service, args ...interface{}) cham.Dispatch { 30 | um := args[0].(*usermanager.UserManager) 31 | return func(session int32, source cham.Address, ptype uint8, args ...interface{}) []interface{} { 32 | sessionid := args[0].(uint32) 33 | gt := args[1].(uint8) 34 | switch gt { 35 | // case gate.OnOpen: 36 | // fmt.Println("OnOpen ", sessionid) 37 | // case gate.OnClose: 38 | // fmt.Println("OnClose ", sessionid, args[2:]) 39 | // case gate.OnPong: 40 | // fmt.Println("OnPong ", sessionid, args[2]) 41 | case gate.OnMessage: 42 | data := args[2].([]byte) 43 | fmt.Println("OnMessage", sessionid, string(data)) 44 | name, request := protocol.Decode(data) 45 | um.Handle(sessionid, name, request) 46 | } 47 | return cham.NORET 48 | } 49 | } 50 | 51 | func main() { 52 | gs := cham.NewService("gate", gate.Start, 8) 53 | um := usermanager.New() 54 | bs := cham.NewService("broker", brokerStart, 8, um) 55 | bs.RegisterProtocol(cham.PTYPE_CLIENT, brokerDispatch, um) 56 | bs.Call(gs, cham.PTYPE_GO, gate.OPEN, gate.NewConf("127.0.0.1:9998", 0, "/ws")) 57 | cham.NewService("debug", debug.Start, 1, "127.0.0.1:8888") 58 | // lobby := cham.NewService("lobby", lobby.Start) 59 | cham.Run() 60 | } 61 | -------------------------------------------------------------------------------- /example/question/model/model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "question/db" 5 | "time" 6 | ) 7 | 8 | //UserModel 9 | type UserModel struct { 10 | ID int `json:"id"` 11 | Openid string `json:"openid" pk:"true"` 12 | Name string `json:"name"` 13 | Headimgurl string `json:"headimgurl"` 14 | Sex int `json:"sex"` 15 | LastLogin time.Time `json:"lastlogin" field:"last_login"` 16 | } 17 | 18 | func (u *UserModel) TableName() string { 19 | return "question_user" 20 | } 21 | 22 | //register all model 23 | func init() { 24 | dc := db.DbCache 25 | dc.Register(&UserModel{}, 1) 26 | } 27 | -------------------------------------------------------------------------------- /example/question/protocol/protocol.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "cham/service/log" 5 | "encoding/json" 6 | "reflect" 7 | "strings" 8 | ) 9 | 10 | var protocols = make(map[string]reflect.Type) 11 | 12 | //request head 13 | type Request struct { 14 | Service string `json:"service"` 15 | Data json.RawMessage `json:"data"` 16 | } 17 | 18 | //response head 19 | type Response struct { 20 | Code int `json:"code"` // 0 correct 21 | Service string `json:"service"` 22 | Result interface{} `json:"result"` 23 | } 24 | 25 | // 26 | // 27 | //concrete protocol 28 | 29 | type Login struct { 30 | Openid string `json:"openid"` 31 | } 32 | 33 | //to common, websocket and tcp use the same 34 | type HeartBeat struct { 35 | } 36 | 37 | func Decode(data []byte) (string, interface{}) { 38 | request := &Request{} 39 | err := json.Unmarshal(data, request) 40 | if err != nil { 41 | log.Errorln("Request Decode Header error:", err.Error()) 42 | return "", nil 43 | } 44 | if p, ok := protocols[request.Service]; ok { 45 | np := reflect.New(p).Interface() 46 | err := json.Unmarshal(request.Data, np) 47 | if err != nil { 48 | log.Errorln("Request Decode ", request.Service, "error:", err.Error()) 49 | } else { 50 | return request.Service, np 51 | } 52 | } 53 | return "", nil 54 | 55 | } 56 | 57 | //result can ptr or value 58 | func Encode(code int, result interface{}) []byte { 59 | t := reflect.TypeOf(result) 60 | if t.Kind() == reflect.Ptr { 61 | t = t.Elem() 62 | } 63 | response := Response{code, strings.ToLower(t.Name()), result} 64 | b, e := json.Marshal(response) 65 | if e != nil { 66 | log.Errorln("Response Encode error, ", e.Error()) 67 | return nil 68 | } 69 | return b 70 | } 71 | 72 | func register(p interface{}) { 73 | v := reflect.ValueOf(p) 74 | name := strings.ToLower(v.Elem().Type().Name()) 75 | protocols[name] = v.Type().Elem() 76 | } 77 | 78 | func init() { 79 | protos := []interface{}{&Login{}} 80 | for _, p := range protos { 81 | register(p) 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /example/question/protocol/protocol_test.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestProto(t *testing.T) { 9 | d := []byte(`{"service":"user","data":{"openid":"lwy"}}`) 10 | fmt.Println(Decode(d)) 11 | } 12 | -------------------------------------------------------------------------------- /example/question/room/room.go: -------------------------------------------------------------------------------- 1 | package room 2 | 3 | import ( 4 | "cham/cham" 5 | "cham/service/log" 6 | "question/model" 7 | ) 8 | 9 | type Room struct { 10 | rid Roomid 11 | owner *user.User 12 | players []*user.User 13 | } 14 | 15 | func newRoom(rid Roomid) *Room { 16 | return &Room{ 17 | rid: rid, 18 | owner: nil, 19 | players: make([]*user.User, 0, 2), 20 | } 21 | } 22 | 23 | func Start(service *cham.Service, args ...interface{}) cham.Dispatch { 24 | log.Infoln("New Service ", room.String()) 25 | room := newRoom(args[0].(Roomid)) 26 | return func(session int32, source cham.Address, ptype uint8, args ...interface{}) []interface{} { 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/question/room/roommanger.go: -------------------------------------------------------------------------------- 1 | package room 2 | 3 | import ( 4 | "sync/atomic" 5 | ) 6 | 7 | type Roomid uint32 8 | 9 | type RoomManager struct { 10 | current Roomid 11 | } 12 | 13 | func NewRoomManager() *RoomManager { 14 | return &RoomManager{0} 15 | } 16 | 17 | func (rm *RoomManager) NextRoom() Roomid { 18 | return Roomid(atomic.AddUint32(&rm.current, 1)) 19 | } 20 | -------------------------------------------------------------------------------- /example/question/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebSocket example 5 | 6 | 7 | 29 | 30 | -------------------------------------------------------------------------------- /example/question/test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebSocket example 5 | 6 | 7 | 27 | 28 | -------------------------------------------------------------------------------- /example/question/usermanager/user/user.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "cham/cham" 5 | "cham/service/log" 6 | // "fmt" 7 | "question/db" 8 | "question/model" 9 | "question/protocol" 10 | "time" 11 | ) 12 | 13 | const ( 14 | CHANGE_SESSION uint8 = iota 15 | DELETE_USER 16 | ) 17 | 18 | //user 19 | type User struct { 20 | *model.UserModel 21 | service *cham.Service 22 | session uint32 23 | activeTime time.Time 24 | } 25 | 26 | func newUser(service *cham.Service, u *model.UserModel, session uint32) (*User, error) { 27 | m, _, err := db.DbCache.GetPk(u) 28 | if err != nil { 29 | return nil, err 30 | } 31 | user := &User{m.(*model.UserModel), service, session, time.Now()} 32 | go user.Run() 33 | return user, nil 34 | } 35 | 36 | func (user *User) Save() { 37 | db.DbCache.UpdateModel(user.UserModel) 38 | } 39 | 40 | func (user *User) Response(data []byte) error { 41 | result := user.service.Call("gate", cham.PTYPE_RESPONSE, user.session, data) 42 | err := result[0] 43 | if err != nil { 44 | return err.(error) 45 | } 46 | return nil 47 | } 48 | 49 | func (user *User) Kill() { 50 | user.service.Call("broker", cham.PTYPE_GO, DELETE_USER, user.Openid) 51 | user.service.Stop() 52 | user.Save() 53 | } 54 | 55 | func (user *User) Run() { 56 | t := cham.DTimer.NewTicker(time.Minute * 5) 57 | t2 := cham.DTimer.NewTicker(time.Second * 5) 58 | heart := protocol.HeartBeat{} 59 | lost := 0 60 | for { 61 | select { 62 | case t := <-t.C: 63 | log.Infoln("every 5 Minute check start, openid:", user.Openid, " time:", t) 64 | user.Save() 65 | case t2 := <-t2.C: 66 | log.Infoln("every 5 Second heart beat, time:", t2) 67 | err := user.Response(protocol.Encode(0, heart)) 68 | if err != nil { 69 | lost++ 70 | log.Infoln("user heart beat lost,openid:", user.Openid) 71 | } else { 72 | lost = 0 73 | } 74 | // fmt.Println("---------", lost) 75 | if lost >= 5 { 76 | log.Infoln("heart beat lost more 5 time, close user service, openid:", user.Openid) 77 | user.Kill() 78 | return 79 | } 80 | } 81 | } 82 | } 83 | 84 | func Start(service *cham.Service, args ...interface{}) cham.Dispatch { 85 | log.Infoln("New Service ", service.String()) 86 | openid, session := args[0].(string), args[1].(uint32) 87 | user, err := newUser(service, &model.UserModel{Openid: openid}, session) 88 | if err != nil { 89 | log.Errorln("Service ", service.String(), "init error,", err.Error()) 90 | service.Stop() 91 | } 92 | return func(session int32, source cham.Address, ptype uint8, args ...interface{}) []interface{} { 93 | cmd := args[0].(uint8) 94 | switch cmd { 95 | case CHANGE_SESSION: 96 | user.session = args[1].(uint32) 97 | } 98 | 99 | return cham.NORET 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /example/question/usermanager/userManager.go: -------------------------------------------------------------------------------- 1 | package usermanager 2 | 3 | import ( 4 | "cham/cham" 5 | "cham/service/log" 6 | "fmt" 7 | "question/protocol" 8 | "question/usermanager/user" 9 | "sync" 10 | ) 11 | 12 | //manager 13 | type UserManager struct { 14 | sync.RWMutex 15 | users map[string]*cham.Service 16 | } 17 | 18 | func New() *UserManager { 19 | return &UserManager{users: make(map[string]*cham.Service)} 20 | } 21 | 22 | func (um *UserManager) Add(request *protocol.Login, session uint32) { 23 | openid := request.Openid 24 | um.RLock() 25 | if us, ok := um.users[openid]; ok { 26 | um.RUnlock() 27 | us.NotifySelf(cham.PTYPE_GO, user.CHANGE_SESSION, session) 28 | return 29 | } 30 | um.RUnlock() 31 | log.Infoln("new user,openid:", openid) 32 | us := cham.NewService(fmt.Sprintf("user-%s", openid), user.Start, 1, openid, session) 33 | if us == nil { 34 | log.Errorln("new user failed, openid:", openid) 35 | return 36 | } 37 | um.Lock() 38 | um.users[openid] = us 39 | um.Unlock() 40 | } 41 | 42 | func (um *UserManager) Delete(openid string) { 43 | um.Lock() 44 | delete(um.users, openid) 45 | um.Unlock() 46 | } 47 | 48 | func (um *UserManager) Handle(session uint32, protocolName string, request interface{}) { 49 | fmt.Printf("protocol: %#v\n", request) 50 | if protocolName == "login" { 51 | um.Add(request.(*protocol.Login), session) 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /example/question/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | const ( 8 | TIME_FORMATER = "2006-01-02 15:04:05" 9 | ) 10 | 11 | func ParseTime(t string) time.Time { 12 | tt, err := time.Parse(TIME_FORMATER, t) 13 | if err != nil { 14 | tt = time.Unix(0, 0) 15 | } 16 | return tt 17 | } 18 | 19 | func FormatTime(t time.Time) string { 20 | return t.Format(TIME_FORMATER) 21 | } 22 | -------------------------------------------------------------------------------- /lib/database/db.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "bytes" 5 | "database/sql" 6 | "fmt" 7 | "reflect" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | const ( 13 | tablenameFunc = "TableName" 14 | fieldTag = "field" 15 | pkTag = "pk" // primary key 16 | attrTag = "attr" 17 | autoAttr = "auto" //auto increamemt,e.g. id 18 | ) 19 | 20 | const ( 21 | TIME_FORMATER = "2006-01-02 15:04:05" 22 | ) 23 | 24 | var ( 25 | TIME_PTR_TYPE = reflect.TypeOf(&time.Time{}) 26 | ) 27 | 28 | type Model interface { 29 | TableName() string 30 | } 31 | 32 | type Database struct { 33 | db *sql.DB 34 | Debug bool 35 | } 36 | 37 | type Scanner interface { 38 | Scan(dest ...interface{}) error 39 | } 40 | 41 | func New(db *sql.DB) *Database { 42 | return &Database{db, false} 43 | } 44 | 45 | func (d *Database) Close() { 46 | d.db.Close() 47 | } 48 | 49 | //sql provide row and rows 50 | func scan(row Scanner, v reflect.Value, args []interface{}) error { 51 | //need support time.Time,save time.Time index and value 52 | keys := make([]int, 0) 53 | values := make([]*string, 0) 54 | for i := 0; i < len(args); i++ { 55 | vv := v.Field(i).Addr() 56 | t := vv.Type() 57 | if t == TIME_PTR_TYPE { 58 | var tmp string 59 | args[i] = &tmp 60 | keys = append(keys, i) 61 | values = append(values, &tmp) 62 | } else { 63 | args[i] = vv.Interface() 64 | } 65 | 66 | } 67 | err := row.Scan(args...) 68 | if err == nil { 69 | for i := 0; i < len(keys); i++ { 70 | t, err := time.Parse(TIME_FORMATER, *values[i]) 71 | if err == nil { 72 | v.Field(keys[i]).Set(reflect.ValueOf(t)) 73 | } 74 | } 75 | } 76 | return err 77 | } 78 | 79 | //need value to reduce once reflect expense 80 | // d.Get(&User{}, "openid", "123456") 81 | func (d *Database) Get(m Model, field string, value interface{}) error { 82 | q, n := query(m, field, "", 0) 83 | if d.Debug { 84 | fmt.Println(q, " [", value, "]") 85 | } 86 | row := d.db.QueryRow(q, value) 87 | v := reflect.ValueOf(m).Elem() 88 | args := make([]interface{}, n) 89 | // for i := 0; i < n; i++ { 90 | // args[i] = v.Field(i).Addr().Interface() 91 | // } 92 | // return row.Scan(args...) 93 | return scan(row, v, args) 94 | } 95 | 96 | //primary key get 97 | func (d *Database) GetPk(m Model) error { 98 | field, value := GetPkValue(m) 99 | return d.Get(m, field, value) 100 | } 101 | 102 | func (d *Database) Select(m Model, field string, condition interface{}, value ...interface{}) (ms []Model, err error) { 103 | q, n := query(m, field, condition, len(value)) 104 | if d.Debug { 105 | fmt.Println(q, " ", value) 106 | } 107 | rows, err := d.db.Query(q, value...) 108 | if err != nil { 109 | return 110 | } 111 | defer rows.Close() 112 | args := make([]interface{}, n) 113 | t := reflect.TypeOf(m).Elem() 114 | for rows.Next() { 115 | v := reflect.New(t).Elem() 116 | // for i := 0; i < n; i++ { 117 | // args[i] = v.Field(i).Addr().Interface() 118 | // } 119 | // if err = rows.Scan(args...); err != nil { 120 | // return 121 | // } 122 | if err = scan(rows, v, args); err != nil { 123 | return 124 | } 125 | ms = append(ms, (v.Addr().Interface()).(Model)) 126 | } 127 | err = rows.Err() 128 | return 129 | } 130 | 131 | // d.GetCondition(&User{}, "where level >10 and money select all, string -> condition 329 | func query(m Model, field string, condition interface{}, inlen int) (string, int) { 330 | buf := bytes.NewBufferString("SELECT ") 331 | keys, n, _, _ := structKeys(m, true) 332 | buf.WriteString(strings.Join(keys, ", ")) 333 | buf.WriteString(" FROM ") 334 | buf.WriteString(m.TableName()) 335 | 336 | if condition != nil { 337 | if c, ok := condition.(string); ok && c != "" { 338 | buf.WriteByte(' ') 339 | buf.WriteString(c) 340 | } else { 341 | buf.WriteString(" Where ") 342 | buf.WriteString(field) 343 | if inlen <= 0 { 344 | buf.WriteString("=?") 345 | } else { 346 | buf.WriteString(" IN(") 347 | var tmp2 []string 348 | // try reuse keys 349 | if cap(keys) >= inlen { 350 | tmp2 = keys[0:0] 351 | } else { 352 | tmp2 = make([]string, 0, inlen) 353 | } 354 | for i := 0; i < inlen; i++ { 355 | tmp2 = append(tmp2, "?") 356 | } 357 | buf.WriteString(strings.Join(tmp2, ", ")) 358 | buf.WriteString(")") 359 | } 360 | } 361 | } 362 | return buf.String(), n 363 | } 364 | 365 | func delQuery(m Model, field string) string { 366 | buf := bytes.NewBufferString("DELETE FROM ") 367 | buf.WriteString(m.TableName()) 368 | buf.WriteString(" WHERE ") 369 | buf.WriteString(field) 370 | buf.WriteString("=?") 371 | return buf.String() 372 | } 373 | 374 | func updateQuery(m Model, field string) (string, int, int) { 375 | buf := bytes.NewBufferString("UPDATE ") 376 | buf.WriteString(m.TableName()) 377 | buf.WriteString(" SET ") 378 | keys, n, auto, _ := structKeys(m, false) 379 | for i, k := range keys { 380 | buf.WriteString(k) 381 | buf.WriteString("=?") 382 | if n != 2 && i != n-1 { // one key || last 383 | buf.WriteString(" ,") 384 | } 385 | } 386 | buf.WriteString(" WHERE ") 387 | buf.WriteString(field) 388 | buf.WriteString("=?") 389 | 390 | return buf.String(), n, auto 391 | } 392 | 393 | // return {insert string, struct NumField, auto field index} 394 | func insertquery(m Model) (string, int, int) { 395 | buf := bytes.NewBufferString("INSERT INTO ") 396 | buf.WriteString(m.TableName()) 397 | buf.WriteString("(") 398 | keys, n, auto, _ := structKeys(m, false) 399 | buf.WriteString(strings.Join(keys, ", ")) 400 | buf.WriteString(")") 401 | buf.WriteString(" VALUES(") 402 | for i := 0; i < len(keys); i++ { 403 | keys[i] = "?" 404 | } 405 | buf.WriteString(strings.Join(keys, ", ")) 406 | buf.WriteString(")") 407 | return buf.String(), n, auto 408 | } 409 | -------------------------------------------------------------------------------- /lib/database/db_test.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | _ "github.com/go-sql-driver/mysql" 7 | "os" 8 | "testing" 9 | ) 10 | 11 | var db *sql.DB 12 | 13 | type User struct { 14 | Id int `attr:"auto" pk:"true"` 15 | Name string 16 | } 17 | 18 | func (u *User) TableName() string { 19 | return "users" 20 | } 21 | 22 | type UserWithName struct { 23 | Id int 24 | Name string `field:"username"` 25 | } 26 | 27 | func (u *UserWithName) TableName() string { 28 | return "usertable" 29 | } 30 | 31 | type DefaultUser struct { 32 | DeafultModel 33 | } 34 | 35 | func TestTableName(t *testing.T) { 36 | u := &User{} 37 | if tableName(u) != "users" { 38 | t.Error("User tableName error") 39 | } 40 | uu := &UserWithName{} 41 | if tableName(uu) != "usertable" { 42 | t.Error("UserWithName tableName error") 43 | } 44 | fmt.Println("end") 45 | } 46 | 47 | func TestDefaultModel(t *testing.T) { 48 | u := &DefaultUser{} 49 | if u.TableName() != "deafultmodel" { 50 | t.Error("TestDefaultModel error") 51 | } 52 | } 53 | 54 | func TestQuery(t *testing.T) { 55 | uu := &UserWithName{} 56 | fmt.Println(query(uu, "name", "", 0)) 57 | } 58 | 59 | func TestGet(t *testing.T) { 60 | u := &User{} 61 | d := &Database{db, true} 62 | fmt.Println(d.Get(u, "id", 3)) 63 | fmt.Println(u) 64 | 65 | fmt.Println("test GetPk") 66 | u.Id = 4 67 | d.GetPk(u) 68 | fmt.Println(u) 69 | 70 | fmt.Println("------------------") 71 | // v, e := d.GetMultiIn(u, "id", 3, 4) 72 | v, e := d.GetCondition(&User{}, "where id >3 and id 0 { 53 | i += len(matches) 54 | } else { 55 | i++ 56 | } 57 | } 58 | return string(filter) 59 | } 60 | -------------------------------------------------------------------------------- /lib/filter/wordfilter_test.go: -------------------------------------------------------------------------------- 1 | package filter 2 | 3 | import ( 4 | // "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestAscFilter(t *testing.T) { 9 | r := New() 10 | r.Add("12") 11 | r.Add("13") 12 | r.Add("14") 13 | r.Add("15") 14 | r.Add("16") 15 | r.Add("34") 16 | r.Add("23") 17 | if r.Filter("1234121567") != "********67" { 18 | t.Error("filter ascii error") 19 | } 20 | } 21 | 22 | func TestRuneFilter(t *testing.T) { 23 | r := New() 24 | r.Add("毛片") 25 | r.Add("毛毛") 26 | if r.Filter("我是老毛") != "我是老毛" { 27 | t.Error("rune error") 28 | } 29 | if r.Filter("我是毛毛") != "我是**" { 30 | t.Error("rune 2 error") 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /lib/helper/helper.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | "syscall" 7 | ) 8 | 9 | func Fork() (int, syscall.Errno) { 10 | r1, r2, err := syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0) 11 | if err != 0 { 12 | return 0, err 13 | } 14 | if runtime.GOOS == "darwin" && r2 == 1 { 15 | r1 = 1 16 | } 17 | return int(r1), 0 18 | } 19 | 20 | // referer http://ikarishinjieva.github.io/blog/blog/2014/03/20/go-file-lock/ 21 | func LockFile(name string, truncate bool) (*os.File, error) { 22 | flag := os.O_RDWR | os.O_CREATE 23 | if truncate { 24 | flag |= os.O_TRUNC 25 | } 26 | f, err := os.OpenFile(name, flag, 0666) 27 | if err != nil { 28 | return nil, err 29 | } 30 | if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil { 31 | f.Close() 32 | return nil, err 33 | } 34 | return f, nil 35 | } 36 | -------------------------------------------------------------------------------- /lib/helper/helper_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestFork(t *testing.T) { 11 | fmt.Println("pid", os.Getpid()) 12 | pid, err := fork() 13 | if err != 0 { 14 | t.Error("fork error,errno:", err) 15 | } 16 | if pid == 0 { 17 | fmt.Println("child:", os.Getpid(), "parent:", os.Getppid()) 18 | time.Sleep(time.Second * 20) 19 | fmt.Println("child exit") 20 | os.Exit(0) 21 | } else { 22 | fmt.Println("parent:", os.Getpid(), "child:", pid) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/lru/lru.go: -------------------------------------------------------------------------------- 1 | package lru 2 | 3 | import ( 4 | "container/list" 5 | "sync" 6 | ) 7 | 8 | type ExpireHandler func(Key, Value) 9 | type Key interface{} 10 | type Value interface{} 11 | 12 | type entry struct { 13 | key Key 14 | value Value 15 | } 16 | 17 | type Cache struct { 18 | sync.Mutex 19 | MaxEntries int // 0 -> no limit 20 | OnExpire ExpireHandler // invoke when expire delete 21 | ll *list.List 22 | cache map[Key]*list.Element 23 | } 24 | 25 | func New(maxEntries int, handler ExpireHandler) *Cache { 26 | return &Cache{ 27 | MaxEntries: maxEntries, 28 | OnExpire: handler, 29 | ll: list.New(), 30 | cache: make(map[Key]*list.Element), 31 | } 32 | } 33 | 34 | func (c *Cache) Add(key Key, value Value) (ok bool) { 35 | c.Lock() 36 | defer c.Unlock() 37 | if e, ok := c.cache[key]; ok { 38 | c.ll.MoveToFront(e) 39 | e.Value.(*entry).value = value 40 | } else { 41 | e = c.ll.PushFront(&entry{key, value}) 42 | c.cache[key] = e 43 | } 44 | if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries { 45 | c.RemoveOldest() 46 | } 47 | return 48 | } 49 | 50 | func (c *Cache) Get(key Key) (Value, bool) { 51 | c.Lock() 52 | defer c.Unlock() 53 | if e, ok := c.cache[key]; ok { 54 | c.ll.MoveToFront(e) 55 | return e.Value.(*entry).value, true 56 | } else { 57 | return nil, false 58 | } 59 | } 60 | 61 | func (c *Cache) Len() int { 62 | c.Lock() 63 | defer c.Unlock() 64 | return c.ll.Len() 65 | } 66 | 67 | func (c *Cache) RemoveOldest() { 68 | e := c.ll.Back() 69 | c.ll.Remove(e) 70 | kv := e.Value.(*entry) 71 | delete(c.cache, kv.key) 72 | if c.OnExpire != nil { 73 | c.OnExpire(kv.key, kv.value) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/lru/lru_test.go: -------------------------------------------------------------------------------- 1 | package lru 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestLru(t *testing.T) { 9 | c := New(2, nil) 10 | c.Add(1, "hello") 11 | c.Add(2, "world") 12 | if d, ok := c.Get(1); ok { 13 | if d.(string) != "hello" { 14 | t.Error("error") 15 | } 16 | } else { 17 | t.Error("error") 18 | } 19 | c.Add(3, "overload") 20 | if e, ok := c.Get(2); ok || e != nil { 21 | t.Error("remove oldest error") 22 | } 23 | if c.Len() != 2 { 24 | t.Error("len error") 25 | } 26 | fmt.Println("end") 27 | } 28 | -------------------------------------------------------------------------------- /lib/zset/skiplist.c: -------------------------------------------------------------------------------- 1 | 2 | // skiplist similar with the version in redis 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "skiplist.h" 9 | 10 | 11 | skiplistNode *slCreateNode(int level, double score, slobj *obj) { 12 | skiplistNode *n = malloc(sizeof(*n) + level * sizeof(struct skiplistLevel)); 13 | n->score = score; 14 | n->obj = obj; 15 | return n; 16 | } 17 | 18 | skiplist *slCreate(void) { 19 | int j; 20 | skiplist *sl; 21 | 22 | sl = malloc(sizeof(*sl)); 23 | sl->level = 1; 24 | sl->length = 0; 25 | sl->header = slCreateNode(SKIPLIST_MAXLEVEL, 0, NULL); 26 | for (j=0; j < SKIPLIST_MAXLEVEL; j++) { 27 | sl->header->level[j].forward = NULL; 28 | sl->header->level[j].span = 0; 29 | } 30 | sl->header->backward = NULL; 31 | sl->tail = NULL; 32 | return sl; 33 | } 34 | 35 | slobj* slCreateObj(const char* ptr, size_t length) { 36 | slobj *obj = malloc(sizeof(*obj)); 37 | obj->ptr = malloc(length + 1); 38 | 39 | if(ptr) { 40 | memcpy(obj->ptr, ptr, length); 41 | } 42 | obj->ptr[length] = '\0'; 43 | 44 | obj->length = length; 45 | return obj; 46 | } 47 | 48 | void slFreeObj(slobj *obj) { 49 | free(obj->ptr); 50 | free(obj); 51 | } 52 | 53 | void slFreeNode(skiplistNode *node) { 54 | slFreeObj(node->obj); 55 | free(node); 56 | } 57 | 58 | void slFree(skiplist *sl) { 59 | skiplistNode *node = sl->header->level[0].forward, *next; 60 | 61 | free(sl->header); 62 | while(node) { 63 | next = node->level[0].forward; 64 | slFreeNode(node); 65 | node = next; 66 | } 67 | free(sl); 68 | } 69 | 70 | int slRandomLevel(void) { 71 | int level = 1; 72 | while((random() & 0xffff) < (SKIPLIST_P * 0xffff)) 73 | level += 1; 74 | return (level < SKIPLIST_MAXLEVEL) ? level : SKIPLIST_MAXLEVEL; 75 | } 76 | 77 | int compareslObj(slobj *a, slobj *b) { 78 | int cmp = memcmp(a->ptr, b->ptr, a->length <= b->length ? a->length : b->length); 79 | if(cmp == 0) return a->length - b->length; 80 | return cmp; 81 | } 82 | 83 | int equalslObj(slobj *a, slobj *b) { 84 | return compareslObj(a, b) == 0; 85 | } 86 | 87 | void slInsert(skiplist *sl, double score, slobj *obj) { 88 | skiplistNode *update[SKIPLIST_MAXLEVEL], *x; 89 | unsigned int rank[SKIPLIST_MAXLEVEL]; 90 | int i, level; 91 | 92 | x = sl->header; 93 | for (i = sl->level-1; i >= 0; i--) { 94 | /* store rank that is crossed to reach the insert position */ 95 | rank[i] = i == (sl->level-1) ? 0 : rank[i+1]; 96 | while (x->level[i].forward && 97 | (x->level[i].forward->score < score || 98 | (x->level[i].forward->score == score && 99 | compareslObj(x->level[i].forward->obj,obj) < 0))) { 100 | rank[i] += x->level[i].span; 101 | x = x->level[i].forward; 102 | } 103 | update[i] = x; 104 | } 105 | /* we assume the key is not already inside, since we allow duplicated 106 | * scores, and the re-insertion of score and redis object should never 107 | * happen since the caller of slInsert() should test in the hash table 108 | * if the element is already inside or not. */ 109 | level = slRandomLevel(); 110 | if (level > sl->level) { 111 | for (i = sl->level; i < level; i++) { 112 | rank[i] = 0; 113 | update[i] = sl->header; 114 | update[i]->level[i].span = sl->length; 115 | } 116 | sl->level = level; 117 | } 118 | x = slCreateNode(level,score,obj); 119 | for (i = 0; i < level; i++) { 120 | x->level[i].forward = update[i]->level[i].forward; 121 | update[i]->level[i].forward = x; 122 | 123 | /* update span covered by update[i] as x is inserted here */ 124 | x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]); 125 | update[i]->level[i].span = (rank[0] - rank[i]) + 1; 126 | } 127 | 128 | /* increment span for untouched levels */ 129 | for (i = level; i < sl->level; i++) { 130 | update[i]->level[i].span++; 131 | } 132 | 133 | x->backward = (update[0] == sl->header) ? NULL : update[0]; 134 | if (x->level[0].forward) 135 | x->level[0].forward->backward = x; 136 | else 137 | sl->tail = x; 138 | sl->length++; 139 | } 140 | 141 | /* Internal function used by slDelete, slDeleteByScore */ 142 | void slDeleteNode(skiplist *sl, skiplistNode *x, skiplistNode **update) { 143 | int i; 144 | for (i = 0; i < sl->level; i++) { 145 | if (update[i]->level[i].forward == x) { 146 | update[i]->level[i].span += x->level[i].span - 1; 147 | update[i]->level[i].forward = x->level[i].forward; 148 | } else { 149 | update[i]->level[i].span -= 1; 150 | } 151 | } 152 | if (x->level[0].forward) { 153 | x->level[0].forward->backward = x->backward; 154 | } else { 155 | sl->tail = x->backward; 156 | } 157 | while(sl->level > 1 && sl->header->level[sl->level-1].forward == NULL) 158 | sl->level--; 159 | sl->length--; 160 | } 161 | 162 | /* Delete an element with matching score/object from the skiplist. */ 163 | int slDelete(skiplist *sl, double score, slobj *obj) { 164 | skiplistNode *update[SKIPLIST_MAXLEVEL], *x; 165 | int i; 166 | 167 | x = sl->header; 168 | for (i = sl->level-1; i >= 0; i--) { 169 | while (x->level[i].forward && 170 | (x->level[i].forward->score < score || 171 | (x->level[i].forward->score == score && 172 | compareslObj(x->level[i].forward->obj,obj) < 0))) 173 | x = x->level[i].forward; 174 | update[i] = x; 175 | } 176 | /* We may have multiple elements with the same score, what we need 177 | * is to find the element with both the right score and object. */ 178 | x = x->level[0].forward; 179 | if (x && score == x->score && equalslObj(x->obj,obj)) { 180 | slDeleteNode(sl, x, update); 181 | slFreeNode(x); 182 | return 1; 183 | } else { 184 | return 0; /* not found */ 185 | } 186 | return 0; /* not found */ 187 | } 188 | 189 | void slDeleteCb(void* ud, slobj *obj) { 190 | delCb(ud, obj); 191 | } 192 | 193 | /* Delete all the elements with rank between start and end from the skiplist. 194 | * Start and end are inclusive. Note that start and end need to be 1-based */ 195 | unsigned long slDeleteByRank(skiplist *sl, unsigned int start, unsigned int end, void* ud) { 196 | skiplistNode *update[SKIPLIST_MAXLEVEL], *x; 197 | unsigned long traversed = 0, removed = 0; 198 | int i; 199 | 200 | x = sl->header; 201 | for (i = sl->level-1; i >= 0; i--) { 202 | while (x->level[i].forward && (traversed + x->level[i].span) < start) { 203 | traversed += x->level[i].span; 204 | x = x->level[i].forward; 205 | } 206 | update[i] = x; 207 | } 208 | 209 | traversed++; 210 | x = x->level[0].forward; 211 | while (x && traversed <= end) { 212 | skiplistNode *next = x->level[0].forward; 213 | slDeleteNode(sl,x,update); 214 | slDeleteCb(ud, x->obj); 215 | slFreeNode(x); 216 | removed++; 217 | traversed++; 218 | x = next; 219 | } 220 | return removed; 221 | } 222 | 223 | /* Find the rank for an element by both score and key. 224 | * Returns 0 when the element cannot be found, rank otherwise. 225 | * Note that the rank is 1-based due to the span of sl->header to the 226 | * first element. */ 227 | unsigned long slGetRank(skiplist *sl, double score, slobj *o) { 228 | skiplistNode *x; 229 | unsigned long rank = 0; 230 | int i; 231 | 232 | x = sl->header; 233 | for (i = sl->level-1; i >= 0; i--) { 234 | while (x->level[i].forward && 235 | (x->level[i].forward->score < score || 236 | (x->level[i].forward->score == score && 237 | compareslObj(x->level[i].forward->obj,o) <= 0))) { 238 | rank += x->level[i].span; 239 | x = x->level[i].forward; 240 | } 241 | 242 | /* x might be equal to sl->header, so test if obj is non-NULL */ 243 | if (x->obj && equalslObj(x->obj, o)) { 244 | return rank; 245 | } 246 | } 247 | return 0; 248 | } 249 | 250 | /* Finds an element by its rank. The rank argument needs to be 1-based. */ 251 | skiplistNode* slGetNodeByRank(skiplist *sl, unsigned long rank) { 252 | if(rank == 0 || rank > sl->length) { 253 | return NULL; 254 | } 255 | 256 | skiplistNode *x; 257 | unsigned long traversed = 0; 258 | int i; 259 | 260 | x = sl->header; 261 | for (i = sl->level-1; i >= 0; i--) { 262 | while (x->level[i].forward && (traversed + x->level[i].span) <= rank) 263 | { 264 | traversed += x->level[i].span; 265 | x = x->level[i].forward; 266 | } 267 | if (traversed == rank) { 268 | return x; 269 | } 270 | } 271 | 272 | return NULL; 273 | } 274 | 275 | /* range [min, max], left & right both include */ 276 | /* Returns if there is a part of the zset is in range. */ 277 | int slIsInRange(skiplist *sl, double min, double max) { 278 | skiplistNode *x; 279 | 280 | /* Test for ranges that will always be empty. */ 281 | if(min > max) { 282 | return 0; 283 | } 284 | x = sl->tail; 285 | if (x == NULL || x->score < min) 286 | return 0; 287 | 288 | x = sl->header->level[0].forward; 289 | if (x == NULL || x->score > max) 290 | return 0; 291 | return 1; 292 | } 293 | 294 | /* Find the first node that is contained in the specified range. 295 | * Returns NULL when no element is contained in the range. */ 296 | skiplistNode *slFirstInRange(skiplist *sl, double min, double max) { 297 | skiplistNode *x; 298 | int i; 299 | 300 | /* If everything is out of range, return early. */ 301 | if (!slIsInRange(sl,min, max)) return NULL; 302 | 303 | x = sl->header; 304 | for (i = sl->level-1; i >= 0; i--) { 305 | /* Go forward while *OUT* of range. */ 306 | while (x->level[i].forward && x->level[i].forward->score < min) 307 | x = x->level[i].forward; 308 | } 309 | 310 | /* This is an inner range, so the next node cannot be NULL. */ 311 | x = x->level[0].forward; 312 | return x; 313 | } 314 | 315 | /* Find the last node that is contained in the specified range. 316 | * Returns NULL when no element is contained in the range. */ 317 | skiplistNode *slLastInRange(skiplist *sl, double min, double max) { 318 | skiplistNode *x; 319 | int i; 320 | 321 | /* If everything is out of range, return early. */ 322 | if (!slIsInRange(sl, min, max)) return NULL; 323 | 324 | x = sl->header; 325 | for (i = sl->level-1; i >= 0; i--) { 326 | /* Go forward while *IN* range. */ 327 | while (x->level[i].forward && 328 | x->level[i].forward->score <= max) 329 | x = x->level[i].forward; 330 | } 331 | 332 | /* This is an inner range, so this node cannot be NULL. */ 333 | return x; 334 | } 335 | 336 | skiplistNode *getNextNode(skiplistNode *node, int reverse) { 337 | return reverse? node->backward : node->level[0].forward; 338 | } 339 | 340 | void slDump(skiplist *sl) { 341 | skiplistNode *x; 342 | int i; 343 | 344 | x = sl->header; 345 | i = 0; 346 | while(x->level[0].forward) { 347 | x = x->level[0].forward; 348 | i++; 349 | printf("node %d: score:%f, member:%s\n", i, x->score, x->obj->ptr); 350 | } 351 | } 352 | 353 | 354 | 355 | -------------------------------------------------------------------------------- /lib/zset/skiplist.h: -------------------------------------------------------------------------------- 1 | // 2 | #include 3 | 4 | #define SKIPLIST_MAXLEVEL 32 5 | #define SKIPLIST_P 0.25 6 | 7 | typedef struct slobj { 8 | char *ptr; 9 | size_t length; 10 | } slobj; 11 | 12 | typedef struct skiplistNode { 13 | slobj* obj; 14 | double score; 15 | struct skiplistNode *backward; 16 | struct skiplistLevel { 17 | struct skiplistNode *forward; 18 | unsigned int span; 19 | }level[]; 20 | } skiplistNode; 21 | 22 | typedef struct skiplist { 23 | struct skiplistNode *header, *tail; 24 | unsigned long length; 25 | int level; 26 | } skiplist; 27 | 28 | slobj* slCreateObj(const char* ptr, size_t length); 29 | void slFreeObj(slobj *obj); 30 | 31 | skiplist *slCreate(void); 32 | void slFree(skiplist *sl); 33 | void slDump(skiplist *sl); 34 | 35 | void slInsert(skiplist *sl, double score, slobj *obj); 36 | int slDelete(skiplist *sl, double score, slobj *obj); 37 | unsigned long slDeleteByRank(skiplist *sl, unsigned int start, unsigned int end, void* ud); 38 | 39 | unsigned long slGetRank(skiplist *sl, double score, slobj *o); 40 | skiplistNode* slGetNodeByRank(skiplist *sl, unsigned long rank); 41 | 42 | skiplistNode *slFirstInRange(skiplist *sl, double min, double max); 43 | skiplistNode *slLastInRange(skiplist *sl, double min, double max); 44 | 45 | skiplistNode *getNextNode(skiplistNode *node, int reverse); 46 | 47 | extern void delCb(void* ud, slobj *o); 48 | 49 | -------------------------------------------------------------------------------- /lib/zset/zset.go: -------------------------------------------------------------------------------- 1 | package zset 2 | 3 | //#include "skiplist.h" 4 | import "C" 5 | 6 | import ( 7 | "bufio" 8 | "cham/lib/helper" 9 | "encoding/json" 10 | "errors" 11 | "os" 12 | "reflect" 13 | "runtime" 14 | "unsafe" 15 | ) 16 | 17 | const DEFAULT_TBL_LEN = 16 18 | 19 | type zset struct { 20 | sl *C.skiplist 21 | tbl map[string]float64 22 | } 23 | 24 | func tocstring(s string) (*C.char, C.size_t) { 25 | v := (*reflect.StringHeader)(unsafe.Pointer(&s)) 26 | return (*C.char)(unsafe.Pointer(v.Data)), C.size_t(v.Len) 27 | } 28 | 29 | func newslobj(s string) *C.slobj { 30 | p, l := tocstring(s) 31 | return C.slCreateObj(p, l) 32 | } 33 | 34 | func New() *zset { 35 | z := &zset{C.slCreate(), make(map[string]float64, DEFAULT_TBL_LEN)} 36 | runtime.SetFinalizer(z, func(z *zset) { 37 | C.slFree(z.sl) 38 | z.tbl = nil 39 | }) 40 | return z 41 | } 42 | 43 | func (z *zset) Add(score float64, member string) { 44 | if old, ok := z.tbl[member]; ok { 45 | if old == score { 46 | return 47 | } 48 | var obj C.slobj 49 | obj.ptr, obj.length = tocstring(member) 50 | C.slDelete(z.sl, C.double(old), &obj) 51 | } 52 | C.slInsert(z.sl, C.double(score), newslobj(member)) 53 | z.tbl[member] = score 54 | } 55 | 56 | func (z *zset) Rem(member string) { 57 | if score, ok := z.tbl[member]; ok { 58 | var obj C.slobj 59 | obj.ptr, obj.length = tocstring(member) 60 | C.slDelete(z.sl, C.double(score), &obj) 61 | delete(z.tbl, member) 62 | } 63 | } 64 | 65 | func (z *zset) Count() int { 66 | return int(z.sl.length) 67 | } 68 | 69 | func (z *zset) Score(member string) (float64, bool) { 70 | score, ex := z.tbl[member] 71 | return score, ex 72 | } 73 | 74 | func (z *zset) Range(r1, r2 int) []string { 75 | if r1 < 1 { 76 | r1 = 1 77 | } 78 | if r2 < 1 { 79 | r2 = 1 80 | } 81 | var reverse, rangelen int 82 | if r1 <= r2 { 83 | reverse = 0 84 | rangelen = r2 - r1 + 1 85 | } else { 86 | reverse = 1 87 | rangelen = r1 - r2 + 1 88 | } 89 | node := C.slGetNodeByRank(z.sl, C.ulong(r1)) 90 | result := make([]string, 0, rangelen) 91 | rr := C.int(reverse) 92 | for n := 0; node != nil && n < rangelen; { 93 | result = append(result, C.GoStringN(node.obj.ptr, C.int(node.obj.length))) 94 | node = C.getNextNode(node, rr) 95 | n++ 96 | } 97 | return result 98 | } 99 | 100 | func (z *zset) reverseRank(r int) int { 101 | return z.Count() - r + 1 102 | } 103 | 104 | func (z *zset) RevRange(r1, r2 int) []string { 105 | return z.Range(z.reverseRank(r1), z.reverseRank(r2)) 106 | } 107 | 108 | func (z *zset) RangeByScore(s1, s2 float64) []string { 109 | var reverse int 110 | var node *C.skiplistNode 111 | cs1, cs2 := C.double(s1), C.double(s2) 112 | if s1 <= s2 { 113 | reverse = 0 114 | node = C.slFirstInRange(z.sl, cs1, cs2) 115 | } else { 116 | reverse = 1 117 | node = C.slLastInRange(z.sl, cs2, cs1) 118 | } 119 | 120 | result := make([]string, 0) 121 | rr := C.int(reverse) 122 | for node != nil { 123 | if reverse == 1 { 124 | if node.score < cs2 { 125 | break 126 | } 127 | } else { 128 | if node.score > cs2 { 129 | break 130 | } 131 | } 132 | result = append(result, C.GoStringN(node.obj.ptr, C.int(node.obj.length))) 133 | node = C.getNextNode(node, rr) 134 | } 135 | return result 136 | } 137 | 138 | // rank is 1-based, 0 not found 139 | func (z *zset) Rank(member string) int { 140 | score, ex := z.tbl[member] 141 | if !ex { 142 | return 0 143 | } 144 | var obj C.slobj 145 | obj.ptr, obj.length = tocstring(member) 146 | rank := C.slGetRank(z.sl, C.double(score), &obj) 147 | return int(rank) 148 | } 149 | 150 | func (z *zset) RevRank(member string) int { 151 | rank := z.Rank(member) 152 | if rank != 0 { 153 | rank = z.reverseRank(rank) 154 | } 155 | return rank 156 | } 157 | 158 | func (z *zset) deleteByRank(from, to int) int { 159 | if from > to { 160 | from, to = to, from 161 | } 162 | return int(C.slDeleteByRank(z.sl, C.uint(from), C.uint(to), unsafe.Pointer(z))) 163 | } 164 | 165 | //export delCb 166 | func delCb(p unsafe.Pointer, obj *C.slobj) { 167 | z := (*zset)(p) 168 | member := C.GoStringN(obj.ptr, C.int(obj.length)) 169 | delete(z.tbl, member) 170 | } 171 | 172 | func (z *zset) Limit(count int) int { 173 | total := z.Count() 174 | if total <= count { 175 | return 0 176 | } 177 | return z.deleteByRank(count+1, total) 178 | } 179 | 180 | func (z *zset) RevLimit(count int) int { 181 | total := z.Count() 182 | if total <= count { 183 | return 0 184 | } 185 | from := z.reverseRank(count + 1) 186 | to := z.reverseRank(total) 187 | return z.deleteByRank(from, to) 188 | } 189 | 190 | func (z *zset) Dump() { 191 | C.slDump(z.sl) 192 | } 193 | 194 | //use fork snapshot to save 195 | func (z *zset) BgStore(name string) error { 196 | f, err := helper.LockFile(name, true) 197 | if err != nil { 198 | return err 199 | } 200 | pid, errno := helper.Fork() 201 | if errno != 0 { 202 | return errors.New("fork error," + errno.Error()) 203 | } 204 | //child 205 | if pid == 0 { 206 | buf := bufio.NewWriter(f) 207 | encoder := json.NewEncoder(buf) 208 | encoder.Encode(z.tbl) 209 | buf.Flush() 210 | f.Close() 211 | os.Exit(0) 212 | } 213 | 214 | return nil 215 | 216 | } 217 | 218 | func (z *zset) ReStore(name string) error { 219 | f, err := helper.LockFile(name, false) 220 | if err != nil { 221 | return err 222 | } 223 | tbl := make(map[string]float64, DEFAULT_TBL_LEN) 224 | buf := bufio.NewReader(f) 225 | decoder := json.NewDecoder(buf) 226 | decoder.Decode(&tbl) 227 | for k, v := range tbl { 228 | z.Add(v, k) 229 | } 230 | f.Close() 231 | return nil 232 | } 233 | -------------------------------------------------------------------------------- /lib/zset/zset_test.go: -------------------------------------------------------------------------------- 1 | package zset 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func equal(a []string, b []string) bool { 10 | if len(a) != len(b) { 11 | return false 12 | } 13 | for i := 0; i < len(a); i++ { 14 | if a[i] != b[i] { 15 | return false 16 | } 17 | } 18 | return true 19 | } 20 | 21 | func assert(t *testing.T, ok bool, s string) { 22 | if !ok { 23 | t.Error(s) 24 | } 25 | } 26 | 27 | func TestBase(t *testing.T) { 28 | z := New() 29 | assert(t, z.Count() == 0, "empty Count error") 30 | z.Add(1, "12") 31 | z.Add(1, "32") 32 | assert(t, z.Count() == 2, "not empty Count error") 33 | var score float64 34 | var ex bool 35 | score, ex = z.Score("12") 36 | assert(t, score == 1, "Score error") 37 | z.Add(2, "12") 38 | assert(t, z.Count() == 2, "after add duplicate Count error") 39 | score, ex = z.Score("12") 40 | assert(t, score == 2, "after add Score error") 41 | z.Rem("12") 42 | assert(t, z.Count() == 1, "after rem Count error") 43 | score, ex = z.Score("12") 44 | assert(t, ex == false, "not exist Score error") 45 | fmt.Println("") 46 | } 47 | 48 | func TestRangeByScore(t *testing.T) { 49 | z := New() 50 | z.Add(2, "22") 51 | z.Add(1, "11") 52 | z.Add(3, "33") 53 | s := "TestRangeByScore error" 54 | assert(t, equal(z.RangeByScore(2, 3), []string{"22", "33"}), s) 55 | assert(t, equal(z.RangeByScore(0, 5), []string{"11", "22", "33"}), s) 56 | assert(t, equal(z.RangeByScore(10, 5), []string{}), s) 57 | assert(t, equal(z.RangeByScore(10, 0), []string{"33", "22", "11"}), s) 58 | } 59 | 60 | func TestRange(t *testing.T) { 61 | z := New() 62 | z.Add(100.1, "1") 63 | z.Add(100.9, "9") 64 | z.Add(100.5, "5") 65 | assert(t, equal(z.Range(1, 3), []string{"1", "5", "9"}), "Range1 error") 66 | assert(t, equal(z.Range(3, 1), []string{"9", "5", "1"}), "Range2 error") 67 | assert(t, equal(z.RevRange(1, 2), []string{"9", "5"}), "RevRange1 error") 68 | assert(t, equal(z.RevRange(3, 2), []string{"1", "5"}), "RevRange2 error") 69 | 70 | } 71 | 72 | func TestRank(t *testing.T) { 73 | z := New() 74 | assert(t, z.Rank("kehan") == 0, "Rank empty error") 75 | z.Add(1111.1111, "kehan") 76 | assert(t, z.Rank("kehan") == 1, "Rank error") 77 | z.Add(222.2222, "lwy") 78 | assert(t, z.Rank("kehan") == 2, "Rank 2 error") 79 | assert(t, z.RevRank("kehan") == 1, "RevRank error") 80 | } 81 | 82 | func TestLimit(t *testing.T) { 83 | z := New() 84 | z.Add(1, "1") 85 | z.Add(2, "2") 86 | z.Add(3, "3") 87 | z.Limit(1) 88 | assert(t, z.Count() == 1, "Limit error") 89 | assert(t, z.Rank("3") == 0, "Limit Rank error") 90 | z.Add(4.4, "4") 91 | z.Add(5.5, "5") 92 | z.Add(0.5, "0.5") 93 | z.Dump() 94 | assert(t, z.RevLimit(4) == 0, "RevLimit error") 95 | assert(t, z.RevLimit(0) == 4, "RevLimit2 error") 96 | } 97 | 98 | func TestStore(t *testing.T) { 99 | z := New() 100 | z.Add(1, "1") 101 | z.Add(2, "2") 102 | z.Add(3, "3") 103 | z.BgStore("zdb.json") 104 | go func() { 105 | time.Sleep(time.Second * 2) 106 | fmt.Println("continue add") 107 | z.Add(4, "4") 108 | z.Add(5, "5") 109 | }() 110 | 111 | } 112 | 113 | func TestReStore(t *testing.T) { 114 | z := New() 115 | assert(t, len(z.tbl) == 0, "restore error") 116 | z.ReStore("zdb.json") 117 | fmt.Println(z.tbl) 118 | assert(t, len(z.tbl) != 0, "restore2 error") 119 | assert(t, z.Count() == len(z.tbl), "restore3 error") 120 | } 121 | -------------------------------------------------------------------------------- /service/debug/debug.go: -------------------------------------------------------------------------------- 1 | package debug 2 | 3 | import ( 4 | "bufio" 5 | "cham/cham" 6 | "cham/service/log" 7 | "fmt" 8 | "net" 9 | "reflect" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | type buffer struct { 15 | conn net.Conn 16 | } 17 | 18 | func (s buffer) WriteString(str string) { 19 | s.conn.Write([]byte(str)) 20 | } 21 | 22 | type debug struct { 23 | addr string 24 | funcs map[string]reflect.Value 25 | } 26 | 27 | func (d *debug) serve(conn net.Conn) { 28 | defer conn.Close() 29 | buf := buffer{conn} 30 | br := bufio.NewReader(conn) 31 | buf.WriteString("Welcome to cham console\r\n") 32 | 33 | for { 34 | line, _, err := br.ReadLine() 35 | if err != nil { 36 | return 37 | } 38 | if len(line) < 1 { 39 | continue 40 | } 41 | cmds := strings.Fields(string(line)) 42 | result := d.handle(cmds) 43 | buf.WriteString(result) 44 | buf.WriteString("\r\n") 45 | } 46 | } 47 | 48 | func (d *debug) handle(cmds []string) string { 49 | defer func() { 50 | if err := recover(); err != nil { 51 | log.Infoln("debug cmd", cmds, " error:", err) 52 | } 53 | }() 54 | 55 | funName := strings.Title(cmds[0]) 56 | if f, ok := d.funcs[funName]; !ok { 57 | return "Invalid command, type help for command list" 58 | } else { 59 | var in []reflect.Value 60 | n := len(cmds) - 1 61 | if n > 0 { 62 | args := cmds[1:] 63 | in = make([]reflect.Value, n) 64 | for i := 0; i < n; i++ { 65 | in[i] = reflect.ValueOf(args[i]) 66 | } 67 | } else { 68 | in = []reflect.Value{} 69 | } 70 | result := f.Call(in) 71 | return result[0].String() 72 | } 73 | } 74 | 75 | func (d *debug) getfuncs() { 76 | v := reflect.ValueOf(d) 77 | t := v.Type() 78 | for i := 0; i < t.NumMethod(); i++ { 79 | name := t.Method(i).Name 80 | if strings.HasPrefix(name, "Cmd") { 81 | d.funcs[name[3:]] = v.Method(i) 82 | } 83 | } 84 | } 85 | 86 | func (d *debug) start() { 87 | listener, err := net.Listen("tcp", d.addr) 88 | if err != nil { 89 | panic("debug listen addr:" + d.addr + " ,error:" + err.Error()) 90 | } 91 | log.Infoln("debug start, listen ", d.addr) 92 | defer listener.Close() 93 | d.getfuncs() 94 | for { 95 | conn, err := listener.Accept() 96 | if err != nil { 97 | continue 98 | } 99 | go d.serve(conn) 100 | } 101 | } 102 | 103 | func (d *debug) CmdList() string { 104 | return cham.DumpService() 105 | } 106 | 107 | func (d *debug) CmdHelp() string { 108 | return ` 109 | <--------------------> 110 | help help message 111 | list list all service 112 | send send message to service(send addr message) 113 | <--------------------> 114 | 115 | ` 116 | } 117 | 118 | func (d *debug) CmdSend(args ...string) string { 119 | if len(args) < 2 { 120 | return "FAILED, args not enough" 121 | } 122 | vv := make([]interface{}, len(args)-1) 123 | addr, err := strconv.Atoi(args[0]) 124 | if err != nil { 125 | return "FAILED, addr error" 126 | } 127 | pt, err := strconv.Atoi(args[1]) 128 | if err != nil { 129 | return "FAILED, ptype error" 130 | } 131 | vv[0] = uint8(pt) 132 | for i := 1; i < len(vv); i++ { 133 | vv[i] = args[i+2] 134 | } 135 | 136 | msg := cham.Main.Send(cham.Address(addr), cham.PTYPE_GO, vv...) 137 | return "SUCCESS," + fmt.Sprint(msg) 138 | } 139 | 140 | func Start(service *cham.Service, args ...interface{}) cham.Dispatch { 141 | d := &debug{args[0].(string), make(map[string]reflect.Value)} 142 | go d.start() 143 | return func(session int32, source cham.Address, ptype uint8, args ...interface{}) []interface{} { 144 | return cham.NORET 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /service/debug/debug_test.go: -------------------------------------------------------------------------------- 1 | package debug 2 | 3 | import ( 4 | "cham/cham" 5 | "cham/service/log" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestDebug(t *testing.T) { 11 | ll := log.New("log.txt", log.LDEFAULT, log.LDEBUG) 12 | ll.Infoln("hello") 13 | cham.NewService("debug", Start, 1, "127.0.0.1:8888") 14 | time.Sleep(time.Minute * 30) 15 | } 16 | -------------------------------------------------------------------------------- /service/gate/gate.go: -------------------------------------------------------------------------------- 1 | package gate 2 | 3 | import ( 4 | "bufio" 5 | "cham/cham" 6 | "cham/service/log" 7 | "encoding/binary" 8 | "io" 9 | "net" 10 | "net/http" 11 | "sync" 12 | "sync/atomic" 13 | ) 14 | 15 | const ( 16 | OPEN uint8 = iota 17 | KICK 18 | ) 19 | 20 | const ( 21 | OnOpen uint8 = iota 22 | OnMessage 23 | OnClose 24 | OnPong 25 | ) 26 | 27 | var ( 28 | bufioReaderPool sync.Pool 29 | bufioWriterPool sync.Pool 30 | ) 31 | 32 | type Conf struct { 33 | address string //127.0.0.1:8000 34 | maxclient uint32 // 0 -> no limit 35 | path string // "/ws" websocket, null is tcp 36 | } 37 | 38 | func NewConf(address string, maxclient uint32, path string) *Conf { 39 | return &Conf{address, maxclient, path} 40 | } 41 | 42 | type Gate struct { 43 | rwmutex *sync.RWMutex 44 | Source cham.Address 45 | service *cham.Service 46 | session uint32 47 | clinetnum uint32 48 | maxclient uint32 49 | sessions map[uint32]Backend 50 | } 51 | 52 | type Backend interface { 53 | Write(data []byte) error 54 | Close() 55 | } 56 | 57 | type TcpBackend struct { 58 | session uint32 59 | conn net.Conn 60 | brw *bufio.ReadWriter 61 | } 62 | 63 | // tcp readbuf start 64 | func newBufioReader(r io.Reader) *bufio.Reader { 65 | if v := bufioReaderPool.Get(); v != nil { 66 | br := v.(*bufio.Reader) 67 | br.Reset(r) 68 | return br 69 | } 70 | return bufio.NewReader(r) 71 | } 72 | 73 | func putBufioReader(r *bufio.Reader) { 74 | r.Reset(nil) 75 | bufioReaderPool.Put(r) 76 | } 77 | 78 | func newBufioWriter(w io.Writer) *bufio.Writer { 79 | if v := bufioWriterPool.Get(); v != nil { 80 | bw := v.(*bufio.Writer) 81 | bw.Reset(w) 82 | return bw 83 | } 84 | return bufio.NewWriter(w) 85 | } 86 | 87 | func putBufioWriter(w *bufio.Writer) { 88 | w.Reset(nil) 89 | bufioWriterPool.Put(w) 90 | } 91 | 92 | func newTcpBackend(session uint32, conn net.Conn) *TcpBackend { 93 | br := newBufioReader(conn) 94 | bw := newBufioWriter(conn) 95 | return &TcpBackend{session, conn, bufio.NewReadWriter(br, bw)} 96 | } 97 | 98 | // tcp readbuf end 99 | 100 | func (t *TcpBackend) Close() { 101 | putBufioReader(t.brw.Reader) 102 | putBufioWriter(t.brw.Writer) 103 | t.conn.Close() 104 | } 105 | 106 | func (t *TcpBackend) Write(data []byte) (err error) { 107 | head := make([]byte, 2) 108 | binary.BigEndian.PutUint16(head, uint16(len(data))) 109 | _, err = t.brw.Write(head) 110 | if err == nil { 111 | _, err = t.brw.Write(data) 112 | if err == nil { 113 | err = t.brw.Flush() 114 | } 115 | } 116 | return 117 | } 118 | 119 | func (t *TcpBackend) readFull(buf []byte) error { 120 | if _, err := io.ReadFull(t.brw, buf); err != nil { 121 | if e, ok := err.(net.Error); ok && !e.Temporary() { 122 | return err 123 | } 124 | } 125 | return nil 126 | } 127 | 128 | // bigendian 2byte length+data 129 | func (t *TcpBackend) serve(g *Gate) { 130 | head := make([]byte, 2) 131 | for { 132 | if err := t.readFull(head); err != nil { 133 | g.kick(t.session) 134 | return 135 | } 136 | 137 | length := binary.BigEndian.Uint16(head) 138 | data := make([]byte, length, length) 139 | 140 | if err := t.readFull(data); err != nil { 141 | g.kick(t.session) 142 | return 143 | } 144 | g.service.Notify(g.Source, cham.PTYPE_CLIENT, t.session, OnMessage, data) 145 | } 146 | } 147 | 148 | type WebsocketBackend struct { 149 | *Websocket 150 | } 151 | 152 | func (w *WebsocketBackend) Close() { 153 | w.Websocket.Close(0, []byte("")) 154 | } 155 | 156 | func (w *WebsocketBackend) Write(data []byte) error { 157 | return w.SendText(data) 158 | } 159 | 160 | func newWebsocket(w http.ResponseWriter, r *http.Request, opt *Option, session uint32, gate *Gate) (*WebsocketBackend, error) { 161 | ws, err := NewWebsocket(w, r, opt, session, gate) 162 | if err != nil { 163 | return nil, err 164 | } 165 | return &WebsocketBackend{ws}, nil 166 | } 167 | 168 | //websocket start 169 | type wsHandler struct { 170 | } 171 | 172 | func (wd wsHandler) CheckOrigin(origin, host string) bool { 173 | return true 174 | } 175 | 176 | func (wd wsHandler) OnOpen(ws *Websocket) { 177 | ws.gate.service.Notify(ws.gate.Source, cham.PTYPE_CLIENT, ws.session, OnOpen) 178 | } 179 | 180 | func (wd wsHandler) OnMessage(ws *Websocket, message []byte) { 181 | ws.gate.service.Notify(ws.gate.Source, cham.PTYPE_CLIENT, ws.session, OnMessage, message) 182 | } 183 | 184 | func (wd wsHandler) OnClose(ws *Websocket, code uint16, reason []byte) { 185 | ws.gate.service.Notify(ws.gate.Source, cham.PTYPE_CLIENT, ws.session, OnClose, code, reason) 186 | } 187 | 188 | func (wd wsHandler) OnPong(ws *Websocket, data []byte) { 189 | ws.gate.service.Notify(ws.gate.Source, cham.PTYPE_CLIENT, ws.session, OnPong, data) 190 | } 191 | 192 | //websocket end 193 | 194 | func New(source cham.Address, service *cham.Service) *Gate { 195 | gate := new(Gate) 196 | gate.rwmutex = new(sync.RWMutex) 197 | gate.service = service 198 | gate.Source = source 199 | gate.clinetnum = 0 200 | gate.session = 0 201 | gate.sessions = make(map[uint32]Backend) 202 | return gate 203 | } 204 | 205 | func (g *Gate) nextSession() uint32 { 206 | return atomic.AddUint32(&g.session, 1) 207 | } 208 | 209 | func (g *Gate) addBackend(session uint32, b Backend) { 210 | g.rwmutex.Lock() 211 | g.sessions[session] = b 212 | g.rwmutex.Unlock() 213 | } 214 | 215 | //gate listen 216 | func (g *Gate) open(conf *Conf) { 217 | maxclient := conf.maxclient 218 | g.maxclient = maxclient 219 | if conf.path == "" { 220 | log.Infoln("Tcp Gate start, listen ", conf.address) 221 | listen, err := net.Listen("tcp", conf.address) 222 | if err != nil { 223 | panic("gate http open error:" + err.Error()) 224 | } 225 | go func() { 226 | defer listen.Close() 227 | for { 228 | conn, err := listen.Accept() 229 | if err != nil { 230 | continue 231 | } 232 | if maxclient != 0 && g.clinetnum >= maxclient { 233 | conn.Close() //server close socket(!net.Error) 234 | break 235 | } 236 | g.clinetnum++ 237 | session := g.nextSession() 238 | backend := newTcpBackend(session, conn) 239 | g.sessions[session] = backend // not need mutex, so not addBackend 240 | go backend.serve(g) 241 | } 242 | }() 243 | 244 | } else { 245 | log.Infoln("Websocket Gate start, listen ", conf.address) 246 | var opt = Option{wsHandler{}, false} 247 | http.HandleFunc(conf.path, func(w http.ResponseWriter, r *http.Request) { 248 | 249 | if maxclient != 0 && g.clinetnum >= maxclient { 250 | return 251 | } 252 | session := g.nextSession() 253 | ws, err := newWebsocket(w, r, &opt, session, g) 254 | if err != nil { 255 | return 256 | } 257 | g.addBackend(session, ws) 258 | g.clinetnum++ 259 | ws.Start() 260 | }) 261 | go func() { http.ListenAndServe(conf.address, nil) }() 262 | } 263 | } 264 | 265 | func (g *Gate) kick(session uint32) { 266 | var b Backend 267 | var ok bool 268 | g.rwmutex.Lock() 269 | if b, ok = g.sessions[session]; ok { 270 | delete(g.sessions, session) 271 | g.clinetnum-- 272 | } 273 | g.rwmutex.Unlock() 274 | if ok { 275 | b.Close() 276 | } 277 | } 278 | 279 | func (g *Gate) Write(session uint32, data []byte) (err error) { 280 | g.rwmutex.RLock() 281 | b, ok := g.sessions[session] 282 | g.rwmutex.RUnlock() 283 | if ok { 284 | err = b.Write(data) 285 | } 286 | return 287 | } 288 | 289 | func ResponseStart(service *cham.Service, args ...interface{}) cham.Dispatch { 290 | gate := args[0].(*Gate) 291 | return func(session int32, source cham.Address, ptype uint8, args ...interface{}) []interface{} { 292 | sessionid := args[0].(uint32) 293 | data := args[1].([]byte) 294 | err := gate.Write(sessionid, data) 295 | return cham.Ret(err) 296 | } 297 | } 298 | 299 | func Start(service *cham.Service, args ...interface{}) cham.Dispatch { 300 | log.Infoln("New Service ", service.String()) 301 | gate := New(0, service) 302 | return func(session int32, source cham.Address, ptype uint8, args ...interface{}) []interface{} { 303 | cmd := args[0].(uint8) 304 | result := cham.NORET 305 | switch cmd { 306 | case OPEN: 307 | gate.Source = source 308 | service.RegisterProtocol(cham.PTYPE_RESPONSE, ResponseStart, gate) 309 | gate.open(args[1].(*Conf)) 310 | case KICK: 311 | gate.kick(args[1].(uint32)) 312 | } 313 | 314 | return result 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /service/gate/gate_test.go: -------------------------------------------------------------------------------- 1 | package gate 2 | 3 | import ( 4 | "cham/cham" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | "net" 9 | "strconv" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | func watchDogStart(service *cham.Service, args ...interface{}) cham.Dispatch { 15 | return func(session int32, source cham.Address, ptype uint8, args ...interface{}) []interface{} { 16 | return cham.NORET 17 | } 18 | } 19 | 20 | func clientDispatch(service *cham.Service, args ...interface{}) cham.Dispatch { 21 | return func(session int32, source cham.Address, ptype uint8, args ...interface{}) []interface{} { 22 | sessionid := args[0].(uint32) 23 | gt := args[1].(uint8) 24 | time.Sleep(time.Second * 5) 25 | switch gt { 26 | case OnOpen: 27 | fmt.Println("OnOpen ", sessionid) 28 | case OnClose: 29 | fmt.Println("OnClose ", sessionid, args[2:]) 30 | case OnPong: 31 | fmt.Println("OnPong ", sessionid, args[2]) 32 | case OnMessage: 33 | data := string(args[2].([]byte)) 34 | fmt.Println("OnMessage", sessionid, data) 35 | if data == "hello" { 36 | service.Notify("gate", cham.PTYPE_RESPONSE, sessionid, []byte("world"+strconv.Itoa(int(sessionid)))) 37 | } else if data == "WebSocket" { 38 | service.Notify("gate", cham.PTYPE_RESPONSE, sessionid, []byte("websocket reply"+strconv.Itoa(int(sessionid)))) 39 | } 40 | go func() { 41 | time.Sleep(time.Second * 5) 42 | fmt.Println("kick") 43 | // service.Notify("gate", cham.PTYPE_GO, KICK, sessionid) 44 | err := service.Call("gate", cham.PTYPE_RESPONSE, sessionid, []byte("world")) 45 | fmt.Println(err) 46 | }() 47 | } 48 | return cham.NORET 49 | } 50 | } 51 | 52 | func runClient(n int) { 53 | conn, err := net.Dial("tcp", "127.0.0.1:9999") 54 | if err != nil { 55 | fmt.Println("client error:" + err.Error()) 56 | return 57 | } 58 | i := string(strconv.Itoa(n)) 59 | fmt.Println("client start " + i) 60 | for { 61 | data := []byte("hello") 62 | head := make([]byte, 2) 63 | binary.BigEndian.PutUint16(head, uint16(len(data))) 64 | if _, err := conn.Write(head); err != nil { 65 | fmt.Println("client error:" + err.Error()) 66 | break 67 | } 68 | conn.Write(data) 69 | time.Sleep(time.Second) 70 | io.ReadFull(conn, head) 71 | length := binary.BigEndian.Uint16(head) 72 | result := make([]byte, length) 73 | io.ReadFull(conn, result) 74 | fmt.Println("client get:"+i, string(result)) 75 | time.Sleep(time.Second * 15) 76 | // break 77 | } 78 | fmt.Println("client end" + i) 79 | 80 | } 81 | 82 | func TestGateService(t *testing.T) { 83 | ws := cham.NewService("watchdog", watchDogStart, 16) // 16 worker to process client data 84 | ws.RegisterProtocol(cham.PTYPE_CLIENT, clientDispatch) 85 | gs := cham.NewService("gate", Start, 16) // 16 worker to send data to client 86 | ws.Call(gs, cham.PTYPE_GO, OPEN, NewConf("127.0.0.1:9999", 100, "")) 87 | for i := 0; i < 20; i++ { 88 | go runClient(i) 89 | } 90 | time.Sleep(time.Minute * 2) 91 | } 92 | 93 | func TestGateWsService(t *testing.T) { 94 | gs := cham.NewService("gate", Start, 16) // 16 worker to send data to clie 95 | ws := cham.NewService("watchdog", watchDogStart, 16) // 16 worker to process client data 96 | ws.RegisterProtocol(cham.PTYPE_CLIENT, clientDispatch) 97 | ws.Call(gs, cham.PTYPE_GO, OPEN, NewConf("127.0.0.1:9998", 100, "/ws")) 98 | time.Sleep(time.Minute * 10) 99 | } 100 | -------------------------------------------------------------------------------- /service/gate/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebSocket example 5 | 6 | 7 | 29 | 30 | -------------------------------------------------------------------------------- /service/gate/websocket.go: -------------------------------------------------------------------------------- 1 | package gate 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto/sha1" 7 | "encoding/base64" 8 | "encoding/binary" 9 | "errors" 10 | "io" 11 | "net" 12 | "net/http" 13 | "strings" 14 | ) 15 | 16 | var ( 17 | ErrUpgrade = errors.New("Can \"Upgrade\" only to \"WebSocket\"") 18 | ErrConnection = errors.New("\"Connection\" must be \"Upgrade\"") 19 | ErrCrossOrigin = errors.New("Cross origin websockets not allowed") 20 | ErrSecVersion = errors.New("HTTP/1.1 Upgrade Required\r\nSec-WebSocket-Version: 13\r\n\r\n") 21 | ErrSecKey = errors.New("\"Sec-WebSocket-Key\" must not be nil") 22 | ErrHijacker = errors.New("Not implement http.Hijacker") 23 | ) 24 | 25 | var ( 26 | ErrReservedBits = errors.New("Reserved_bits show using undefined extensions") 27 | ErrFrameOverload = errors.New("Control frame payload overload") 28 | ErrFrameFragmented = errors.New("Control frame must not be fragmented") 29 | ErrInvalidOpcode = errors.New("Invalid frame opcode") 30 | ) 31 | 32 | var ( 33 | crlf = []byte("\r\n") 34 | challengeKey = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") 35 | ) 36 | 37 | //referer https://github.com/Skycrab/skynet_websocket/blob/master/websocket.lua 38 | 39 | type WsHandler interface { 40 | CheckOrigin(origin, host string) bool 41 | OnOpen(ws *Websocket) 42 | OnMessage(ws *Websocket, message []byte) 43 | OnClose(ws *Websocket, code uint16, reason []byte) 44 | OnPong(ws *Websocket, data []byte) 45 | } 46 | 47 | type WsDefaultHandler struct { 48 | checkOriginOr bool // 是否校验origin, default true 49 | } 50 | 51 | func (wd WsDefaultHandler) CheckOrigin(origin, host string) bool { 52 | return true 53 | } 54 | 55 | func (wd WsDefaultHandler) OnOpen(ws *Websocket) { 56 | } 57 | 58 | func (wd WsDefaultHandler) OnMessage(ws *Websocket, message []byte) { 59 | } 60 | 61 | func (wd WsDefaultHandler) OnClose(ws *Websocket, code uint16, reason []byte) { 62 | } 63 | 64 | func (wd WsDefaultHandler) OnPong(ws *Websocket, data []byte) { 65 | 66 | } 67 | 68 | type Websocket struct { 69 | conn net.Conn 70 | session uint32 71 | gate *Gate 72 | rw *bufio.ReadWriter 73 | handler WsHandler 74 | clientTerminated bool 75 | serverTerminated bool 76 | maskOutgoing bool 77 | } 78 | 79 | type Option struct { 80 | Handler WsHandler // 处理器, default WsDefaultHandler 81 | MaskOutgoing bool //发送frame是否mask, default false 82 | } 83 | 84 | func challengeResponse(key, protocol string) []byte { 85 | sha := sha1.New() 86 | sha.Write([]byte(key)) 87 | sha.Write(challengeKey) 88 | accept := base64.StdEncoding.EncodeToString(sha.Sum(nil)) 89 | buf := bytes.NewBufferString("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ") 90 | buf.WriteString(accept) 91 | buf.Write(crlf) 92 | if protocol != "" { 93 | buf.WriteString("Sec-WebSocket-Protocol: ") 94 | buf.WriteString(protocol) 95 | buf.Write(crlf) 96 | } 97 | buf.Write(crlf) 98 | 99 | return buf.Bytes() 100 | } 101 | 102 | func acceptConnection(r *http.Request, h WsHandler) (challenge []byte, err error) { 103 | //Upgrade header should be present and should be equal to WebSocket 104 | if strings.ToLower(r.Header.Get("Upgrade")) != "websocket" { 105 | return nil, ErrUpgrade 106 | } 107 | 108 | //Connection header should be upgrade. Some proxy servers/load balancers 109 | // might mess with it. 110 | if !strings.Contains(strings.ToLower(r.Header.Get("Connection")), "upgrade") { 111 | return nil, ErrConnection 112 | } 113 | 114 | // Handle WebSocket Origin naming convention differences 115 | // The difference between version 8 and 13 is that in 8 the 116 | // client sends a "Sec-Websocket-Origin" header and in 13 it's 117 | // simply "Origin". 118 | if r.Header.Get("Sec-Websocket-Version") != "13" { 119 | return nil, ErrSecVersion 120 | } 121 | 122 | origin := r.Header.Get("Origin") 123 | if origin == "" { 124 | origin = r.Header.Get("Sec-Websocket-Origin") 125 | } 126 | 127 | if origin != "" && !h.CheckOrigin(origin, r.Header.Get("Host")) { 128 | return nil, ErrCrossOrigin 129 | } 130 | 131 | key := r.Header.Get("Sec-Websocket-Key") 132 | if key == "" { 133 | return nil, ErrSecKey 134 | } 135 | 136 | protocol := r.Header.Get("Sec-Websocket-Protocol") 137 | if protocol != "" { 138 | idx := strings.IndexByte(protocol, ',') 139 | if idx != -1 { 140 | protocol = protocol[:idx] 141 | } 142 | } 143 | 144 | return challengeResponse(key, protocol), nil 145 | 146 | } 147 | 148 | func websocketMask(mask []byte, data []byte) { 149 | for i := range data { 150 | data[i] ^= mask[i%4] 151 | } 152 | } 153 | 154 | func NewWebsocket(w http.ResponseWriter, r *http.Request, opt *Option, session uint32, gate *Gate) (*Websocket, error) { 155 | 156 | var h WsHandler 157 | var maskOutgoing bool 158 | if opt == nil { 159 | h = WsDefaultHandler{true} 160 | maskOutgoing = false 161 | } else { 162 | h = opt.Handler 163 | maskOutgoing = opt.MaskOutgoing 164 | } 165 | 166 | challenge, err := acceptConnection(r, h) 167 | if err != nil { 168 | var code int 169 | if err == ErrCrossOrigin { 170 | code = 403 171 | } else { 172 | code = 400 173 | } 174 | w.WriteHeader(code) 175 | w.Write([]byte(err.Error())) 176 | return nil, err 177 | } 178 | hj, ok := w.(http.Hijacker) 179 | if !ok { 180 | return nil, ErrHijacker 181 | } 182 | 183 | conn, rw, err := hj.Hijack() 184 | 185 | ws := new(Websocket) 186 | ws.session = session 187 | ws.gate = gate 188 | ws.conn = conn 189 | ws.rw = rw 190 | ws.handler = h 191 | ws.maskOutgoing = maskOutgoing 192 | 193 | if _, err := ws.conn.Write(challenge); err != nil { 194 | ws.conn.Close() 195 | return nil, err 196 | } 197 | ws.handler.OnOpen(ws) 198 | return ws, nil 199 | } 200 | 201 | func (ws *Websocket) read(buf []byte) error { 202 | _, err := io.ReadFull(ws.rw, buf) 203 | return err 204 | } 205 | 206 | func (ws *Websocket) SendFrame(fin bool, opcode byte, data []byte) error { 207 | //max frame header may 14 length 208 | buf := make([]byte, 0, len(data)+14) 209 | var finBit, maskBit byte 210 | if fin { 211 | finBit = 0x80 212 | } else { 213 | finBit = 0 214 | } 215 | 216 | buf = append(buf, finBit|opcode) 217 | length := len(data) 218 | if ws.maskOutgoing { 219 | maskBit = 0x80 220 | } else { 221 | maskBit = 0 222 | } 223 | if length < 126 { 224 | buf = append(buf, byte(length)|maskBit) 225 | } else if length < 0xFFFF { 226 | buf = append(buf, 126|maskBit, 0, 0) 227 | binary.BigEndian.PutUint16(buf[len(buf)-2:], uint16(length)) 228 | } else { 229 | buf = append(buf, 127|maskBit, 0, 0, 0, 0, 0, 0, 0, 0) 230 | binary.BigEndian.PutUint64(buf[len(buf)-8:], uint64(length)) 231 | } 232 | 233 | if ws.maskOutgoing { 234 | 235 | } 236 | 237 | buf = append(buf, data...) 238 | ws.rw.Write(buf) 239 | return ws.rw.Flush() 240 | } 241 | 242 | func (ws *Websocket) SendText(data []byte) error { 243 | return ws.SendFrame(true, 0x1, data) 244 | } 245 | 246 | func (ws *Websocket) SendBinary(data []byte) error { 247 | return ws.SendFrame(true, 0x2, data) 248 | } 249 | 250 | func (ws *Websocket) SendPing(data []byte) error { 251 | return ws.SendFrame(true, 0x9, data) 252 | } 253 | 254 | func (ws *Websocket) SendPong(data []byte) error { 255 | return ws.SendFrame(true, 0xA, data) 256 | } 257 | 258 | func (ws *Websocket) Close(code uint16, reason []byte) { 259 | if !ws.serverTerminated { 260 | data := make([]byte, 0, len(reason)+2) 261 | if code == 0 && reason != nil { 262 | code = 1000 263 | } 264 | if code != 0 { 265 | data = append(data, 0, 0) 266 | binary.BigEndian.PutUint16(data, code) 267 | } 268 | if reason != nil { 269 | data = append(data, reason...) 270 | } 271 | ws.SendFrame(true, 0x8, data) 272 | ws.serverTerminated = true 273 | } 274 | if ws.clientTerminated { 275 | ws.conn.Close() 276 | } 277 | 278 | } 279 | 280 | func (ws *Websocket) RecvFrame() (final bool, message []byte, err error) { //text 数据报文 281 | buf := make([]byte, 8, 8) 282 | err = ws.read(buf[:2]) 283 | if err != nil { 284 | return 285 | } 286 | header, payload := buf[0], buf[1] 287 | final = header&0x80 != 0 288 | reservedBits := header&0x70 != 0 289 | frameOpcode := header & 0xf 290 | frameOpcodeIsControl := frameOpcode&0x8 != 0 291 | 292 | if reservedBits { 293 | // client is using as-yet-undefined extensions 294 | err = ErrReservedBits 295 | return 296 | } 297 | 298 | maskFrame := payload&0x80 != 0 299 | payloadlen := uint64(payload & 0x7f) 300 | 301 | if frameOpcodeIsControl && payloadlen >= 126 { 302 | err = ErrFrameOverload 303 | return 304 | } 305 | 306 | if frameOpcodeIsControl && !final { 307 | err = ErrFrameFragmented 308 | return 309 | } 310 | 311 | //解析frame长度 312 | var frameLength uint64 313 | if payloadlen < 126 { 314 | frameLength = payloadlen 315 | } else if payloadlen == 126 { 316 | err = ws.read(buf[:2]) 317 | if err != nil { 318 | return 319 | } 320 | frameLength = uint64(binary.BigEndian.Uint16(buf[:2])) 321 | 322 | } else { //payloadlen == 127 323 | err = ws.read(buf[:8]) 324 | if err != nil { 325 | return 326 | } 327 | frameLength = binary.BigEndian.Uint64(buf[:8]) 328 | } 329 | 330 | frameMask := make([]byte, 4, 4) 331 | if maskFrame { 332 | err = ws.read(frameMask) 333 | if err != nil { 334 | return 335 | } 336 | } 337 | 338 | // fmt.Println("final_frame:", final, "frame_opcode:", frameOpcode, "mask_frame:", maskFrame, "frame_length:", frameLength) 339 | 340 | message = make([]byte, frameLength, frameLength) 341 | if frameLength > 0 { 342 | err = ws.read(message) 343 | if err != nil { 344 | return 345 | } 346 | } 347 | 348 | if maskFrame && frameLength > 0 { 349 | websocketMask(frameMask, message) 350 | } 351 | 352 | if !final { 353 | return 354 | } else { 355 | switch frameOpcode { 356 | case 0x1: //text 357 | case 0x2: //binary 358 | case 0x8: // close 359 | var code uint16 360 | var reason []byte 361 | if frameLength >= 2 { 362 | code = binary.BigEndian.Uint16(message[:2]) 363 | } 364 | if frameLength > 2 { 365 | reason = message[2:] 366 | } 367 | message = nil 368 | ws.clientTerminated = true 369 | ws.Close(0, nil) 370 | ws.handler.OnClose(ws, code, reason) 371 | case 0x9: //ping 372 | message = nil 373 | ws.SendPong(nil) 374 | case 0xA: 375 | ws.handler.OnPong(ws, message) 376 | message = nil 377 | default: 378 | err = ErrInvalidOpcode 379 | } 380 | return 381 | } 382 | 383 | } 384 | 385 | func (ws *Websocket) Recv() ([]byte, error) { 386 | data := make([]byte, 0, 8) 387 | for { 388 | final, message, err := ws.RecvFrame() 389 | if final { 390 | data = append(data, message...) 391 | break 392 | } else { 393 | data = append(data, message...) 394 | } 395 | if err != nil { 396 | return data, err 397 | } 398 | } 399 | if len(data) > 0 { 400 | ws.handler.OnMessage(ws, data) 401 | } 402 | return data, nil 403 | } 404 | 405 | func (ws *Websocket) Start() { 406 | for { 407 | _, err := ws.Recv() 408 | if err != nil { 409 | ws.conn.Close() 410 | } 411 | } 412 | 413 | } 414 | -------------------------------------------------------------------------------- /service/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "cham/cham" 5 | "fmt" 6 | "runtime" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | const ( 12 | LDEBUG = iota 13 | LINFO 14 | LERROR 15 | ) 16 | 17 | const ( 18 | LTIME = 1 << iota 19 | LFILE 20 | LLEVEL 21 | LDEFAULT = LTIME | LLEVEL 22 | LALL = LTIME | LFILE | LLEVEL 23 | ) 24 | 25 | const TimeFormat = "2006/01/02 15:04:05" 26 | 27 | var Names = [3]string{"Debug", "Info", "Error"} 28 | var logs = cham.NewService("log", Start) 29 | 30 | type Logger struct { 31 | level int 32 | flag int 33 | out string 34 | } 35 | 36 | //if out empty, use os.Stdout 37 | func New(out string, flag int, level int) *Logger { 38 | cham.Main.Call(logs, cham.PTYPE_GO, OPEN, out) 39 | return &Logger{level, flag, out} 40 | } 41 | 42 | var std = New("", LDEFAULT, LINFO) 43 | 44 | func (l *Logger) Output(calldepth int, level int, s string) { 45 | if l.level > level { 46 | return 47 | } 48 | buf := make([]byte, 0, len(s)+28) 49 | if l.flag<IME != 0 { 50 | now := time.Now().Format(TimeFormat) 51 | buf = append(buf, now...) 52 | } 53 | if l.flag&LFILE != 0 { 54 | if _, file, line, ok := runtime.Caller(calldepth); ok { 55 | buf = append(buf, file...) 56 | buf = append(buf, ':') 57 | buf = strconv.AppendInt(buf, int64(line), 10) 58 | } 59 | } 60 | if l.flag&LLEVEL != 0 { 61 | buf = append(buf, " ["...) 62 | buf = append(buf, Names[level]...) 63 | buf = append(buf, "] "...) 64 | } 65 | buf = append(buf, s...) 66 | cham.Main.Notify(logs, cham.PTYPE_GO, WRITE, buf) 67 | } 68 | 69 | func (l *Logger) Debug(v ...interface{}) { 70 | l.Output(2, LDEBUG, fmt.Sprint(v...)) 71 | } 72 | 73 | func (l *Logger) Debugln(v ...interface{}) { 74 | l.Output(2, LDEBUG, fmt.Sprintln(v...)) 75 | } 76 | 77 | func (l *Logger) Debugf(f string, v ...interface{}) { 78 | l.Output(2, LDEBUG, fmt.Sprintf(f, v...)) 79 | } 80 | 81 | func (l *Logger) Info(v ...interface{}) { 82 | l.Output(2, LINFO, fmt.Sprint(v...)) 83 | } 84 | 85 | func (l *Logger) Infoln(v ...interface{}) { 86 | l.Output(2, LINFO, fmt.Sprintln(v...)) 87 | } 88 | 89 | func (l *Logger) Infof(f string, v ...interface{}) { 90 | l.Output(2, LINFO, fmt.Sprintf(f, v...)) 91 | } 92 | 93 | func (l *Logger) Error(v ...interface{}) { 94 | l.Output(2, LERROR, fmt.Sprint(v...)) 95 | } 96 | 97 | func (l *Logger) Errorln(v ...interface{}) { 98 | l.Output(2, LERROR, fmt.Sprintln(v...)) 99 | } 100 | 101 | func (l *Logger) Errorf(f string, v ...interface{}) { 102 | l.Output(2, LERROR, fmt.Sprintf(f, v...)) 103 | } 104 | 105 | // for std 106 | func Debug(v ...interface{}) { 107 | std.Output(2, LDEBUG, fmt.Sprint(v...)) 108 | } 109 | 110 | func Debugln(v ...interface{}) { 111 | std.Output(2, LDEBUG, fmt.Sprintln(v...)) 112 | } 113 | 114 | func Debugf(f string, v ...interface{}) { 115 | std.Output(2, LDEBUG, fmt.Sprintf(f, v...)) 116 | } 117 | 118 | func Info(v ...interface{}) { 119 | std.Output(2, LINFO, fmt.Sprint(v...)) 120 | } 121 | 122 | func Infoln(v ...interface{}) { 123 | std.Output(2, LINFO, fmt.Sprintln(v...)) 124 | } 125 | 126 | func Infof(f string, v ...interface{}) { 127 | std.Output(2, LINFO, fmt.Sprintf(f, v...)) 128 | } 129 | 130 | func Error(v ...interface{}) { 131 | std.Output(2, LERROR, fmt.Sprint(v...)) 132 | } 133 | 134 | func Errorln(v ...interface{}) { 135 | std.Output(2, LERROR, fmt.Sprintln(v...)) 136 | } 137 | 138 | func Errorf(f string, v ...interface{}) { 139 | std.Output(2, LERROR, fmt.Sprintf(f, v...)) 140 | } 141 | -------------------------------------------------------------------------------- /service/log/log_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "cham/cham" 5 | "testing" 6 | ) 7 | 8 | func TestLog(t *testing.T) { 9 | Info("hello") 10 | Debug("world") 11 | } 12 | 13 | func TestFileLog(t *testing.T) { 14 | ll := New("log.txt", LDEFAULT, LDEBUG) 15 | ll.Infoln("hello") 16 | ll.Debugln("world") 17 | cham.Main.Call("log", cham.PTYPE_GO, FLUSH) 18 | } 19 | 20 | func TestAllFlagLog(t *testing.T) { 21 | ll := New("log.txt", LALL, LDEBUG) 22 | ll.Infoln("hello") 23 | ll.Debugln("world") 24 | } 25 | -------------------------------------------------------------------------------- /service/log/logservice.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bufio" 5 | "cham/cham" 6 | "io" 7 | "os" 8 | ) 9 | 10 | const ( 11 | OPEN uint8 = iota 12 | WRITE 13 | FLUSH 14 | ) 15 | 16 | type Loggers struct { 17 | outs []io.Writer 18 | } 19 | 20 | func Start(service *cham.Service, args ...interface{}) cham.Dispatch { 21 | loggers := new(Loggers) 22 | return func(session int32, source cham.Address, ptype uint8, args ...interface{}) []interface{} { 23 | cmd := args[0].(uint8) 24 | switch cmd { 25 | case OPEN: 26 | name := args[1].(string) 27 | if name == "" { 28 | loggers.outs = append(loggers.outs, os.Stdout) 29 | } else { 30 | if f, err := os.Create(name); err != nil { 31 | panic("create log file:" + name + ":" + err.Error()) 32 | } else { 33 | loggers.outs = append(loggers.outs, bufio.NewWriter(f)) 34 | } 35 | } 36 | case WRITE: 37 | data := args[1].([]byte) 38 | for _, f := range loggers.outs { 39 | f.Write(data) 40 | } 41 | case FLUSH: 42 | for _, f := range loggers.outs { 43 | if w, ok := f.(*bufio.Writer); ok { 44 | w.Flush() 45 | } 46 | } 47 | } 48 | return cham.NORET 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /service/multicast/channel.go: -------------------------------------------------------------------------------- 1 | package multicast 2 | 3 | import ( 4 | "cham/cham" 5 | // "fmt" 6 | ) 7 | 8 | var multicast *cham.Service 9 | 10 | type Channel struct { 11 | service *cham.Service 12 | Channel uint32 13 | } 14 | 15 | //Channel base on a service, args[0] is N worker to dispatch msg 16 | func New(service *cham.Service, channel uint32, start cham.Start, args ...interface{}) *Channel { 17 | multicast = cham.UniqueService("multicast", Start, args...) 18 | if channel == 0 { 19 | channel = service.Call(multicast, cham.PTYPE_GO, NEW, uint32(0), service.Addr)[0].(uint32) 20 | } 21 | service.RegisterProtocol(cham.PTYPE_MULTICAST, start) 22 | c := &Channel{service, channel} 23 | return c 24 | } 25 | 26 | func (c *Channel) Publish(args ...interface{}) { 27 | v := make([]interface{}, 0, len(args)+3) 28 | v = append(v, PUB, c.Channel, c.service.Addr) 29 | v = append(v, args...) 30 | c.service.Call(multicast, cham.PTYPE_GO, v...) 31 | } 32 | 33 | func (c *Channel) Subscribe() { 34 | c.service.Call(multicast, cham.PTYPE_GO, SUB, c.Channel, c.service.Addr) 35 | } 36 | 37 | func (c *Channel) Unsubscribe() { 38 | c.service.Notify(multicast, cham.PTYPE_GO, UNSUB, c.Channel, c.service.Addr) 39 | } 40 | 41 | func (c *Channel) Delete() { 42 | c.service.Notify(multicast, cham.PTYPE_GO, DEL, c.Channel, cham.Address(0)) 43 | } 44 | -------------------------------------------------------------------------------- /service/multicast/multicast.go: -------------------------------------------------------------------------------- 1 | package multicast 2 | 3 | import ( 4 | "cham/cham" 5 | "cham/service/log" 6 | // "fmt" 7 | "sync/atomic" 8 | ) 9 | 10 | const ( 11 | DEFAULT_GROUP_SIZE = 64 12 | DEFAULT_CHANNEL_SIZE = 16 13 | ) 14 | 15 | const ( 16 | NEW uint8 = iota 17 | SUB 18 | PUB 19 | UNSUB 20 | DEL 21 | ) 22 | 23 | type Multicast struct { 24 | channel uint32 25 | groups map[uint32]map[cham.Address]cham.NULL // channel->set(address) 26 | } 27 | 28 | func (m *Multicast) new(addr cham.Address) uint32 { 29 | ch := atomic.AddUint32(&m.channel, 1) 30 | m.groups[ch] = make(map[cham.Address]cham.NULL, DEFAULT_CHANNEL_SIZE) 31 | return ch 32 | } 33 | 34 | func (m *Multicast) sub(addr cham.Address, ch uint32) { 35 | if _, ok := m.groups[ch]; !ok { 36 | panic("should new a channel before sub a channel") 37 | } 38 | m.groups[ch][addr] = cham.NULLVALUE 39 | } 40 | 41 | func (m *Multicast) unsub(addr cham.Address, ch uint32) { 42 | if _, ok := m.groups[ch]; !ok { 43 | panic("should new a channel before unsub a channel") 44 | } 45 | delete(m.groups[ch], addr) 46 | } 47 | 48 | // multi invode harmless 49 | func (m *Multicast) del(ch uint32) { 50 | delete(m.groups, ch) 51 | } 52 | 53 | func (m *Multicast) pub(addr cham.Address, ch uint32, args ...interface{}) { 54 | peers := m.groups[ch] 55 | if len(peers) > 0 { 56 | data := make([]interface{}, 0, len(args)+1) 57 | data = append(data, ch) 58 | data = append(data, args...) 59 | msg := cham.NewMsg(addr, 0, cham.PTYPE_MULTICAST, data) // args[0] is channel id 60 | for peer := range peers { 61 | cham.Redirect(peer, msg) 62 | } 63 | } 64 | } 65 | 66 | //service self 67 | func Start(service *cham.Service, args ...interface{}) cham.Dispatch { 68 | log.Infoln("New Service ", service.String()) 69 | mul := new(Multicast) 70 | mul.channel = 0 71 | mul.groups = make(map[uint32]map[cham.Address]cham.NULL, DEFAULT_GROUP_SIZE) 72 | 73 | return func(session int32, source cham.Address, ptype uint8, args ...interface{}) []interface{} { 74 | cmd := args[0].(uint8) 75 | channel := args[1].(uint32) 76 | addr := args[2].(cham.Address) 77 | 78 | result := cham.NORET 79 | 80 | switch cmd { 81 | case NEW: 82 | result = cham.Ret(mul.new(addr)) 83 | case SUB: 84 | mul.sub(addr, channel) 85 | case PUB: 86 | mul.pub(addr, channel, args[3:]...) 87 | case UNSUB: 88 | mul.unsub(addr, channel) 89 | case DEL: 90 | mul.del(channel) 91 | } 92 | return result 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /service/multicast/mutlticase_test.go: -------------------------------------------------------------------------------- 1 | package multicast 2 | 3 | import ( 4 | "cham/cham" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func mainStart(service *cham.Service, args ...interface{}) cham.Dispatch { 10 | return func(session int32, source cham.Address, ptype uint8, args ...interface{}) []interface{} { 11 | fmt.Println(service) 12 | fmt.Println(args) 13 | return cham.NORET 14 | } 15 | } 16 | 17 | // args[0] is channel id 18 | func chatStart(service *cham.Service, args ...interface{}) cham.Dispatch { 19 | return func(session int32, source cham.Address, ptype uint8, args ...interface{}) []interface{} { 20 | fmt.Println(service) 21 | fmt.Println(args) 22 | return cham.NORET 23 | } 24 | } 25 | 26 | func channelStart(service *cham.Service, args ...interface{}) cham.Dispatch { 27 | return func(session int32, source cham.Address, ptype uint8, args ...interface{}) []interface{} { 28 | fmt.Println(args) 29 | return cham.NORET 30 | } 31 | } 32 | 33 | func TestMulticast(t *testing.T) { 34 | main := cham.NewService("Leader", mainStart) 35 | chat1 := cham.NewService("chat1", chatStart) 36 | chat2 := cham.NewService("chat2", chatStart) 37 | //create a channel 38 | channel := New(main, 0, channelStart) 39 | //bind a channel 40 | chat1Channel := New(chat1, channel.Channel, channelStart) 41 | chat2Channel := New(chat2, channel.Channel, channelStart) 42 | chat1Channel.Subscribe() 43 | chat2Channel.Subscribe() 44 | channel.Publish("hello world") 45 | chat1Channel.Publish("i am chat1") 46 | chat2Channel.Unsubscribe() 47 | chat2.Stop() // test stop services 48 | channel.Publish("last") 49 | channel.Delete() 50 | 51 | } 52 | --------------------------------------------------------------------------------