├── .gitignore ├── LICENSE ├── README.md ├── README_cn.md ├── README_en.md ├── benchmark ├── client │ └── main.go ├── multi_push │ └── main.go ├── push │ └── main.go ├── push_room │ └── main.go └── push_rooms │ └── main.go ├── comet ├── bucket.go ├── bucket_test.go ├── channel.go ├── client │ ├── client-example.conf │ ├── config.go │ ├── log.xml │ ├── main.go │ ├── proto.go │ ├── tcp.go │ ├── websocket.go │ └── websocket_tls.go ├── comet-log.xml ├── comet.conf ├── config.go ├── errors.go ├── flash_policy.go ├── logic.go ├── main.go ├── operation.go ├── ring.go ├── ring_test.go ├── room.go ├── round.go ├── round_test.go ├── rpc.go ├── server.go ├── signal.go ├── stat.go ├── tcp.go ├── test_test.go ├── ver.go ├── websocket.go └── whitelist.go ├── doc ├── arch.png ├── benchmark-comet.jpg ├── benchmark-flow.jpg ├── benchmark-heap.jpg ├── benchmark.jpg ├── benchmark_cn.md ├── benchmark_en.md ├── connect.gif ├── en │ ├── proto.md │ └── push.md ├── goim.graffle ├── handshake.png ├── proto.md ├── protocol.png ├── push.gif ├── push.md └── token.md ├── examples ├── cert.pem ├── javascript │ ├── client.js │ ├── demo.html │ └── main.go └── private.pem ├── id ├── .timeid.go.swp ├── timeid.go └── timeid_test.go ├── libs ├── bufio │ ├── bufio.go │ └── bufio_test.go ├── bytes │ ├── buffer.go │ ├── buffer_test.go │ └── writer.go ├── crypto │ ├── aes │ │ ├── aes.go │ │ └── aes_test.go │ ├── cipher │ │ └── ecb.go │ ├── padding │ │ ├── padding.go │ │ ├── pkcs5.go │ │ ├── pkcs5_test.go │ │ └── pkcs7.go │ └── rsa │ │ ├── rsa.go │ │ └── rsa_test.go ├── define │ ├── kafka.go │ ├── operation.go │ ├── room.go │ └── user.go ├── encoding │ └── binary │ │ └── endian.go ├── hash │ ├── cityhash │ │ ├── cityhash.go │ │ └── cityhash_test.go │ ├── ketama │ │ ├── ketama.go │ │ └── ketama_test.go │ └── murmurhash3 │ │ ├── mmhash3.go │ │ └── mmhash3_test.go ├── io │ └── ioutil │ │ └── ioutil.go ├── net │ ├── network.go │ └── xrpc │ │ ├── client.go │ │ └── clients.go ├── perf │ └── perf.go ├── proto │ ├── comet.go │ ├── job.go │ ├── logic.go │ ├── message.go │ ├── proto.go │ ├── ret.go │ └── router.go └── time │ ├── debug.go │ ├── timer.go │ └── timer_test.go ├── logic ├── .idea │ ├── compiler.xml │ ├── copyright │ │ └── profiles_settings.xml │ ├── libraries │ │ └── GOPATH__logic_.xml │ ├── logic.iml │ ├── misc.xml │ ├── modules.xml │ └── workspace.xml ├── auth.go ├── config.go ├── counter.go ├── encoder.go ├── errors.go ├── http.go ├── job │ ├── comet.go │ ├── comet_info.go │ ├── config.go │ ├── errors.go │ ├── job-log.xml │ ├── job.conf │ ├── kafka.go │ ├── logic.conf │ ├── main.go │ ├── push.go │ ├── room.go │ ├── round.go │ ├── signal.go │ └── ver.go ├── kafka.go ├── logic-log.xml ├── logic.conf ├── main.go ├── ret.go ├── router.go ├── rpc.go ├── signal.go ├── store.go └── ver.go ├── router ├── bucket.go ├── cleaner.go ├── cleaner_test.go ├── config.go ├── main.go ├── router-log.xml ├── router.conf ├── rpc.go ├── session.go ├── signal.go └── test │ └── router_test.go └── store ├── config.go ├── handle.go ├── http.go ├── log.xml ├── main.go ├── redis.go ├── ret.go ├── rpc.go ├── signal.go ├── storage.go ├── store.conf └── token.go /.gitignore: -------------------------------------------------------------------------------- 1 | # no 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Terry.Mao 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 | Imgo 2 | ============== 3 | Imgo is a distributed and high performance push server written in golang based on [goim](https://github.com/Terry-Mao/goim). 4 | compared to goim,it added offline message support,add will support IM server later on. 5 | 6 | 7 | ## Features 8 | * Light weight and high performance 9 | * Supports single push, multiple push and broadcasting 10 | * Supports one key to multiple subscribers (Configurable maximum subscribers count) 11 | * Supports authentication (Unauthenticated user can't subscribe) 12 | * Supports multiple protocols (WebSocket,TCP) 13 | * Supports offline message (you can push even if user is not online) 14 | * Scalable architecture (Unlimited dynamic comet,logic,router,job modules) 15 | * Asynchronous push notification based on Kafka 16 | 17 | ## Architecture 18 | Client connect to server: 19 | 20 | ![arch](https://github.com/imroc/imgo/blob/master/doc/connect.gif) 21 | 22 | A client wants to subscribe a channel on comet through tcp or websocket,comet tells logic: "Here comes a guy,shall I keep a connection with him ?". 23 | 24 | Logic take the token from comet and showed it to store: "Is this token valid? If it is,tell me the user id". 25 | 26 | Logic got the user id,told router this user is online and keeps a connection with that comet,and told comet:"yes, you shall". 27 | 28 | Then comet keeps connect to that client,and matains a heartbeat with him. 29 | 30 | Logic knowed that comet and client was keep a connection, he ask store:"Is there any offline message of that user ?","yes,three of it",store answered and gave these to logic. 31 | 32 | Logic packed the message into an envelope and thrown to kafka. 33 | 34 | Job found a new envelope in kafka,fetch it and read the address:"comet 1,user 123456",then he told comet 1:"tell this to user 123456". 35 | 36 | At last,comet told this message to user 123456. 37 | 38 | 39 | ------- 40 | 41 | 42 | Server push message to client: 43 | 44 | ![arch](https://github.com/imroc/imgo/blob/master/doc/push.gif) 45 | 46 | Caller(usually a bussiness system) tells logic:"I want to send hello to a person,his user id is 123456". 47 | 48 | Logic got the user id,ask router:"Is user 123456 online ?","yes,he is keeping a connection with comet 1" router replied. 49 | 50 | Logic packed the message into an envelope and thrown to kafka. 51 | 52 | Job found a new envelope in kafka,fetch it and read the address:"comet 1,user 123456",then he told comet 1:"tell this to user 123456". 53 | 54 | At last,Comet told this message to user 123456. 55 | 56 | Protocol: 57 | 58 | [proto](https://github.com/imroc/imgo/blob/master/doc/protocol.png) 59 | 60 | ## Document 61 | [中文](./README_cn.md) 62 | 63 | ## Examples 64 | Websocket: [Websocket Client Demo](https://github.com/imroc/imgo/tree/master/examples/javascript) 65 | 66 | Java: [Java](https://github.com/imroc/imgo-java-sdk) 67 | 68 | ## Benchmark 69 | ![benchmark](./doc/benchmark.jpg) 70 | 71 | ### Benchmark Server 72 | | CPU | Memory | OS | Instance | 73 | | :---- | :---- | :---- | :---- | 74 | | Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz | DDR3 32GB | Debian GNU/Linux 8 | 1 | 75 | 76 | ### Benchmark Case 77 | * Online: 1,000,000 78 | * Duration: 15min 79 | * Push Speed: 40/s (broadcast room) 80 | * Push Message: {"test":1} 81 | * Received calc mode: 1s per times, total 30 times 82 | 83 | ### Benchmark Resource 84 | * CPU: 2000%~2300% 85 | * Memory: 14GB 86 | * GC Pause: 504ms 87 | * Network: Incoming(450MBit/s), Outgoing(4.39GBit/s) 88 | 89 | ### Benchmark Result 90 | * Received: 35,900,000/s 91 | 92 | [中文](./doc/benchmark_cn.md) 93 | 94 | [English](./doc/benchmark_en.md) 95 | 96 | ## LICENSE 97 | imgo is is distributed under the terms of the MIT License. 98 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | imgo 2 | ============== 3 | `imroc/imgo` is a IM and push notification server cluster.You should not use it in production right now,be patient,waiting for a while. 4 | 5 | --------------------------------------- 6 | * [Features](#features) 7 | * [Installing](#installing) 8 | * [Configurations](#configurations) 9 | * [Examples](#examples) 10 | * [Documents](#documents) 11 | * [More](#more) 12 | 13 | --------------------------------------- 14 | 15 | ## Features 16 | * Light weight 17 | * High performance 18 | * Pure Golang 19 | * Supports single push, multiple push, room push and broadcasting 20 | * Supports offline message 21 | * Supports one key to multiple subscribers (Configurable maximum subscribers count) 22 | * Supports heartbeats (Application heartbeats, TCP, KeepAlive) 23 | * Supports authentication (Unauthenticated user can't subscribe) 24 | * Supports multiple protocols (WebSocket,TCP) 25 | * Scalable architecture (Unlimited dynamic job and logic modules) 26 | * Asynchronous push notification based on Kafka 27 | 28 | ## Installing 29 | ### Dependencies 30 | ```sh 31 | $ yum -y install java-1.7.0-openjdk 32 | ``` 33 | 34 | ### Install Kafka 35 | 36 | Please follow the official quick start [here](http://kafka.apache.org/documentation.html#quickstart). 37 | 38 | ### Install redis 39 | ```sh 40 | $ cd /data/programfiles 41 | $ wget http://download.redis.io/releases/redis-2.8.17.tar.gz 42 | $ tar -xvf redis-2.8.17.tar.gz -C ./ 43 | $ cd redis-2.8.17/src 44 | $ make 45 | $ make test 46 | $ make install 47 | $ mkdir /etc/redis 48 | $ cp /data/programfiles/redis-2.8.17/redis.conf /etc/redis/ 49 | $ cp /data/programfiles/redis-2.8.17/src/redis-server /etc/init.d/redis-server 50 | $ /etc/init.d/redis-server /etc/redis/redis.conf 51 | ``` 52 | * if following error, see FAQ 2 53 | ```sh 54 | which: no tclsh8.5 in (/usr/lib64/qt-3.3/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/geffzhang/bin) 55 | You need 'tclsh8.5' in order to run the Redis test 56 | Make[1]: *** [test] error 1 57 | make[1]: Leaving directory ‘/data/program files/redis-2.6.4/src’ 58 | Make: *** [test] error 2! 59 | ``` 60 | 61 | ### Install Golang environment 62 | 63 | Please follow the official quick start [here](https://golang.org/doc/install). 64 | 65 | ### Deploy imgo 66 | 1.Download imgo 67 | ```sh 68 | $ yum install git 69 | $ cd $GOPATH/src 70 | $ git clone https://github.com/imroc/imgo.git 71 | $ cd $GOPATH/src/imgo 72 | $ go get ./... 73 | ``` 74 | 75 | 2.Install router、logic、comet、job modules(You might need to change the configuration files based on your servers) 76 | ```sh 77 | $ cd $GOPATH/src/imgo/router 78 | $ go install 79 | $ cp router-example.conf $GOPATH/bin/router.conf 80 | $ cp router-log.xml $GOPATH/bin/ 81 | $ cd ../message/ 82 | $ go install 83 | $ cp message.conf $GOPATH/bin/message.conf 84 | $ cp message-log.xml $GOPATH/bin/ 85 | $ cd ../logic/ 86 | $ go install 87 | $ cp logic-example.conf $GOPATH/bin/logic.conf 88 | $ cp logic-log.xml $GOPATH/bin/ 89 | $ cd ../comet/ 90 | $ go install 91 | $ cp comet-example.conf $GOPATH/bin/comet.conf 92 | $ cp comet-log.xml $GOPATH/bin/ 93 | $ cd ../logic/job/ 94 | $ go install 95 | $ cp job-example.conf $GOPATH/bin/job.conf 96 | $ cp job-log.xml $GOPATH/bin/ 97 | ``` 98 | 99 | Everything is DONE! 100 | 101 | ### Run imgo 102 | You may need to change the log files location. 103 | ```sh 104 | $ cd /$GOPATH/bin 105 | $ nohup $GOPATH/bin/message -c $GOPATH/bin/message.conf 2>&1 > /data/logs/imgo/panic-message.log & 106 | $ nohup $GOPATH/bin/router -c $GOPATH/bin/router.conf 2>&1 > /data/logs/imgo/panic-router.log & 107 | $ nohup $GOPATH/bin/logic -c $GOPATH/bin/logic.conf 2>&1 > /data/logs/imgo/panic-logic.log & 108 | $ nohup $GOPATH/bin/comet -c $GOPATH/bin/comet.conf 2>&1 > /data/logs/imgo/panic-comet.log & 109 | $ nohup $GOPATH/bin/job -c $GOPATH/bin/job.conf 2>&1 > /data/logs/imgo/panic-job.log & 110 | ``` 111 | 112 | If it fails, please check the logs for debugging. 113 | 114 | ### Testing 115 | 116 | Check the push protocols here[push HTTP protocols](./doc/push.md) 117 | 118 | ## Configurations 119 | TODO 120 | 121 | ## Examples 122 | Websocket: [Websocket Client Demo](https://github.com/imroc/imgo/tree/master/examples/javascript) 123 | 124 | Android: [Android SDK](https://github.com/roamdy/imgo-sdk) 125 | 126 | iOS: [iOS](https://github.com/roamdy/imgo-oc-sdk) 127 | 128 | ## Documents 129 | [push HTTP protocols](./doc/en/push.md) 130 | 131 | [Comet client protocols](./doc/en/proto.md) 132 | 133 | ##More 134 | TODO 135 | -------------------------------------------------------------------------------- /benchmark/multi_push/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Start Command eg : ./multi_push 0 20000 localhost:7172 60 4 | 5 | import ( 6 | "bytes" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "log" 12 | "net" 13 | "net/http" 14 | "os" 15 | "runtime" 16 | "strconv" 17 | "time" 18 | ) 19 | 20 | var ( 21 | lg *log.Logger 22 | httpClient *http.Client 23 | t int 24 | ) 25 | 26 | const TestContent = "{\"test\":1}" 27 | 28 | type pushsBodyMsg struct { 29 | Msg json.RawMessage `json:"m"` 30 | UserIds []int64 `json:"u"` 31 | } 32 | 33 | func init() { 34 | httpTransport := &http.Transport{ 35 | Dial: func(netw, addr string) (net.Conn, error) { 36 | deadline := time.Now().Add(30 * time.Second) 37 | c, err := net.DialTimeout(netw, addr, 20*time.Second) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | c.SetDeadline(deadline) 43 | return c, nil 44 | }, 45 | DisableKeepAlives: false, 46 | } 47 | httpClient = &http.Client{ 48 | Transport: httpTransport, 49 | } 50 | } 51 | 52 | func main() { 53 | runtime.GOMAXPROCS(runtime.NumCPU()) 54 | infoLogfi, err := os.OpenFile("./multi_push.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) 55 | if err != nil { 56 | panic(err) 57 | } 58 | lg = log.New(infoLogfi, "", log.LstdFlags|log.Lshortfile) 59 | 60 | begin, err := strconv.Atoi(os.Args[1]) 61 | if err != nil { 62 | panic(err) 63 | } 64 | length, err := strconv.Atoi(os.Args[2]) 65 | if err != nil { 66 | panic(err) 67 | } 68 | 69 | t, err = strconv.Atoi(os.Args[4]) 70 | if err != nil { 71 | panic(err) 72 | } 73 | 74 | num := runtime.NumCPU() * 8 75 | 76 | l := length / num 77 | b, e := begin, begin+l 78 | time.AfterFunc(time.Duration(t)*time.Second, stop) 79 | for i := 0; i < num; i++ { 80 | go startPush(b, e) 81 | b += l 82 | e += l 83 | } 84 | if b < begin+length { 85 | go startPush(b, begin+length) 86 | } 87 | 88 | time.Sleep(9999 * time.Hour) 89 | } 90 | 91 | func stop() { 92 | os.Exit(-1) 93 | } 94 | 95 | func startPush(b, e int) { 96 | l := make([]int64, 0, e-b) 97 | for i := b; i < e; i++ { 98 | l = append(l, int64(i)) 99 | } 100 | msg := &pushsBodyMsg{Msg: json.RawMessage(TestContent), UserIds: l} 101 | body, err := json.Marshal(msg) 102 | if err != nil { 103 | panic(err) 104 | } 105 | for { 106 | resp, err := httpPost(fmt.Sprintf("http://%s/1/pushs", os.Args[3]), "application/x-www-form-urlencoded", bytes.NewBuffer(body)) 107 | if err != nil { 108 | lg.Printf("post error (%v)", err) 109 | continue 110 | } 111 | 112 | body, err := ioutil.ReadAll(resp.Body) 113 | if err != nil { 114 | lg.Printf("post error (%v)", err) 115 | return 116 | } 117 | resp.Body.Close() 118 | 119 | lg.Printf("response %s", string(body)) 120 | } 121 | } 122 | 123 | func httpPost(url string, contentType string, body io.Reader) (*http.Response, error) { 124 | req, err := http.NewRequest("POST", url, body) 125 | if err != nil { 126 | return nil, err 127 | } 128 | 129 | req.Header.Set("Content-Type", contentType) 130 | resp, err := httpClient.Do(req) 131 | if err != nil { 132 | return nil, err 133 | } 134 | 135 | return resp, nil 136 | } 137 | -------------------------------------------------------------------------------- /benchmark/push/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Start Command eg : ./push 0 20000 localhost:7172 60 4 | 5 | import ( 6 | "bytes" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "log" 12 | "net" 13 | "net/http" 14 | "os" 15 | "runtime" 16 | "strconv" 17 | "time" 18 | ) 19 | 20 | var ( 21 | lg *log.Logger 22 | httpClient *http.Client 23 | t int 24 | ) 25 | 26 | const TestContent = "{\"test\":1}" 27 | 28 | type pushBodyMsg struct { 29 | Msg json.RawMessage `json:"m"` 30 | UserId int64 `json:"u"` 31 | } 32 | 33 | func init() { 34 | httpTransport := &http.Transport{ 35 | Dial: func(netw, addr string) (net.Conn, error) { 36 | deadline := time.Now().Add(30 * time.Second) 37 | c, err := net.DialTimeout(netw, addr, 20*time.Second) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | c.SetDeadline(deadline) 43 | return c, nil 44 | }, 45 | DisableKeepAlives: false, 46 | } 47 | httpClient = &http.Client{ 48 | Transport: httpTransport, 49 | } 50 | } 51 | 52 | func main() { 53 | runtime.GOMAXPROCS(runtime.NumCPU()) 54 | infoLogfi, err := os.OpenFile("./pushs.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) 55 | if err != nil { 56 | panic(err) 57 | } 58 | lg = log.New(infoLogfi, "", log.LstdFlags|log.Lshortfile) 59 | 60 | begin, err := strconv.Atoi(os.Args[1]) 61 | if err != nil { 62 | panic(err) 63 | } 64 | length, err := strconv.Atoi(os.Args[2]) 65 | if err != nil { 66 | panic(err) 67 | } 68 | 69 | t, err = strconv.Atoi(os.Args[4]) 70 | if err != nil { 71 | panic(err) 72 | } 73 | 74 | num := runtime.NumCPU() * 2 75 | lg.Printf("start routine num:%d", num) 76 | 77 | l := length / num 78 | b, e := begin, begin+l 79 | time.AfterFunc(time.Duration(t)*time.Second, stop) 80 | for i := 0; i < num; i++ { 81 | go startPush(b, e) 82 | b += l 83 | e += l 84 | } 85 | if b < begin+length { 86 | go startPush(b, begin+length) 87 | } 88 | 89 | time.Sleep(9999 * time.Hour) 90 | } 91 | 92 | func stop() { 93 | os.Exit(-1) 94 | } 95 | 96 | func startPush(b, e int) { 97 | lg.Printf("start Push from %d to %d", b, e) 98 | bodys := make([][]byte, e-b) 99 | for i := 0; i < e-b; i++ { 100 | msg := &pushBodyMsg{Msg: json.RawMessage(TestContent), UserId: int64(b)} 101 | body, err := json.Marshal(msg) 102 | if err != nil { 103 | panic(err) 104 | } 105 | bodys[i] = body 106 | } 107 | 108 | for { 109 | for i := 0; i < len(bodys); i++ { 110 | resp, err := httpPost(fmt.Sprintf("http://%s/1/pushs", os.Args[3]), "application/x-www-form-urlencoded", bytes.NewBuffer(bodys[i])) 111 | if err != nil { 112 | lg.Printf("post error (%v)", err) 113 | continue 114 | } 115 | 116 | body, err := ioutil.ReadAll(resp.Body) 117 | if err != nil { 118 | lg.Printf("post error (%v)", err) 119 | return 120 | } 121 | resp.Body.Close() 122 | 123 | lg.Printf("response %s", string(body)) 124 | //time.Sleep(50 * time.Millisecond) 125 | } 126 | } 127 | } 128 | 129 | func httpPost(url string, contentType string, body io.Reader) (*http.Response, error) { 130 | req, err := http.NewRequest("POST", url, body) 131 | if err != nil { 132 | return nil, err 133 | } 134 | 135 | req.Header.Set("Content-Type", contentType) 136 | resp, err := httpClient.Do(req) 137 | if err != nil { 138 | return nil, err 139 | } 140 | 141 | return resp, nil 142 | } 143 | -------------------------------------------------------------------------------- /benchmark/push_room/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Start Commond eg: ./push_room 1 20 localhost:7172 4 | // first parameter: room id 5 | // second parameter: num per seconds 6 | // third parameter: logic server ip 7 | 8 | import ( 9 | "bytes" 10 | "fmt" 11 | "io/ioutil" 12 | "net/http" 13 | "os" 14 | "strconv" 15 | "time" 16 | ) 17 | 18 | func main() { 19 | rountineNum, err := strconv.Atoi(os.Args[2]) 20 | if err != nil { 21 | panic(err) 22 | } 23 | addr := os.Args[3] 24 | 25 | gap := time.Second / time.Duration(rountineNum) 26 | delay := time.Duration(0) 27 | 28 | go run(addr, time.Duration(0)*time.Second) 29 | for i := 0; i < rountineNum-1; i++ { 30 | go run(addr, delay) 31 | delay += gap 32 | fmt.Println("delay:", delay) 33 | } 34 | time.Sleep(9999 * time.Hour) 35 | } 36 | 37 | func run(addr string, delay time.Duration) { 38 | time.Sleep(delay) 39 | i := int64(0) 40 | for { 41 | go post(addr, i) 42 | time.Sleep(time.Second) 43 | i++ 44 | } 45 | } 46 | 47 | func post(addr string, i int64) { 48 | resp, err := http.Post("http://"+addr+"/1/push/room?rid="+os.Args[1], "application/json", bytes.NewBufferString(fmt.Sprintf("{\"test\":%d}", i))) 49 | if err != nil { 50 | fmt.Println("Error: http.post() error(%s)", err) 51 | return 52 | } 53 | defer resp.Body.Close() 54 | body, err := ioutil.ReadAll(resp.Body) 55 | if err != nil { 56 | fmt.Println("Error: http.post() error(%s)", err) 57 | return 58 | } 59 | 60 | fmt.Printf("%s postId:%d, response:%s\n", time.Now().Format("2006-01-02 15:04:05"), i, string(body)) 61 | } 62 | -------------------------------------------------------------------------------- /benchmark/push_rooms/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Start Command eg : ./push_rooms 0 20000 localhost:7172 40 4 | // param 1 : the start of room number 5 | // param 2 : the end of room number 6 | // param 3 : comet server tcp address 7 | // param 4 : push amount each goroutines per second 8 | 9 | import ( 10 | "bytes" 11 | "encoding/json" 12 | "fmt" 13 | "io" 14 | "io/ioutil" 15 | "log" 16 | "net" 17 | "net/http" 18 | "os" 19 | "runtime" 20 | "strconv" 21 | "time" 22 | ) 23 | 24 | var ( 25 | lg *log.Logger 26 | httpClient *http.Client 27 | ) 28 | 29 | const TestContent = "{\"test\":1}" 30 | 31 | type pushBodyMsg struct { 32 | Msg json.RawMessage `json:"m"` 33 | UserId int64 `json:"u"` 34 | } 35 | 36 | func init() { 37 | httpTransport := &http.Transport{ 38 | Dial: func(netw, addr string) (net.Conn, error) { 39 | deadline := time.Now().Add(30 * time.Second) 40 | c, err := net.DialTimeout(netw, addr, 20*time.Second) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | c.SetDeadline(deadline) 46 | return c, nil 47 | }, 48 | DisableKeepAlives: false, 49 | } 50 | httpClient = &http.Client{ 51 | Transport: httpTransport, 52 | } 53 | } 54 | 55 | func main() { 56 | runtime.GOMAXPROCS(runtime.NumCPU()) 57 | infoLogfi, err := os.OpenFile("./push_rooms.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) 58 | if err != nil { 59 | panic(err) 60 | } 61 | lg = log.New(infoLogfi, "", log.LstdFlags|log.Lshortfile) 62 | 63 | begin, err := strconv.Atoi(os.Args[1]) 64 | if err != nil { 65 | panic(err) 66 | } 67 | length, err := strconv.Atoi(os.Args[2]) 68 | if err != nil { 69 | panic(err) 70 | } 71 | 72 | num, err := strconv.Atoi(os.Args[4]) 73 | if err != nil { 74 | panic(err) 75 | } 76 | delay := (1000 * time.Millisecond) / time.Duration(num) 77 | 78 | routines := runtime.NumCPU() * 2 79 | lg.Printf("start routine num:%d", routines) 80 | 81 | l := length / routines 82 | b, e := begin, begin+l 83 | for i := 0; i < routines; i++ { 84 | go startPush(b, e, delay) 85 | b += l 86 | e += l 87 | } 88 | if b < begin+length { 89 | go startPush(b, begin+length, delay) 90 | } 91 | 92 | time.Sleep(9999 * time.Hour) 93 | } 94 | 95 | func stop() { 96 | os.Exit(-1) 97 | } 98 | 99 | func startPush(b, e int, delay time.Duration) { 100 | lg.Printf("start Push from %d to %d", b, e) 101 | 102 | for { 103 | for i := b; i < e; i++ { 104 | resp, err := http.Post(fmt.Sprintf("http://%s/1/push/room?rid=%d", os.Args[3], i), "application/json", bytes.NewBufferString(TestContent)) 105 | if err != nil { 106 | lg.Printf("post error (%v)", err) 107 | continue 108 | } 109 | 110 | body, err := ioutil.ReadAll(resp.Body) 111 | if err != nil { 112 | lg.Printf("post error (%v)", err) 113 | return 114 | } 115 | resp.Body.Close() 116 | 117 | lg.Printf("push room:%d response %s", i, string(body)) 118 | time.Sleep(delay) 119 | } 120 | } 121 | } 122 | 123 | func httpPost(url string, contentType string, body io.Reader) (*http.Response, error) { 124 | req, err := http.NewRequest("POST", url, body) 125 | if err != nil { 126 | return nil, err 127 | } 128 | 129 | req.Header.Set("Content-Type", contentType) 130 | resp, err := httpClient.Do(req) 131 | if err != nil { 132 | return nil, err 133 | } 134 | 135 | return resp, nil 136 | } 137 | -------------------------------------------------------------------------------- /comet/bucket.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "imgo/libs/define" 5 | "imgo/libs/proto" 6 | "sync" 7 | "sync/atomic" 8 | ) 9 | 10 | type BucketOptions struct { 11 | ChannelSize int 12 | RoomSize int 13 | RoutineAmount int64 14 | RoutineSize int 15 | } 16 | 17 | // Bucket is a channel holder. 18 | type Bucket struct { 19 | cLock sync.RWMutex // protect the channels for chs 20 | chs map[string]*Channel // map sub key to a channel 21 | boptions BucketOptions 22 | // room 23 | rooms map[int32]*Room // bucket room channels 24 | routines []chan *proto.BoardcastRoomArg 25 | routinesNum int64 26 | } 27 | 28 | // NewBucket new a bucket struct. store the key with im channel. 29 | func NewBucket(boptions BucketOptions) (b *Bucket) { 30 | b = new(Bucket) 31 | b.chs = make(map[string]*Channel, boptions.ChannelSize) 32 | b.boptions = boptions 33 | 34 | //room 35 | b.rooms = make(map[int32]*Room, boptions.RoomSize) 36 | b.routines = make([]chan *proto.BoardcastRoomArg, boptions.RoutineAmount) 37 | b.routinesNum = int64(0) 38 | for i := int64(0); i < boptions.RoutineAmount; i++ { 39 | c := make(chan *proto.BoardcastRoomArg, boptions.RoutineSize) 40 | b.routines[i] = c 41 | go b.roomproc(c) 42 | } 43 | return 44 | } 45 | 46 | // Put put a channel according with sub key. 47 | func (b *Bucket) Put(key string, ch *Channel) (err error) { 48 | var ( 49 | room *Room 50 | ok bool 51 | ) 52 | b.cLock.Lock() 53 | b.chs[key] = ch 54 | if ch.RoomId != define.NoRoom { 55 | if room, ok = b.rooms[ch.RoomId]; !ok { 56 | room = NewRoom(ch.RoomId) 57 | b.rooms[ch.RoomId] = room 58 | } 59 | } 60 | b.cLock.Unlock() 61 | if room != nil { 62 | err = room.Put(ch) 63 | } 64 | return 65 | } 66 | 67 | // Del delete the channel by sub key. 68 | func (b *Bucket) Del(key string) { 69 | var ( 70 | ok bool 71 | ch *Channel 72 | room *Room 73 | ) 74 | b.cLock.Lock() 75 | if ch, ok = b.chs[key]; ok { 76 | delete(b.chs, key) 77 | if ch.RoomId != define.NoRoom { 78 | room, _ = b.rooms[ch.RoomId] 79 | } 80 | } 81 | b.cLock.Unlock() 82 | if room != nil && room.Del(ch) { 83 | // if empty room, must delete from bucket 84 | b.DelRoom(ch.RoomId) 85 | } 86 | } 87 | 88 | // Channel get a channel by sub key. 89 | func (b *Bucket) Channel(key string) (ch *Channel) { 90 | b.cLock.RLock() 91 | ch = b.chs[key] 92 | b.cLock.RUnlock() 93 | return 94 | } 95 | 96 | // Broadcast push msgs to all channels in the bucket. 97 | func (b *Bucket) Broadcast(p *proto.Proto) { 98 | var ch *Channel 99 | b.cLock.RLock() 100 | for _, ch = range b.chs { 101 | // ignore error 102 | ch.Push(p) 103 | } 104 | b.cLock.RUnlock() 105 | } 106 | 107 | // Room get a room by roomid. 108 | func (b *Bucket) Room(rid int32) (room *Room) { 109 | b.cLock.RLock() 110 | room, _ = b.rooms[rid] 111 | b.cLock.RUnlock() 112 | return 113 | } 114 | 115 | // DelRoom delete a room by roomid. 116 | func (b *Bucket) DelRoom(rid int32) { 117 | var room *Room 118 | b.cLock.Lock() 119 | if room, _ = b.rooms[rid]; room != nil { 120 | delete(b.rooms, rid) 121 | } 122 | b.cLock.Unlock() 123 | if room != nil { 124 | room.Close() 125 | } 126 | return 127 | } 128 | 129 | // BroadcastRoom broadcast a message to specified room 130 | func (b *Bucket) BroadcastRoom(arg *proto.BoardcastRoomArg) { 131 | num := atomic.AddInt64(&b.routinesNum, 1) % b.boptions.RoutineAmount 132 | b.routines[num] <- arg 133 | } 134 | 135 | // Rooms get all room id where online number > 0. 136 | func (b *Bucket) Rooms() (res map[int32]struct{}) { 137 | var ( 138 | roomId int32 139 | room *Room 140 | ) 141 | res = make(map[int32]struct{}) 142 | b.cLock.RLock() 143 | for roomId, room = range b.rooms { 144 | if room.Online > 0 { 145 | res[roomId] = struct{}{} 146 | } 147 | } 148 | b.cLock.RUnlock() 149 | return 150 | } 151 | 152 | // roomproc 153 | func (b *Bucket) roomproc(c chan *proto.BoardcastRoomArg) { 154 | for { 155 | var ( 156 | arg *proto.BoardcastRoomArg 157 | room *Room 158 | ) 159 | arg = <-c 160 | if room = b.Room(arg.RoomId); room != nil { 161 | room.Push(&arg.P) 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /comet/bucket_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestBucket(t *testing.T) { 8 | } 9 | -------------------------------------------------------------------------------- /comet/channel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "imgo/libs/bufio" 5 | "imgo/libs/proto" 6 | ) 7 | 8 | // Channel used by message pusher send msg to write goroutine. 9 | type Channel struct { 10 | RoomId int32 11 | CliProto Ring 12 | signal chan *proto.Proto 13 | Writer bufio.Writer 14 | Reader bufio.Reader 15 | Next *Channel 16 | Prev *Channel 17 | } 18 | 19 | func NewChannel(cli, svr int, rid int32) *Channel { 20 | c := new(Channel) 21 | c.RoomId = rid 22 | c.CliProto.Init(cli) 23 | c.signal = make(chan *proto.Proto, svr) 24 | return c 25 | } 26 | 27 | // Push server push message. 28 | func (c *Channel) Push(p *proto.Proto) (err error) { 29 | select { 30 | case c.signal <- p: 31 | default: 32 | } 33 | return 34 | } 35 | 36 | // Ready check the channel ready or close? 37 | func (c *Channel) Ready() *proto.Proto { 38 | return <-c.signal 39 | } 40 | 41 | // Signal send signal to the channel, protocol ready. 42 | func (c *Channel) Signal() { 43 | c.signal <- proto.ProtoReady 44 | } 45 | 46 | // Close close the channel. 47 | func (c *Channel) Close() { 48 | c.signal <- proto.ProtoFinish 49 | } 50 | -------------------------------------------------------------------------------- /comet/client/client-example.conf: -------------------------------------------------------------------------------- 1 | # Client configuration file example 2 | 3 | # Note on units: when memory size is needed, it is possible to specify 4 | # it in the usual form of 1k 5GB 4M and so forth: 5 | # 6 | # 1kb => 1024 bytes 7 | # 1mb => 1024*1024 bytes 8 | # 1gb => 1024*1024*1024 bytes 9 | # 10 | # units are case insensitive so 1GB 1Gb 1gB are all the same. 11 | 12 | # Note on units: when time duration is needed, it is possible to specify 13 | # it in the usual form of 1s 5M 4h and so forth: 14 | # 15 | # 1s => 1000 * 1000 * 1000 nanoseconds 16 | # 1m => 60 seconds 17 | # 1h => 60 minutes 18 | # 19 | # units are case insensitive so 1h 1H are all the same. 20 | 21 | [base] 22 | # When running daemonized, Comet writes a pid file in 23 | # /tmp/comet.pid by default. You can specify a custom pid file 24 | # location here. 25 | pidfile /tmp/comet_client.pid 26 | 27 | # Sets the maximum number of CPUs that can be executing simultaneously. 28 | # This call will go away when the scheduler improves. By default the number of 29 | # logical CPUs is set. 30 | # 31 | # maxproc 4 32 | 33 | # The working directory. 34 | # 35 | # The log will be written inside this directory, with the filename specified 36 | # above using the 'logfile' configuration directive. 37 | # 38 | # Note that you must specify a directory here, not a file name. 39 | dir ./ 40 | 41 | # Log4go configuration xml path. 42 | # 43 | # Examples: 44 | # 45 | # log /xxx/xxx/log.xml 46 | log ./log.xml 47 | 48 | [cert] 49 | # generate certificate command: 50 | # openssl genrsa -out key.pem 2048 51 | # openssl req -new -x509 -key key.pem -out cert.pem -days 3650 52 | cert.file ../../source/cert.pem 53 | 54 | [proto] 55 | # select connections type 56 | # 0: tcp 57 | # 1: websocket 58 | # 2: websocket tls 59 | type 0 60 | 61 | # By default comet listens for connections from all the network interfaces 62 | # available on the server on 6969 port. It is possible to listen to just one or 63 | # multiple interfaces using the "tcp.bind" configuration directive, followed by 64 | # one or more IP addresses and port. 65 | # 66 | # Examples: 67 | # 68 | # Note this directive is only support "tcp" protocol 69 | # tcp.addr 127.0.0.1:6969 70 | # tcp.addr 0.0.0.0:6969 71 | tcp.addr localhost:8080 72 | 73 | websocket.addr localhost:8090 74 | 75 | # SO_SNDBUF and SO_RCVBUF are options to adjust the normal buffer sizes 76 | # allocated for output and input buffers, respectively. The buffer size may 77 | # be increased for high-volume connections, or may be decreased to limit the 78 | # possible backlog of incoming data. The system places an absolute limit on 79 | # these values. 80 | # 81 | # Sets the maximum socket send buffer in bytes. The kernel doubles 82 | # this value (to allow space for bookkeeping overhead) when it is set using 83 | # setsockopt(2). The default value is set by the 84 | # /proc/sys/net/core/wmem_default file and the maximum allowed value is set by 85 | # the /proc/sys/net/core/wmem_max file. The minimum (doubled) value for this 86 | # option is 2048. 87 | sndbuf 2048 88 | 89 | # Sets the maximum socket receive buffer in bytes. The kernel doubles this 90 | # value (to allow space for bookkeeping overhead) when it is set using 91 | # setsockopt(2). The default value is set by the 92 | # /proc/sys/net/core/rmem_default file, and the maximum allowed value is set by 93 | # the /proc/sys/net/core/rmem_max file. The minimum (doubled) value 94 | # for this option is 256. 95 | rcvbuf 256 96 | 97 | [crypto] 98 | # First handshake use rsa encrypt the request. 99 | # set the rsa private key pem file path. 100 | # 101 | # Examples: 102 | # 103 | # rsa.private ./pri.pem 104 | rsa.public ./pub.pem 105 | 106 | [sub] 107 | sub.key 111 108 | -------------------------------------------------------------------------------- /comet/client/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "runtime" 6 | 7 | "github.com/Terry-Mao/goconf" 8 | ) 9 | 10 | var ( 11 | gconf *goconf.Config 12 | Conf *Config 13 | confFile string 14 | ) 15 | 16 | func init() { 17 | flag.StringVar(&confFile, "c", "./client.conf", " set client config file path") 18 | } 19 | 20 | type Config struct { 21 | // base section 22 | PidFile string `goconf:"base:pidfile"` 23 | Dir string `goconf:"base:dir"` 24 | Log string `goconf:"base:log"` 25 | MaxProc int `goconf:"base:maxproc"` 26 | // cert 27 | CertFile string `goconf:"cert:cert.file"` 28 | // proto section 29 | TCPAddr string `goconf:"proto:tcp.addr"` 30 | WebsocketAddr string `goconf:"proto:websocket.addr"` 31 | Sndbuf int `goconf:"proto:sndbuf:memory"` 32 | Rcvbuf int `goconf:"proto:rcvbuf:memory"` 33 | Type int `goconf:"proto:type"` 34 | // sub 35 | SubKey string `goconf:sub:sub.key` 36 | } 37 | 38 | func NewConfig() *Config { 39 | return &Config{ 40 | // base section 41 | PidFile: "/tmp/imgo-client.pid", 42 | Dir: "./", 43 | Log: "./log.xml", 44 | MaxProc: runtime.NumCPU(), 45 | // proto section 46 | TCPAddr: "localhost:8080", 47 | WebsocketAddr: "localhost:8090", 48 | Sndbuf: 2048, 49 | Rcvbuf: 256, 50 | Type: ProtoTCP, 51 | // sub 52 | SubKey: "Terry-Mao", 53 | } 54 | } 55 | 56 | // InitConfig init the global config. 57 | func InitConfig() (err error) { 58 | Conf = NewConfig() 59 | gconf = goconf.New() 60 | if err = gconf.Parse(confFile); err != nil { 61 | return err 62 | } 63 | if err := gconf.Unmarshal(Conf); err != nil { 64 | return err 65 | } 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /comet/client/log.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | stdout 4 | console 5 | 6 | DEBUG 7 | 8 | 9 | debug_file 10 | file 11 | DEBUG 12 | /tmp/comet_client_debug.log 13 | [%D %T] [%L] [%S] %M 14 | true 15 | 0M 16 | 0K 17 | true 18 | 19 | 20 | info_file 21 | file 22 | INFO 23 | /tmp/comet_client_info.log 24 | 35 | [%D %T] [%L] [%S] %M 36 | true 37 | 0M 38 | 0K 39 | true 40 | 41 | 42 | warn_file 43 | file 44 | WARNING 45 | /tmp/comet_client_warn.log 46 | [%D %T] [%L] [%S] %M 47 | true 48 | 0M 49 | 0K 50 | true 51 | 52 | 53 | error_file 54 | file 55 | ERROR 56 | /tmp/comet_client_error.log 57 | [%D %T] [%L] [%S] %M 58 | true 59 | 0M 60 | 0K 61 | true 62 | 63 | 64 | -------------------------------------------------------------------------------- /comet/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "runtime" 6 | 7 | log "github.com/thinkboy/log4go" 8 | ) 9 | 10 | func main() { 11 | flag.Parse() 12 | if err := InitConfig(); err != nil { 13 | panic(err) 14 | } 15 | runtime.GOMAXPROCS(Conf.MaxProc) 16 | log.LoadConfiguration(Conf.Log) 17 | defer log.Close() 18 | if Conf.Type == ProtoTCP { 19 | initTCP() 20 | } else if Conf.Type == ProtoWebsocket { 21 | initWebsocket() 22 | } else if Conf.Type == ProtoWebsocketTLS { 23 | initWebsocketTLS() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /comet/client/proto.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | log "github.com/thinkboy/log4go" 7 | ) 8 | 9 | const ( 10 | OP_HANDSHARE = int32(0) 11 | OP_HANDSHARE_REPLY = int32(1) 12 | OP_HEARTBEAT = int32(2) 13 | OP_HEARTBEAT_REPLY = int32(3) 14 | OP_SEND_SMS = int32(4) 15 | OP_SEND_SMS_REPLY = int32(5) 16 | OP_DISCONNECT_REPLY = int32(6) 17 | OP_AUTH = int32(7) 18 | OP_AUTH_REPLY = int32(8) 19 | OP_TEST = int32(254) 20 | OP_TEST_REPLY = int32(255) 21 | ) 22 | 23 | const ( 24 | rawHeaderLen = int16(16) 25 | ) 26 | 27 | const ( 28 | ProtoTCP = 0 29 | ProtoWebsocket = 1 30 | ProtoWebsocketTLS = 2 31 | ) 32 | 33 | type Proto struct { 34 | Ver int16 `json:"ver"` // protocol version 35 | Operation int32 `json:"op"` // operation for request 36 | SeqId int32 `json:"seq"` // sequence number chosen by client 37 | Body json.RawMessage `json:"body"` // binary body bytes(json.RawMessage is []byte) 38 | } 39 | 40 | func (p *Proto) Print() { 41 | log.Info("\n-------- proto --------\nver: %d\nop: %d\nseq: %d\nbody: %s\n", p.Ver, p.Operation, p.SeqId, string(p.Body)) 42 | } 43 | -------------------------------------------------------------------------------- /comet/client/tcp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "net" 7 | "time" 8 | 9 | log "github.com/thinkboy/log4go" 10 | ) 11 | 12 | func initTCP() { 13 | conn, err := net.Dial("tcp", Conf.TCPAddr) 14 | if err != nil { 15 | log.Error("net.Dial(\"%s\") error(%v)", Conf.TCPAddr, err) 16 | return 17 | } 18 | seqId := int32(0) 19 | wr := bufio.NewWriter(conn) 20 | rd := bufio.NewReader(conn) 21 | proto := new(Proto) 22 | proto.Ver = 1 23 | // auth 24 | // test handshake timeout 25 | // time.Sleep(time.Second * 31) 26 | proto.Operation = OP_AUTH 27 | proto.SeqId = seqId 28 | proto.Body = []byte("test") 29 | if err = tcpWriteProto(wr, proto); err != nil { 30 | log.Error("tcpWriteProto() error(%v)", err) 31 | return 32 | } 33 | if err = tcpReadProto(rd, proto); err != nil { 34 | log.Error("tcpReadProto() error(%v)", err) 35 | return 36 | } 37 | log.Debug("auth ok, proto: %v", proto) 38 | seqId++ 39 | // writer 40 | go func() { 41 | proto1 := new(Proto) 42 | for { 43 | // heartbeat 44 | proto1.Operation = OP_HEARTBEAT 45 | proto1.SeqId = seqId 46 | proto1.Body = nil 47 | if err = tcpWriteProto(wr, proto1); err != nil { 48 | log.Error("tcpWriteProto() error(%v)", err) 49 | return 50 | } 51 | // test heartbeat 52 | //time.Sleep(time.Second * 31) 53 | seqId++ 54 | // op_test 55 | proto1.Operation = OP_TEST 56 | proto1.SeqId = seqId 57 | if err = tcpWriteProto(wr, proto1); err != nil { 58 | log.Error("tcpWriteProto() error(%v)", err) 59 | return 60 | } 61 | seqId++ 62 | time.Sleep(10000 * time.Millisecond) 63 | } 64 | }() 65 | // reader 66 | for { 67 | if err = tcpReadProto(rd, proto); err != nil { 68 | log.Error("tcpReadProto() error(%v)", err) 69 | return 70 | } 71 | if proto.Operation == OP_HEARTBEAT_REPLY { 72 | log.Debug("receive heartbeat") 73 | if err = conn.SetReadDeadline(time.Now().Add(25 * time.Second)); err != nil { 74 | log.Error("conn.SetReadDeadline() error(%v)", err) 75 | return 76 | } 77 | } else if proto.Operation == OP_TEST_REPLY { 78 | log.Debug("body: %s", string(proto.Body)) 79 | } else if proto.Operation == OP_SEND_SMS_REPLY { 80 | log.Debug("body: %s", string(proto.Body)) 81 | } 82 | } 83 | } 84 | 85 | func tcpWriteProto(wr *bufio.Writer, proto *Proto) (err error) { 86 | // write 87 | if err = binary.Write(wr, binary.BigEndian, uint32(rawHeaderLen)+uint32(len(proto.Body))); err != nil { 88 | return 89 | } 90 | if err = binary.Write(wr, binary.BigEndian, rawHeaderLen); err != nil { 91 | return 92 | } 93 | if err = binary.Write(wr, binary.BigEndian, proto.Ver); err != nil { 94 | return 95 | } 96 | if err = binary.Write(wr, binary.BigEndian, proto.Operation); err != nil { 97 | return 98 | } 99 | if err = binary.Write(wr, binary.BigEndian, proto.SeqId); err != nil { 100 | return 101 | } 102 | if proto.Body != nil { 103 | log.Debug("cipher body: %v", proto.Body) 104 | if err = binary.Write(wr, binary.BigEndian, proto.Body); err != nil { 105 | return 106 | } 107 | } 108 | err = wr.Flush() 109 | return 110 | } 111 | 112 | func tcpReadProto(rd *bufio.Reader, proto *Proto) (err error) { 113 | var ( 114 | packLen int32 115 | headerLen int16 116 | ) 117 | // read 118 | if err = binary.Read(rd, binary.BigEndian, &packLen); err != nil { 119 | return 120 | } 121 | log.Debug("packLen: %d", packLen) 122 | if err = binary.Read(rd, binary.BigEndian, &headerLen); err != nil { 123 | return 124 | } 125 | log.Debug("headerLen: %d", headerLen) 126 | if err = binary.Read(rd, binary.BigEndian, &proto.Ver); err != nil { 127 | return 128 | } 129 | log.Debug("ver: %d", proto.Ver) 130 | if err = binary.Read(rd, binary.BigEndian, &proto.Operation); err != nil { 131 | return 132 | } 133 | log.Debug("operation: %d", proto.Operation) 134 | if err = binary.Read(rd, binary.BigEndian, &proto.SeqId); err != nil { 135 | return 136 | } 137 | log.Debug("seqId: %d", proto.SeqId) 138 | var ( 139 | n = int(0) 140 | t = int(0) 141 | bodyLen = int(packLen - int32(headerLen)) 142 | ) 143 | log.Debug("read body len: %d", bodyLen) 144 | if bodyLen > 0 { 145 | proto.Body = make([]byte, bodyLen) 146 | for { 147 | if t, err = rd.Read(proto.Body[n:]); err != nil { 148 | return 149 | } 150 | if n += t; n == bodyLen { 151 | break 152 | } else if n < bodyLen { 153 | } else { 154 | } 155 | } 156 | } else { 157 | proto.Body = nil 158 | } 159 | return 160 | } 161 | -------------------------------------------------------------------------------- /comet/client/websocket.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | 7 | log "github.com/thinkboy/log4go" 8 | "golang.org/x/net/websocket" 9 | ) 10 | 11 | func initWebsocket() { 12 | origin := "http://" + Conf.WebsocketAddr + "/sub" 13 | url := "ws://" + Conf.WebsocketAddr + "/sub" 14 | conn, err := websocket.Dial(url, "", origin) 15 | if err != nil { 16 | log.Error("websocket.Dial(\"%s\") error(%v)", Conf.WebsocketAddr, err) 17 | return 18 | } 19 | proto := new(Proto) 20 | proto.Ver = 1 21 | // auth 22 | // test handshake timeout 23 | // time.Sleep(time.Second * 31) 24 | proto.Operation = OP_AUTH 25 | seqId := int32(0) 26 | proto.SeqId = seqId 27 | proto.Body = []byte("{\"test\":1}") 28 | if err = websocketWriteProto(conn, proto); err != nil { 29 | log.Error("websocketWriteProto() error(%v)", err) 30 | return 31 | } 32 | if err = websocketReadProto(conn, proto); err != nil { 33 | log.Error("websocketReadProto() error(%v)", err) 34 | return 35 | } 36 | log.Debug("auth ok, proto: %v", proto) 37 | seqId++ 38 | // writer 39 | go func() { 40 | proto1 := new(Proto) 41 | for { 42 | // heartbeat 43 | proto1.Operation = OP_HEARTBEAT 44 | proto1.SeqId = seqId 45 | proto1.Body = nil 46 | if err = websocketWriteProto(conn, proto1); err != nil { 47 | log.Error("tcpWriteProto() error(%v)", err) 48 | return 49 | } 50 | // test heartbeat 51 | //time.Sleep(time.Second * 31) 52 | seqId++ 53 | // op_test 54 | proto1.Operation = OP_TEST 55 | proto1.SeqId = seqId 56 | if err = websocketWriteProto(conn, proto1); err != nil { 57 | log.Error("tcpWriteProto() error(%v)", err) 58 | return 59 | } 60 | seqId++ 61 | time.Sleep(10000 * time.Millisecond) 62 | } 63 | }() 64 | // reader 65 | for { 66 | if err = websocketReadProto(conn, proto); err != nil { 67 | log.Error("tcpReadProto() error(%v)", err) 68 | return 69 | } 70 | if proto.Operation == OP_HEARTBEAT_REPLY { 71 | log.Debug("receive heartbeat") 72 | if err = conn.SetReadDeadline(time.Now().Add(25 * time.Second)); err != nil { 73 | log.Error("conn.SetReadDeadline() error(%v)", err) 74 | return 75 | } 76 | } else if proto.Operation == OP_TEST_REPLY { 77 | log.Debug("body: %s", string(proto.Body)) 78 | } else if proto.Operation == OP_SEND_SMS_REPLY { 79 | log.Debug("body: %s", string(proto.Body)) 80 | } 81 | } 82 | } 83 | 84 | func websocketReadProto(conn *websocket.Conn, p *Proto) error { 85 | msg, _ := json.Marshal(p) 86 | log.Debug("%s", string(msg)) 87 | return websocket.JSON.Receive(conn, p) 88 | } 89 | 90 | func websocketWriteProto(conn *websocket.Conn, p *Proto) error { 91 | if p.Body == nil { 92 | p.Body = []byte("{}") 93 | } 94 | return websocket.JSON.Send(conn, p) 95 | } 96 | -------------------------------------------------------------------------------- /comet/client/websocket_tls.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "io/ioutil" 7 | "time" 8 | 9 | log "github.com/thinkboy/log4go" 10 | "golang.org/x/net/websocket" 11 | ) 12 | 13 | func initWebsocketTLS() { 14 | origin := "https://" + Conf.WebsocketAddr + "/sub" 15 | url := "wss://" + Conf.WebsocketAddr + "/sub" 16 | conf, err := websocket.NewConfig(url, origin) 17 | if err != nil { 18 | log.Error("websocket.NewConfig(\"%s\") error(%v)", Conf.WebsocketAddr, err) 19 | return 20 | } 21 | roots := x509.NewCertPool() 22 | certPem, err := ioutil.ReadFile(Conf.CertFile) 23 | if err != nil { 24 | panic(err) 25 | } 26 | ok := roots.AppendCertsFromPEM(certPem) 27 | if !ok { 28 | panic("failed to parse root certificate") 29 | } 30 | 31 | tlsConf := &tls.Config{ 32 | //InsecureSkipVerify: true, 33 | RootCAs: roots, 34 | ServerName: "bili.com", 35 | } 36 | conf.TlsConfig = tlsConf 37 | 38 | conn, err := websocket.DialConfig(conf) 39 | if err != nil { 40 | log.Error("websocket.Dial(\"%s\") error(%v)", Conf.WebsocketAddr, err) 41 | return 42 | } 43 | 44 | proto := new(Proto) 45 | proto.Ver = 1 46 | // auth 47 | // test handshake timeout 48 | // time.Sleep(time.Second * 31) 49 | proto.Operation = OP_AUTH 50 | seqId := int32(0) 51 | proto.SeqId = seqId 52 | proto.Body = []byte("{\"test\":1}") 53 | if err = websocketWriteProto(conn, proto); err != nil { 54 | log.Error("websocketWriteProto() error(%v)", err) 55 | return 56 | } 57 | if err = websocketReadProto(conn, proto); err != nil { 58 | log.Error("websocketReadProto() error(%v)", err) 59 | return 60 | } 61 | log.Debug("auth ok, proto: %v", proto) 62 | seqId++ 63 | // writer 64 | go func() { 65 | proto1 := new(Proto) 66 | for { 67 | // heartbeat 68 | proto1.Operation = OP_HEARTBEAT 69 | proto1.SeqId = seqId 70 | proto1.Body = nil 71 | if err = websocketWriteProto(conn, proto1); err != nil { 72 | log.Error("tcpWriteProto() error(%v)", err) 73 | return 74 | } 75 | // test heartbeat 76 | //time.Sleep(time.Second * 31) 77 | seqId++ 78 | // op_test 79 | proto1.Operation = OP_TEST 80 | proto1.SeqId = seqId 81 | if err = websocketWriteProto(conn, proto1); err != nil { 82 | log.Error("tcpWriteProto() error(%v)", err) 83 | return 84 | } 85 | seqId++ 86 | time.Sleep(10000 * time.Millisecond) 87 | } 88 | }() 89 | // reader 90 | for { 91 | if err = websocketReadProto(conn, proto); err != nil { 92 | log.Error("tcpReadProto() error(%v)", err) 93 | return 94 | } 95 | if proto.Operation == OP_HEARTBEAT_REPLY { 96 | log.Debug("receive heartbeat") 97 | if err = conn.SetReadDeadline(time.Now().Add(25 * time.Second)); err != nil { 98 | log.Error("conn.SetReadDeadline() error(%v)", err) 99 | return 100 | } 101 | } else if proto.Operation == OP_TEST_REPLY { 102 | log.Debug("body: %s", string(proto.Body)) 103 | } else if proto.Operation == OP_SEND_SMS_REPLY { 104 | log.Debug("body: %s", string(proto.Body)) 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /comet/comet-log.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | stdout 4 | console 5 | 6 | DEBUG 7 | 8 | 9 | debug_file 10 | file 11 | DEBUG 12 | /tmp/comet_debug.log 13 | [%D %T] [%L] [%S] %M 14 | true 15 | 0M 16 | 0K 17 | true 18 | 19 | 20 | info_file 21 | file 22 | INFO 23 | /tmp/comet_info.log 24 | 35 | [%D %T] [%L] [%S] %M 36 | true 37 | 0M 38 | 0K 39 | true 40 | 41 | 42 | warn_file 43 | file 44 | WARNING 45 | /tmp/comet_warn.log 46 | [%D %T] [%L] [%S] %M 47 | true 48 | 0M 49 | 0K 50 | true 51 | 52 | 53 | error_file 54 | file 55 | ERROR 56 | /tmp/comet_error.log 57 | [%D %T] [%L] [%S] %M 58 | true 59 | 0M 60 | 0K 61 | true 62 | 63 | 64 | -------------------------------------------------------------------------------- /comet/errors.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | // server 9 | ErrHandshake = errors.New("handshake failed") 10 | ErrOperation = errors.New("request operation not valid") 11 | // ring 12 | ErrRingEmpty = errors.New("ring buffer empty") 13 | ErrRingFull = errors.New("ring buffer full") 14 | // timer 15 | ErrTimerFull = errors.New("timer full") 16 | ErrTimerEmpty = errors.New("timer empty") 17 | ErrTimerNoItem = errors.New("timer item not exist") 18 | // channel 19 | ErrPushMsgArg = errors.New("rpc pushmsg arg error") 20 | ErrPushMsgsArg = errors.New("rpc pushmsgs arg error") 21 | ErrMPushMsgArg = errors.New("rpc mpushmsg arg error") 22 | ErrMPushMsgsArg = errors.New("rpc mpushmsgs arg error") 23 | // room 24 | ErrRoomDroped = errors.New("room droped") 25 | // rpc 26 | ErrLogic = errors.New("logic rpc is not available") 27 | ) 28 | -------------------------------------------------------------------------------- /comet/flash_policy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "imgo/libs/bufio" 5 | "imgo/libs/bytes" 6 | itime "imgo/libs/time" 7 | "net" 8 | 9 | log "github.com/thinkboy/log4go" 10 | ) 11 | 12 | const ( 13 | FlashPolicyRequestLen = len("") 14 | ) 15 | 16 | var ( 17 | FlashPolicyResponse []byte 18 | ) 19 | 20 | // InitFlashPolicy listen all network interface and start accept connections. 21 | func InitFlashPolicy() (err error) { 22 | var ( 23 | listener *net.TCPListener 24 | addr *net.TCPAddr 25 | ) 26 | FlashPolicyResponse = []byte("\n") 27 | FlashPolicyResponse = append(FlashPolicyResponse, 0) 28 | for _, bind := range Conf.FlashPolicyBind { 29 | if addr, err = net.ResolveTCPAddr("tcp4", bind); err != nil { 30 | log.Error("net.ResolveTCPAddr(\"tcp4\", \"%s\") error(%v)", bind, err) 31 | return 32 | } 33 | if listener, err = net.ListenTCP("tcp4", addr); err != nil { 34 | log.Error("net.ListenTCP(\"tcp4\", \"%s\") error(%v)", bind, err) 35 | return 36 | } 37 | log.Debug("start tcp listen: \"%s\"", bind) 38 | // split N core accept 39 | for i := 0; i < Conf.MaxProc; i++ { 40 | go acceptFlashPolicy(DefaultServer, listener) 41 | } 42 | } 43 | return 44 | } 45 | 46 | // Accept accepts connections on the listener and serves requests 47 | // for each incoming connection. Accept blocks; the caller typically 48 | // invokes it in a go statement. 49 | func acceptFlashPolicy(server *Server, lis *net.TCPListener) { 50 | var ( 51 | conn *net.TCPConn 52 | err error 53 | r int 54 | ) 55 | for { 56 | if conn, err = lis.AcceptTCP(); err != nil { 57 | // if listener close then return 58 | log.Error("listener.Accept(\"%s\") error(%v)", lis.Addr().String(), err) 59 | return 60 | } 61 | if err = conn.SetWriteBuffer(Conf.TCPRcvbuf); err != nil { 62 | log.Error("conn.SetWriteBuffer() error(%v)", err) 63 | return 64 | } 65 | go serveFlashPolicy(server, conn, r) 66 | if r++; r == maxInt { 67 | r = 0 68 | } 69 | } 70 | } 71 | 72 | func serveFlashPolicy(server *Server, conn *net.TCPConn, r int) { 73 | var ( 74 | // timer 75 | tr = server.round.Timer(r) 76 | rp = server.round.Reader(r) 77 | wp = server.round.Writer(r) 78 | // ip addr 79 | lAddr = conn.LocalAddr().String() 80 | rAddr = conn.RemoteAddr().String() 81 | ) 82 | if Debug { 83 | log.Debug("start tcp flash policy serve \"%s\" with \"%s\"", lAddr, rAddr) 84 | } 85 | 86 | server.serverFlashPolicy(conn, rp, wp, tr, rAddr) 87 | } 88 | 89 | func (server *Server) serverFlashPolicy(conn *net.TCPConn, rp, wp *bytes.Pool, tr *itime.Timer, rAddr string) { 90 | var ( 91 | rr bufio.Reader 92 | wr bufio.Writer 93 | rb = rp.Get() 94 | wb = wp.Get() 95 | ) 96 | rr.ResetBuffer(conn, rb.Bytes()) 97 | wr.ResetBuffer(conn, wb.Bytes()) 98 | 99 | trd := tr.Add(server.Options.HandshakeTimeout, func() { //安全协议设置超时时间 100 | conn.Close() 101 | }) 102 | _, err := rr.Pop(FlashPolicyRequestLen) 103 | if err != nil { 104 | log.Error("rr.Pop() error(%v)", err) 105 | goto failed 106 | } 107 | _, err = wr.Write(FlashPolicyResponse) 108 | if err != nil { 109 | log.Error("wr.Write() error(%v)", err) 110 | goto failed 111 | } 112 | wr.Flush() 113 | if Conf.Debug { 114 | log.Debug("remote ip:%s write a flash safe proto succeed", rAddr) 115 | } 116 | 117 | failed: 118 | tr.Del(trd) 119 | conn.Close() 120 | rp.Put(rb) 121 | wp.Put(wb) 122 | } 123 | -------------------------------------------------------------------------------- /comet/logic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | inet "imgo/libs/net" 5 | "imgo/libs/net/xrpc" 6 | "imgo/libs/proto" 7 | "strings" 8 | "time" 9 | 10 | log "github.com/thinkboy/log4go" 11 | ) 12 | 13 | var ( 14 | logicRpcClient *xrpc.Clients 15 | logicRpcQuit = make(chan struct{}, 1) 16 | 17 | logicService = "RPC" 18 | logicServicePing = "RPC.Ping" 19 | logicServiceConnect = "RPC.Connect" 20 | logicServiceDisconnect = "RPC.Disconnect" 21 | ) 22 | 23 | func InitLogicRpc(addrs []string) (err error) { 24 | var ( 25 | bind string 26 | network, addr string 27 | rpcOptions []xrpc.ClientOptions 28 | ) 29 | for _, bind = range addrs { 30 | if network, addr, err = inet.ParseNetwork(bind); err != nil { 31 | log.Error("inet.ParseNetwork() error(%v)", err) 32 | return 33 | } 34 | options := xrpc.ClientOptions{ 35 | Proto: network, 36 | Addr: addr, 37 | } 38 | rpcOptions = append(rpcOptions, options) 39 | } 40 | // rpc clients 41 | logicRpcClient = xrpc.Dials(rpcOptions) 42 | // ping & reconnect 43 | logicRpcClient.Ping(logicServicePing) 44 | log.Info("init logic rpc: %v", rpcOptions) 45 | return 46 | } 47 | 48 | func connect(p *proto.Proto) (key string, rid int32, heartbeat time.Duration, err error) { 49 | token := string(p.Body) 50 | token = strings.Trim(token, "\"") 51 | var ( 52 | arg = proto.ConnArg{Token: token, Server: Conf.ServerId} 53 | reply = proto.ConnReply{} 54 | ) 55 | if err = logicRpcClient.Call(logicServiceConnect, &arg, &reply); err != nil { 56 | log.Error("c.Call(\"%s\", \"%v\", &ret) error(%v)", logicServiceConnect, arg, err) 57 | return 58 | } 59 | key = reply.Key 60 | rid = reply.RoomId 61 | heartbeat = 5 * 60 * time.Second 62 | return 63 | } 64 | 65 | func disconnect(key string, roomId int32) (has bool, err error) { 66 | var ( 67 | arg = proto.DisconnArg{Key: key, RoomId: roomId} 68 | reply = proto.DisconnReply{} 69 | ) 70 | if err = logicRpcClient.Call(logicServiceDisconnect, &arg, &reply); err != nil { 71 | return 72 | } 73 | has = reply.Has 74 | return 75 | } 76 | -------------------------------------------------------------------------------- /comet/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "imgo/libs/perf" 6 | "runtime" 7 | 8 | log "github.com/thinkboy/log4go" 9 | ) 10 | 11 | var ( 12 | DefaultServer *Server 13 | DefaultWhitelist *Whitelist 14 | Debug bool 15 | ) 16 | 17 | func main() { 18 | flag.Parse() 19 | if err := InitConfig(); err != nil { 20 | panic(err) 21 | } 22 | Debug = Conf.Debug 23 | runtime.GOMAXPROCS(Conf.MaxProc) 24 | log.LoadConfiguration(Conf.Log) 25 | defer log.Close() 26 | log.Info("comet[%s] start", Ver) 27 | // white list log 28 | if wl, err := NewWhitelist(Conf.WhiteLog, Conf.Whitelist); err != nil { 29 | panic(err) 30 | } else { 31 | DefaultWhitelist = wl 32 | } 33 | perf.Init(Conf.PprofBind) 34 | // logic rpc 35 | if err := InitLogicRpc(Conf.LogicAddrs); err != nil { 36 | log.Warn("logic rpc current can't connect, retry") 37 | } 38 | // new server 39 | buckets := make([]*Bucket, Conf.Bucket) 40 | for i := 0; i < Conf.Bucket; i++ { 41 | buckets[i] = NewBucket(BucketOptions{ 42 | ChannelSize: Conf.BucketChannel, 43 | RoomSize: Conf.BucketRoom, 44 | RoutineAmount: Conf.RoutineAmount, 45 | RoutineSize: Conf.RoutineSize, 46 | }) 47 | } 48 | round := NewRound(RoundOptions{ 49 | Reader: Conf.TCPReader, 50 | ReadBuf: Conf.TCPReadBuf, 51 | ReadBufSize: Conf.TCPReadBufSize, 52 | Writer: Conf.TCPWriter, 53 | WriteBuf: Conf.TCPWriteBuf, 54 | WriteBufSize: Conf.TCPWriteBufSize, 55 | Timer: Conf.Timer, 56 | TimerSize: Conf.TimerSize, 57 | }) 58 | operator := new(DefaultOperator) 59 | DefaultServer = NewServer(buckets, round, operator, ServerOptions{ 60 | CliProto: Conf.CliProto, 61 | SvrProto: Conf.SvrProto, 62 | HandshakeTimeout: Conf.HandshakeTimeout, 63 | TCPKeepalive: Conf.TCPKeepalive, 64 | TCPRcvbuf: Conf.TCPRcvbuf, 65 | TCPSndbuf: Conf.TCPSndbuf, 66 | }) 67 | // white list 68 | // tcp comet 69 | if err := InitTCP(Conf.TCPBind, Conf.MaxProc); err != nil { 70 | panic(err) 71 | } 72 | // websocket comet 73 | if err := InitWebsocket(Conf.WebsocketBind); err != nil { 74 | panic(err) 75 | } 76 | // flash safe policy 77 | if Conf.FlashPolicyOpen { 78 | if err := InitFlashPolicy(); err != nil { 79 | panic(err) 80 | } 81 | } 82 | // wss comet 83 | if Conf.WebsocketTLSOpen { 84 | if err := InitWebsocketWithTLS(Conf.WebsocketTLSBind, Conf.WebsocketCertFile, Conf.WebsocketPrivateFile); err != nil { 85 | panic(err) 86 | } 87 | } 88 | // start rpc 89 | if err := InitRPCPush(Conf.RPCPushAddrs); err != nil { 90 | panic(err) 91 | } 92 | // block until a signal is received. 93 | InitSignal() 94 | } 95 | -------------------------------------------------------------------------------- /comet/operation.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "imgo/libs/define" 5 | "imgo/libs/proto" 6 | "time" 7 | 8 | log "github.com/thinkboy/log4go" 9 | ) 10 | 11 | type Operator interface { 12 | // Operate process the common operation such as send message etc. 13 | Operate(*proto.Proto) error 14 | // Connect used for auth user and return a subkey, roomid, hearbeat. 15 | Connect(*proto.Proto) (string, int32, time.Duration, error) 16 | // Disconnect used for revoke the subkey. 17 | Disconnect(string, int32) error 18 | } 19 | 20 | type DefaultOperator struct { 21 | } 22 | 23 | func (operator *DefaultOperator) Operate(p *proto.Proto) error { 24 | var ( 25 | body []byte 26 | ) 27 | if p.Operation == define.OP_SEND_SMS { 28 | // call suntao's api 29 | // p.Body = nil 30 | p.Operation = define.OP_SEND_SMS_REPLY 31 | log.Info("send sms proto: %v", p) 32 | } else if p.Operation == define.OP_TEST { 33 | log.Debug("test operation: %s", body) 34 | p.Operation = define.OP_TEST_REPLY 35 | p.Body = []byte("{\"test\":\"come on\"}") 36 | } else { 37 | return ErrOperation 38 | } 39 | return nil 40 | } 41 | 42 | func (operator *DefaultOperator) Connect(p *proto.Proto) (key string, rid int32, heartbeat time.Duration, err error) { 43 | key, rid, heartbeat, err = connect(p) 44 | return 45 | } 46 | 47 | func (operator *DefaultOperator) Disconnect(key string, rid int32) (err error) { 48 | var has bool 49 | if has, err = disconnect(key, rid); err != nil { 50 | return 51 | } 52 | if !has { 53 | log.Warn("disconnect key: \"%s\" not exists", key) 54 | } 55 | return 56 | } 57 | -------------------------------------------------------------------------------- /comet/ring.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "imgo/libs/proto" 5 | 6 | log "github.com/thinkboy/log4go" 7 | ) 8 | 9 | type Ring struct { 10 | // read 11 | rp uint64 12 | num uint64 13 | mask uint64 14 | // TODO split cacheline, many cpu cache line size is 64 15 | // pad [40]byte 16 | // write 17 | wp uint64 18 | data []proto.Proto 19 | } 20 | 21 | func NewRing(num int) *Ring { 22 | r := new(Ring) 23 | r.init(uint64(num)) 24 | return r 25 | } 26 | 27 | func (r *Ring) Init(num int) { 28 | r.init(uint64(num)) 29 | } 30 | 31 | func (r *Ring) init(num uint64) { 32 | // 2^N 33 | if num&(num-1) != 0 { 34 | for num&(num-1) != 0 { 35 | num &= (num - 1) 36 | } 37 | num = num << 1 38 | } 39 | r.data = make([]proto.Proto, num) 40 | r.num = num 41 | r.mask = r.num - 1 42 | } 43 | 44 | func (r *Ring) Get() (proto *proto.Proto, err error) { 45 | if r.rp == r.wp { 46 | return nil, ErrRingEmpty 47 | } 48 | proto = &r.data[r.rp&r.mask] 49 | return 50 | } 51 | 52 | func (r *Ring) GetAdv() { 53 | r.rp++ 54 | if Debug { 55 | log.Debug("ring rp: %d, idx: %d", r.rp, r.rp&r.mask) 56 | } 57 | } 58 | 59 | func (r *Ring) Set() (proto *proto.Proto, err error) { 60 | if r.wp-r.rp >= r.num { 61 | return nil, ErrRingFull 62 | } 63 | proto = &r.data[r.wp&r.mask] 64 | return 65 | } 66 | 67 | func (r *Ring) SetAdv() { 68 | r.wp++ 69 | if Debug { 70 | log.Debug("ring wp: %d, idx: %d", r.wp, r.wp&r.mask) 71 | } 72 | } 73 | 74 | func (r *Ring) Reset() { 75 | r.rp = 0 76 | r.wp = 0 77 | // prevent pad compiler optimization 78 | // r.pad = [40]byte{} 79 | } 80 | -------------------------------------------------------------------------------- /comet/ring_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestRing(t *testing.T) { 8 | r := NewRing(3) // aligned to 4 9 | p0, err := r.Set() 10 | if err != nil { 11 | t.Error(err) 12 | t.FailNow() 13 | } 14 | p0.SeqId = 10 15 | r.SetAdv() 16 | p1, err := r.Set() 17 | if err != nil { 18 | t.Error(err) 19 | t.FailNow() 20 | } 21 | p1.SeqId = 11 22 | r.SetAdv() 23 | p2, err := r.Set() 24 | if err != nil { 25 | t.Error(err) 26 | t.FailNow() 27 | } 28 | p2.SeqId = 12 29 | r.SetAdv() 30 | p3, err := r.Set() 31 | if err != nil { 32 | t.Error(err) 33 | t.FailNow() 34 | } 35 | p3.SeqId = 13 36 | r.SetAdv() 37 | p4, err := r.Set() 38 | if err != ErrRingFull || p4 != nil { 39 | t.Error(err) 40 | t.FailNow() 41 | } 42 | p0, err = r.Get() 43 | if err != nil && p0.SeqId != 10 { 44 | t.Error(err) 45 | t.FailNow() 46 | } 47 | r.GetAdv() 48 | p1, err = r.Get() 49 | if err != nil && p1.SeqId != 11 { 50 | t.Error(err) 51 | t.FailNow() 52 | } 53 | r.GetAdv() 54 | p2, err = r.Get() 55 | if err != nil && p2.SeqId != 12 { 56 | t.Error(err) 57 | t.FailNow() 58 | } 59 | r.GetAdv() 60 | p3, err = r.Get() 61 | if err != nil && p3.SeqId != 13 { 62 | t.Error(err) 63 | t.FailNow() 64 | } 65 | r.GetAdv() 66 | p4, err = r.Get() 67 | if err != ErrRingEmpty || p4 != nil { 68 | t.Error(err) 69 | t.FailNow() 70 | } 71 | p0, err = r.Set() 72 | if err != nil { 73 | t.Error(err) 74 | t.FailNow() 75 | } 76 | p0.SeqId = 10 77 | r.SetAdv() 78 | p1, err = r.Set() 79 | if err != nil { 80 | t.Error(err) 81 | t.FailNow() 82 | } 83 | p1.SeqId = 11 84 | r.SetAdv() 85 | p2, err = r.Set() 86 | if err != nil { 87 | t.Error(err) 88 | t.FailNow() 89 | } 90 | p2.SeqId = 12 91 | r.SetAdv() 92 | p3, err = r.Set() 93 | if err != nil { 94 | t.Error(err) 95 | t.FailNow() 96 | } 97 | r.SetAdv() 98 | p4, err = r.Set() 99 | if err != ErrRingFull || p4 != nil { 100 | t.Error(err) 101 | t.FailNow() 102 | } 103 | p0, err = r.Get() 104 | if err != nil && p0.SeqId != 10 { 105 | t.Error(err) 106 | t.FailNow() 107 | } 108 | r.GetAdv() 109 | p1, err = r.Get() 110 | if err != nil && p1.SeqId != 11 { 111 | t.Error(err) 112 | t.FailNow() 113 | } 114 | r.GetAdv() 115 | p2, err = r.Get() 116 | if err != nil && p2.SeqId != 12 { 117 | t.Error(err) 118 | t.FailNow() 119 | } 120 | r.GetAdv() 121 | p3, err = r.Get() 122 | if err != nil { 123 | t.Error(err) 124 | t.FailNow() 125 | } 126 | r.GetAdv() 127 | p4, err = r.Get() 128 | if err != ErrRingEmpty || p4 != nil { 129 | t.Error(err) 130 | t.FailNow() 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /comet/room.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "imgo/libs/proto" 5 | "sync" 6 | ) 7 | 8 | type Room struct { 9 | id int32 10 | rLock sync.RWMutex 11 | next *Channel 12 | drop bool 13 | Online int // dirty read is ok 14 | } 15 | 16 | // NewRoom new a room struct, store channel room info. 17 | func NewRoom(id int32) (r *Room) { 18 | r = new(Room) 19 | r.id = id 20 | r.drop = false 21 | r.next = nil 22 | r.Online = 0 23 | return 24 | } 25 | 26 | // Put put channel into the room. 27 | func (r *Room) Put(ch *Channel) (err error) { 28 | r.rLock.Lock() 29 | if !r.drop { 30 | if r.next != nil { 31 | r.next.Prev = ch 32 | } 33 | ch.Next = r.next 34 | ch.Prev = nil 35 | r.next = ch // insert to header 36 | r.Online++ 37 | } else { 38 | err = ErrRoomDroped 39 | } 40 | r.rLock.Unlock() 41 | return 42 | } 43 | 44 | // Del delete channel from the room. 45 | func (r *Room) Del(ch *Channel) bool { 46 | r.rLock.Lock() 47 | if ch.Next != nil { 48 | // if not footer 49 | ch.Next.Prev = ch.Prev 50 | } 51 | if ch.Prev != nil { 52 | // if not header 53 | ch.Prev.Next = ch.Next 54 | } else { 55 | r.next = ch.Next 56 | } 57 | r.Online-- 58 | r.drop = (r.Online == 0) 59 | r.rLock.Unlock() 60 | return r.drop 61 | } 62 | 63 | // Push push msg to the room, if chan full discard it. 64 | func (r *Room) Push(p *proto.Proto) { 65 | r.rLock.RLock() 66 | for ch := r.next; ch != nil; ch = ch.Next { 67 | ch.Push(p) 68 | } 69 | r.rLock.RUnlock() 70 | return 71 | } 72 | 73 | // Close close the room. 74 | func (r *Room) Close() { 75 | r.rLock.RLock() 76 | for ch := r.next; ch != nil; ch = ch.Next { 77 | ch.Close() 78 | } 79 | r.rLock.RUnlock() 80 | } 81 | -------------------------------------------------------------------------------- /comet/round.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "imgo/libs/bytes" 5 | "imgo/libs/time" 6 | ) 7 | 8 | type RoundOptions struct { 9 | Timer int 10 | TimerSize int 11 | Reader int 12 | ReadBuf int 13 | ReadBufSize int 14 | Writer int 15 | WriteBuf int 16 | WriteBufSize int 17 | } 18 | 19 | // Ronnd userd for connection round-robin get a reader/writer/timer for split big lock. 20 | type Round struct { 21 | readers []bytes.Pool 22 | writers []bytes.Pool 23 | timers []time.Timer 24 | options RoundOptions 25 | readerIdx int 26 | writerIdx int 27 | timerIdx int 28 | } 29 | 30 | // NewRound new a round struct. 31 | func NewRound(options RoundOptions) (r *Round) { 32 | var i int 33 | r = new(Round) 34 | r.options = options 35 | // reader 36 | r.readers = make([]bytes.Pool, options.Reader) 37 | for i = 0; i < options.Reader; i++ { 38 | r.readers[i].Init(options.ReadBuf, options.ReadBufSize) 39 | } 40 | // writer 41 | r.writers = make([]bytes.Pool, options.Writer) 42 | for i = 0; i < options.Writer; i++ { 43 | r.writers[i].Init(options.WriteBuf, options.WriteBufSize) 44 | } 45 | // timer 46 | r.timers = make([]time.Timer, options.Timer) 47 | for i = 0; i < options.Timer; i++ { 48 | r.timers[i].Init(options.TimerSize) 49 | } 50 | return 51 | } 52 | 53 | // Timer get a timer. 54 | func (r *Round) Timer(rn int) *time.Timer { 55 | return &(r.timers[rn%r.options.Timer]) 56 | } 57 | 58 | // Reader get a reader memory buffer. 59 | func (r *Round) Reader(rn int) *bytes.Pool { 60 | return &(r.readers[rn%r.options.Reader]) 61 | } 62 | 63 | // Writer get a writer memory buffer pool. 64 | func (r *Round) Writer(rn int) *bytes.Pool { 65 | return &(r.writers[rn%r.options.Writer]) 66 | } 67 | -------------------------------------------------------------------------------- /comet/round_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestRound(t *testing.T) { 8 | r := NewRound(10, 10, 2, 10) 9 | t0 := r.Timer(0) 10 | if t0 == nil { 11 | t.FailNow() 12 | } 13 | t1 := r.Timer(1) 14 | if t1 == nil { 15 | t.FailNow() 16 | } 17 | t2 := r.Timer(2) 18 | if t2 == nil { 19 | t.FailNow() 20 | } 21 | if t0 != t2 { 22 | t.FailNow() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /comet/rpc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | inet "imgo/libs/net" 5 | "imgo/libs/proto" 6 | "net" 7 | "net/rpc" 8 | 9 | log "github.com/thinkboy/log4go" 10 | ) 11 | 12 | func InitRPCPush(addrs []string) (err error) { 13 | var ( 14 | bind string 15 | network, addr string 16 | c = &PushRPC{} 17 | ) 18 | rpc.Register(c) 19 | for _, bind = range addrs { 20 | if network, addr, err = inet.ParseNetwork(bind); err != nil { 21 | log.Error("inet.ParseNetwork() error(%v)", err) 22 | return 23 | } 24 | go rpcListen(network, addr) 25 | } 26 | return 27 | } 28 | 29 | func rpcListen(network, addr string) { 30 | l, err := net.Listen(network, addr) 31 | if err != nil { 32 | log.Error("net.Listen(\"%s\", \"%s\") error(%v)", network, addr, err) 33 | panic(err) 34 | } 35 | // if process exit, then close the rpc addr 36 | defer func() { 37 | log.Info("listen rpc: \"%s\" close", addr) 38 | if err := l.Close(); err != nil { 39 | log.Error("listener.Close() error(%v)", err) 40 | } 41 | }() 42 | rpc.Accept(l) 43 | } 44 | 45 | // Push RPC 46 | type PushRPC struct { 47 | } 48 | 49 | func (this *PushRPC) Ping(arg *proto.NoArg, reply *proto.NoReply) error { 50 | return nil 51 | } 52 | 53 | // Push push a message to a specified sub key 54 | func (this *PushRPC) PushMsg(arg *proto.PushMsgArg, reply *proto.NoReply) (err error) { 55 | var ( 56 | bucket *Bucket 57 | channel *Channel 58 | ) 59 | if arg == nil { 60 | err = ErrPushMsgArg 61 | return 62 | } 63 | bucket = DefaultServer.Bucket(arg.Key) 64 | if channel = bucket.Channel(arg.Key); channel != nil { 65 | err = channel.Push(&arg.P) 66 | } 67 | return 68 | } 69 | 70 | // Push push a message to a specified sub key 71 | func (this *PushRPC) MPushMsg(arg *proto.MPushMsgArg, reply *proto.MPushMsgReply) (err error) { 72 | var ( 73 | bucket *Bucket 74 | channel *Channel 75 | key string 76 | n int 77 | ) 78 | reply.Index = -1 79 | if arg == nil { 80 | err = ErrMPushMsgArg 81 | return 82 | } 83 | for n, key = range arg.Keys { 84 | bucket = DefaultServer.Bucket(key) 85 | if channel = bucket.Channel(key); channel != nil { 86 | if err = channel.Push(&arg.P); err != nil { 87 | return 88 | } 89 | reply.Index = int32(n) 90 | } 91 | } 92 | return 93 | } 94 | 95 | // MPushMsgs push msgs to multiple user. 96 | func (this *PushRPC) MPushMsgs(arg *proto.MPushMsgsArg, reply *proto.MPushMsgsReply) (err error) { 97 | var ( 98 | bucket *Bucket 99 | channel *Channel 100 | n int32 101 | PMArg *proto.PushMsgArg 102 | ) 103 | reply.Index = -1 104 | if arg == nil { 105 | err = ErrMPushMsgsArg 106 | return 107 | } 108 | for _, PMArg = range arg.PMArgs { 109 | bucket = DefaultServer.Bucket(PMArg.Key) 110 | if channel = bucket.Channel(PMArg.Key); channel != nil { 111 | if err = channel.Push(&PMArg.P); err != nil { 112 | return 113 | } 114 | n++ 115 | reply.Index = n 116 | } 117 | } 118 | return 119 | } 120 | 121 | // Broadcast broadcast msg to all user. 122 | func (this *PushRPC) Broadcast(arg *proto.BoardcastArg, reply *proto.NoReply) (err error) { 123 | var bucket *Bucket 124 | for _, bucket = range DefaultServer.Buckets { 125 | go bucket.Broadcast(&arg.P) 126 | } 127 | return 128 | } 129 | 130 | // Broadcast broadcast msg to specified room. 131 | func (this *PushRPC) BroadcastRoom(arg *proto.BoardcastRoomArg, reply *proto.NoReply) (err error) { 132 | var bucket *Bucket 133 | for _, bucket = range DefaultServer.Buckets { 134 | bucket.BroadcastRoom(arg) 135 | } 136 | return 137 | } 138 | 139 | func (this *PushRPC) Rooms(arg *proto.NoArg, reply *proto.RoomsReply) (err error) { 140 | var ( 141 | roomId int32 142 | bucket *Bucket 143 | roomIds []int32 144 | ) 145 | for _, bucket = range DefaultServer.Buckets { 146 | for roomId, _ = range bucket.Rooms() { 147 | roomIds = append(roomIds, roomId) 148 | } 149 | } 150 | reply.RoomIds = roomIds 151 | return 152 | } 153 | -------------------------------------------------------------------------------- /comet/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "imgo/libs/hash/cityhash" 5 | "time" 6 | 7 | log "github.com/thinkboy/log4go" 8 | ) 9 | 10 | var ( 11 | maxInt = 1<<31 - 1 12 | emptyJSONBody = []byte("{}") 13 | ) 14 | 15 | type ServerOptions struct { 16 | CliProto int 17 | SvrProto int 18 | HandshakeTimeout time.Duration 19 | TCPKeepalive bool 20 | TCPRcvbuf int 21 | TCPSndbuf int 22 | } 23 | 24 | type Server struct { 25 | Buckets []*Bucket // subkey bucket 26 | bucketIdx uint32 27 | round *Round // accept round store 28 | operator Operator 29 | Options ServerOptions 30 | } 31 | 32 | // NewServer returns a new Server. 33 | func NewServer(b []*Bucket, r *Round, o Operator, options ServerOptions) *Server { 34 | s := new(Server) 35 | s.Buckets = b 36 | s.bucketIdx = uint32(len(b)) 37 | s.round = r 38 | s.operator = o 39 | s.Options = options 40 | return s 41 | } 42 | 43 | func (server *Server) Bucket(subKey string) *Bucket { 44 | idx := cityhash.CityHash32([]byte(subKey), uint32(len(subKey))) % server.bucketIdx 45 | if Debug { 46 | log.Debug("\"%s\" hit channel bucket index: %d use cityhash", subKey, idx) 47 | } 48 | return server.Buckets[idx] 49 | } 50 | -------------------------------------------------------------------------------- /comet/signal.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "syscall" 7 | 8 | log "github.com/thinkboy/log4go" 9 | ) 10 | 11 | // InitSignal register signals handler. 12 | func InitSignal() { 13 | c := make(chan os.Signal, 1) 14 | signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGSTOP) 15 | for { 16 | s := <-c 17 | log.Info("comet[%s] get a signal %s", Ver, s.String()) 18 | switch s { 19 | case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGINT: 20 | return 21 | case syscall.SIGHUP: 22 | reload() 23 | default: 24 | return 25 | } 26 | } 27 | } 28 | 29 | func reload() { 30 | newConf, err := ReloadConfig() 31 | if err != nil { 32 | log.Error("ReloadConfig() error(%v)", err) 33 | return 34 | } 35 | Conf = newConf 36 | } 37 | -------------------------------------------------------------------------------- /comet/stat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // TODO 4 | // bucket 5 | // channel 6 | // ring 7 | // timer 8 | // push 9 | // bufio 10 | // operation 11 | -------------------------------------------------------------------------------- /comet/test_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func init() { 4 | Conf = new(Config) 5 | } 6 | -------------------------------------------------------------------------------- /comet/ver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | Ver = "0.1" 5 | ) 6 | -------------------------------------------------------------------------------- /comet/whitelist.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | type Whitelist struct { 10 | Log *log.Logger 11 | list map[string]struct{} // whitelist for debug 12 | } 13 | 14 | // NewWhitelist a whitelist struct. 15 | func NewWhitelist(file string, list []string) (w *Whitelist, err error) { 16 | var ( 17 | key string 18 | f *os.File 19 | ) 20 | if f, err = os.OpenFile(file, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0644); err == nil { 21 | w = new(Whitelist) 22 | w.Log = log.New(f, "", log.LstdFlags) 23 | w.list = make(map[string]struct{}) 24 | for _, key = range list { 25 | w.list[key] = struct{}{} 26 | } 27 | } 28 | return 29 | } 30 | 31 | // Contains whitelist contains a key or not. 32 | func (w *Whitelist) Contains(key string) (ok bool) { 33 | if ix := strings.Index(key, "_"); ix > -1 { 34 | _, ok = w.list[key[:ix]] 35 | } 36 | return 37 | } 38 | -------------------------------------------------------------------------------- /doc/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imroc/imgo/2e78ecb2c1d201cc6d0a16e0f0cacefe925ad26c/doc/arch.png -------------------------------------------------------------------------------- /doc/benchmark-comet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imroc/imgo/2e78ecb2c1d201cc6d0a16e0f0cacefe925ad26c/doc/benchmark-comet.jpg -------------------------------------------------------------------------------- /doc/benchmark-flow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imroc/imgo/2e78ecb2c1d201cc6d0a16e0f0cacefe925ad26c/doc/benchmark-flow.jpg -------------------------------------------------------------------------------- /doc/benchmark-heap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imroc/imgo/2e78ecb2c1d201cc6d0a16e0f0cacefe925ad26c/doc/benchmark-heap.jpg -------------------------------------------------------------------------------- /doc/benchmark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imroc/imgo/2e78ecb2c1d201cc6d0a16e0f0cacefe925ad26c/doc/benchmark.jpg -------------------------------------------------------------------------------- /doc/benchmark_cn.md: -------------------------------------------------------------------------------- 1 | ## 压测图表 2 | ![benchmark](benchmark.jpg) 3 | 4 | ### 服务端配置 5 | | CPU | 内存 | 操作系统 | 数量 | 6 | | :---- | :---- | :---- | :---- | 7 | | Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz | DDR3 32GB | Debian GNU/Linux 8 | 1 | 8 | 9 | ### 压测参数 10 | * 不同UID同房间在线人数: 1,000,000 11 | * 持续推送时长: 15分钟 12 | * 持续推送数量: 40条/秒 13 | * 推送内容: {"test":1} 14 | * 推送类型: 单房间推送 15 | * 到达计算方式: 1秒统计一次,共30次 16 | 17 | ### 资源使用 18 | * 每台服务端CPU使用: 2000%~2300%(刚好满负载) 19 | * 每台服务端内存使用: 14GB左右 20 | * GC耗时: 504毫秒左右 21 | * 流量使用: Incoming(450MBit/s), Outgoing(4.39GBit/s) 22 | 23 | ### 压测结果 24 | * 推送到达: 3590万/秒左右; 25 | 26 | ## comet模块 27 | ![benchmark-comet](benchmark-comet.jpg) 28 | 29 | ## 流量 30 | ![benchmark-flow](benchmark-flow.jpg) 31 | 32 | ## heap信息(包含GC) 33 | ![benchmark-flow](benchmark-heap.jpg) 34 | -------------------------------------------------------------------------------- /doc/benchmark_en.md: -------------------------------------------------------------------------------- 1 | ## Benchmark Chart 2 | ![benchmark](benchmark.jpg) 3 | 4 | ### Benchmark Server 5 | | CPU | Memory | OS | Instance | 6 | | :---- | :---- | :---- | :---- | 7 | | Intel(R) Xeon(R) CPU E5-2630 v2 @ 2.60GHz | DDR3 32GB | Debian GNU/Linux 8 | 1 | 8 | 9 | ### Benchmark Case 10 | * Online: 1,000,000 11 | * Duration: 15min 12 | * Push Speed: 40/s (broadcast room) 13 | * Push Message: {"test":1} 14 | * Received calc mode: 1s per times, total 30 times 15 | 16 | ### Benchmark Resource 17 | 18 | * CPU: 2000%~2300% 19 | * Memory: 14GB 20 | * GC Pause: 504ms 21 | * Network: Incoming(450MBit/s), Outgoing(4.39GBit/s) 22 | 23 | ### Benchmark Result 24 | * Received: 35,900,000/s 25 | 26 | ## Comet 27 | ![benchmark-comet](benchmark-comet.jpg) 28 | 29 | ## Network traffic 30 | ![benchmark-flow](benchmark-flow.jpg) 31 | 32 | ## Heap (include GC) 33 | ![benchmark-flow](benchmark-heap.jpg) 34 | -------------------------------------------------------------------------------- /doc/connect.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imroc/imgo/2e78ecb2c1d201cc6d0a16e0f0cacefe925ad26c/doc/connect.gif -------------------------------------------------------------------------------- /doc/en/proto.md: -------------------------------------------------------------------------------- 1 | # comet and clients protocols 2 | comet supports two protocols to communicate with client: WebSocket, TCP 3 | 4 | ## websocket 5 | **Request URL** 6 | 7 | ws://DOMAIN/sub 8 | 9 | **HTTP Request Method** 10 | 11 | WebSocket (JSON Frame). Response is same as the request. 12 | 13 | **Response Result** 14 | 15 | ```json 16 | { 17 | "ver": 102, 18 | "op": 10, 19 | "seq": 10, 20 | "body": {"data": "xxx"} 21 | } 22 | ``` 23 | 24 | **Request and Response Parameters** 25 | 26 | | parameter | is required | type | comment| 27 | | :----- | :--- | :--- | :--- | 28 | | ver | true | int | Protocol version | 29 | | op | true | int | Operation | 30 | | seq | true | int | Sequence number (Server returned number maps to client sent) | 31 | | body | json | The JSON message pushed | 32 | 33 | ## tcp 34 | **Request URL** 35 | 36 | tcp://DOMAIN 37 | 38 | **Protocol** 39 | 40 | Binary. Response is same as the request. 41 | 42 | **Request and Response Parameters** 43 | 44 | | parameter | is required | type | comment| 45 | | :----- | :--- | :--- | :--- | 46 | | package length | true | int32 bigendian | package length | 47 | | header Length | true | int16 bigendian | header length | 48 | | ver | true | int16 bigendian | Protocol version | 49 | | operation | true | int32 bigendian | Operation | 50 | | seq | true | int32 bigendian | jsonp callback | 51 | | body | false | binary | $(package lenth) - $(header length) | 52 | 53 | ## Operations 54 | | operation | comment | 55 | | :----- | :--- | 56 | | 2 | Client send heartbeat| 57 | | 3 | Server reply heartbeat| 58 | | 7 | authentication request | 59 | | 8 | authentication response | 60 | 61 | -------------------------------------------------------------------------------- /doc/en/push.md: -------------------------------------------------------------------------------- 1 |

imroc/imgo push HTTP protocols

2 | push HTTP interface protocols for pusher 3 | 4 |

Interfaces

5 | | Name | URL | HTTP method | 6 | | :---- | :---- | :---- | 7 | | [single push](#single push) | /1/push | POST | 8 | | [multiple push](#multiple push) | /1/pushs | POST | 9 | | [room push](#room push) | /1/push/room | POST | 10 | | [broadcasting](#broadcasting) | /1/push/all | POST | 11 | 12 |

Public response body

13 | 14 | | response code | description | 15 | | :---- | :---- | 16 | | 1 | success | 17 | | 65535 | internal error | 18 | 19 |

Response structure

20 |
21 | {
22 |     "ret": 1  //response code
23 | }
24 | 
25 | 26 | 27 | ##### single push 28 | * Example request 29 | 30 | ```sh 31 | # uid is the user id pushing to?uid=0 32 | curl -d "{\"test\":1}" http://127.0.0.1:7172/1/push?uid=0 33 | ``` 34 | 35 | * Response 36 | 37 |
38 | {
39 |     "ret": 1
40 | }
41 | 
42 | 43 | ##### Multiple push 44 | * Example request 45 | 46 | ```sh 47 | curl -d "{\"u\":[1,2,3,4,5],\"m\":{\"test\":1}}" http://127.0.0.1:7172/1/pushs 48 | ``` 49 | 50 | * Response 51 | 52 |
53 | {
54 |     "ret": 1
55 | }
56 | 
57 | 58 | ##### room push 59 | * Example request 60 | 61 | ```sh 62 | curl -d "{\"test\": 1}" http://127.0.0.1:7172/1/push/room?rid=1 63 | ``` 64 | 65 | * Response 66 | 67 |
68 | {
69 |     "ret": 1
70 | }
71 | 
72 | 73 | ##### Broadcasting 74 | * Example request 75 | 76 | ```sh 77 | curl -d "{\"test\": 1}" http://127.0.0.1:7172/1/push/all 78 | ``` 79 | 80 | * Response 81 | 82 |
83 | {
84 |     "ret": 1
85 | }
86 | 
87 | -------------------------------------------------------------------------------- /doc/goim.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imroc/imgo/2e78ecb2c1d201cc6d0a16e0f0cacefe925ad26c/doc/goim.graffle -------------------------------------------------------------------------------- /doc/handshake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imroc/imgo/2e78ecb2c1d201cc6d0a16e0f0cacefe925ad26c/doc/handshake.png -------------------------------------------------------------------------------- /doc/proto.md: -------------------------------------------------------------------------------- 1 | # comet 客户端通讯协议文档 2 | comet支持两种协议和客户端通讯 websocket, tcp。 3 | 4 | ## websocket 5 | **请求URL** 6 | 7 | ws://DOMAIN/sub 8 | 9 | **HTTP请求方式** 10 | 11 | Websocket(JSON Frame),请求和返回协议一致 12 | 13 | **请求和返回json** 14 | 15 | ```json 16 | { 17 | "ver": 102, 18 | "op": 10, 19 | "seq": 10, 20 | "body": {"data": "xxx"} 21 | } 22 | ``` 23 | 24 | **请求和返回参数说明** 25 | 26 | | 参数名 | 必选 | 类型 | 说明 | 27 | | :----- | :--- | :--- | :--- | 28 | | ver | true | int | 协议版本号 | 29 | | op | true | int | 指令 | 30 | | seq | true | int | 序列号(服务端返回和客户端发送一一对应) | 31 | | body | true | string | 授权令牌,用于检验获取用户真实用户Id | 32 | 33 | ## tcp 34 | **请求URL** 35 | 36 | tcp://DOMAIN 37 | 38 | **协议格式** 39 | 40 | 二进制,请求和返回协议一致 41 | 42 | **请求&返回参数** 43 | 44 | | 参数名 | 必选 | 类型 | 说明 | 45 | | :----- | :--- | :--- | :--- | 46 | | package length | true | int32 bigendian | 包长度 | 47 | | header Length | true | int16 bigendian | 包头长度 | 48 | | ver | true | int16 bigendian | 协议版本 | 49 | | operation | true | int32 bigendian | 协议指令 | 50 | | seq | true | int32 bigendian | 序列号 | 51 | | body | false | binary | $(package lenth) - $(header length) | 52 | 53 | ## 指令 54 | | 指令 | 说明 | 55 | | :----- | :--- | 56 | | 2 | 客户端请求心跳 | 57 | | 3 | 服务端心跳答复 | 58 | | 5 | 下行消息 | 59 | | 7 | auth认证 | 60 | | 8 | auth认证返回 | 61 | 62 | -------------------------------------------------------------------------------- /doc/protocol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imroc/imgo/2e78ecb2c1d201cc6d0a16e0f0cacefe925ad26c/doc/protocol.png -------------------------------------------------------------------------------- /doc/push.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imroc/imgo/2e78ecb2c1d201cc6d0a16e0f0cacefe925ad26c/doc/push.gif -------------------------------------------------------------------------------- /doc/push.md: -------------------------------------------------------------------------------- 1 |

imroc/imgo push http协议文档

2 | push http接口文档,用于推送接口接入 3 | 4 |

接口汇总

5 | | 接口名 | URL | 访问方式 | 6 | | :---- | :---- | :---- | 7 | | [单人推送](#单人推送) | /1/push | POST | 8 | | [单消息多人推送](#单消息多人推送) | /1/pushs | POST | 9 | | [房间推送](#房间推送) | /1/push/room | POST | 10 | | [广播](#广播) | /1/push/all | POST | 11 | 12 |

公共返回码

13 | 14 | | 错误码 | 描述 | 15 | | :---- | :---- | 16 | | 1 | 成功 | 17 | | 65535 | 内部错误 | 18 | 19 |

基本返回结构

20 |
21 | {
22 |     "ret": 1  //错误码
23 | }
24 | 
25 | 26 | 27 | ##### 单人推送 28 | * 请求例子 29 | 30 | ```sh 31 | # uid 表示推送的用户id 32 | curl -d "{\"test\":1}" http://127.0.0.1:7172/1/push?uid=0 33 | ``` 34 | 35 | * 返回 36 | 37 |
38 | {
39 |     "ret": 1
40 | }
41 | 
42 | 43 | ##### 单消息多人推送 44 | * 请求例子 45 | 46 | ```sh 47 | curl -d "{\"u\":[1,2,3,4,5],\"m\":{\"test\":1}}" http://127.0.0.1:7172/1/pushs 48 | ``` 49 | 50 | * 返回 51 | 52 |
53 | {
54 |     "ret": 1
55 | }
56 | 
57 | 58 | ##### 房间推送 59 | * 请求例子 60 | 61 | ```sh 62 | curl -d "{\"test\": 1}" http://127.0.0.1:7172/1/push/room?rid=1 63 | ``` 64 | 65 | * 返回 66 | 67 |
68 | {
69 |     "ret": 1
70 | }
71 | 
72 | 73 | ##### 广播 74 | * 请求例子 75 | 76 | ```sh 77 | curl -d "{\"test\": 1}" http://127.0.0.1:7172/1/push/all 78 | ``` 79 | 80 | * 返回 81 | 82 |
83 | {
84 |     "ret": 1
85 | }
86 | 
87 | 88 | 89 | -------------------------------------------------------------------------------- /doc/token.md: -------------------------------------------------------------------------------- 1 |

imroc/imgo 添加token协议文档

2 | 添加token接口文档,用于校验用户身份 3 | 4 |

接口地址

5 | | 接口名 | URL | 访问方式 | 6 | | :---- | :---- | :---- | 7 | | 添加token | /1/admin/token/new | POST | 8 | 9 |

返回码

10 | | 错误码 | 描述 | 11 | | :---- | :---- | 12 | | 1 | 成功 | 13 | | 65535 | 内部错误 | 14 | | 65534 | 参数错误 | 15 | 16 |

基本返回结构

17 |
18 | {
19 |     "ret": 1  //错误码
20 | }
21 | 
22 | 23 |

例子

24 | ```sh 25 | # uid 表示该token对应的用户id,expire表示该token的存留时长,单位为秒 26 | curl -d "{\"uid\":10086,\"token\":\"JKF67897FDS325sdkfJK\",\"expire\":123456}" http://127.0.0.1:7172/1/admin/token/new 27 | ``` 28 | * 返回 29 |
30 | {
31 |     "ret": 1
32 | }
33 | 
34 | 35 | -------------------------------------------------------------------------------- /examples/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID3TCCAsWgAwIBAgIJAKWU8wETRh4fMA0GCSqGSIb3DQEBBQUAMIGEMQswCQYD 3 | VQQGEwJjbjERMA8GA1UECAwIc2hhbmdoYWkxETAPBgNVBAcMCHNoYW5naGFpMQ0w 4 | CwYDVQQKDARiaWxpMQ0wCwYDVQQLDARiaWxpMREwDwYDVQQDDAhiaWxpLmNvbTEe 5 | MBwGCSqGSIb3DQEJARYPMjI0MzAzNTRAcXEuY29tMB4XDTE1MDkwMTEyMDQxMloX 6 | DTI1MDgyOTEyMDQxMlowgYQxCzAJBgNVBAYTAmNuMREwDwYDVQQIDAhzaGFuZ2hh 7 | aTERMA8GA1UEBwwIc2hhbmdoYWkxDTALBgNVBAoMBGJpbGkxDTALBgNVBAsMBGJp 8 | bGkxETAPBgNVBAMMCGJpbGkuY29tMR4wHAYJKoZIhvcNAQkBFg8yMjQzMDM1NEBx 9 | cS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKFwSxJqYPxzMe 10 | m5PeYA4YmcUsDCqS9Z7PsszOMZ1YsWZIHMB74D49ad2R+9PoqlfNH1L9C4NFSBrF 11 | rhSkaLmFYxw9yeJ2EAPijASBgfxMFVrEJhu7SW86OPTVnHblU8UqQdnMFOqF49C9 12 | mCdbiGu/99BZVCL1WmlSQCWVEIzOgX+goxqHuwXUF58YUwr6WLtF0DuBcLUai1vB 13 | Pg+PJ2fLjSR2o0KJkPOd6+y90cgoxfyJFUHuUKyV8EU4VwHEIA9rVizprziqPx6c 14 | 9A9Ng0FpA2leSPLGYCjnDtKIOvbSOS8DMkRT55ujqoVrj0yiNWsuJlc/NbD6bS16 15 | fJjuLOtJAgMBAAGjUDBOMB0GA1UdDgQWBBQzxdSIYIkDABh98Cj6VeYasC7/STAf 16 | BgNVHSMEGDAWgBQzxdSIYIkDABh98Cj6VeYasC7/STAMBgNVHRMEBTADAQH/MA0G 17 | CSqGSIb3DQEBBQUAA4IBAQA1Fhr+SU62xHWlPOBhTbjod49+mNfXn2TZz/vBp/Jl 18 | pHZgDLAEcrhXHmi2A0G9K9+qOIEn4BvTd70jSYvYlaeUSzZ/nEpeM0oE0f2Qaxov 19 | PhxDpsqPsSQm6pE64/los1doaiElfMVFaP56UGV01kFdI013wxwd2WCuj51Hmvi9 20 | thsS027aqxjHMJnKXPvBm2E6EDkPfc/e+AEmwBzry+aamRizaMrk/SfSGTy9/rvd 21 | +VbBfHiJ50kMld51SLIc6qkVaTXess7mIfcsk7kyjP4eFA0y+3wmXfRZeWadND3I 22 | O9XNNwsDVFXlhW40GUVriy95qa1Sq3sLUfUQcCH4VFFK 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /examples/javascript/client.js: -------------------------------------------------------------------------------- 1 | (function(win) { 2 | var Client = function(options) { 3 | var MAX_CONNECT_TIME = 10; 4 | var DELAY = 15000; 5 | this.options = options || {}; 6 | this.createConnect(MAX_CONNECT_TIME, DELAY); 7 | } 8 | 9 | Client.prototype.createConnect = function(max, delay) { 10 | var self = this; 11 | if (max === 0) { 12 | return; 13 | } 14 | connect(); 15 | 16 | var heartbeatInterval; 17 | 18 | function connect() { 19 | var ws = new WebSocket('ws://114.215.174.73:8090/sub'); 20 | var auth = false; 21 | 22 | ws.onopen = function() { 23 | getAuth(); 24 | } 25 | 26 | ws.onmessage = function(evt) { 27 | var receives = JSON.parse(evt.data) 28 | for(var i=0; i 2 | 3 | 4 | 5 | 6 | client demo 7 | 8 | 9 |

client demo

10 | 11 | 20 | 获取离线消息 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/javascript/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | // Simple static webserver: 10 | log.Fatal(http.ListenAndServe(":1999", http.FileServer(http.Dir("./")))) 11 | } 12 | -------------------------------------------------------------------------------- /examples/private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEAyhcEsSamD8czHpuT3mAOGJnFLAwqkvWez7LMzjGdWLFmSBzA 3 | e+A+PWndkfvT6KpXzR9S/QuDRUgaxa4UpGi5hWMcPcnidhAD4owEgYH8TBVaxCYb 4 | u0lvOjj01Zx25VPFKkHZzBTqhePQvZgnW4hrv/fQWVQi9VppUkAllRCMzoF/oKMa 5 | h7sF1BefGFMK+li7RdA7gXC1GotbwT4Pjydny40kdqNCiZDznevsvdHIKMX8iRVB 6 | 7lCslfBFOFcBxCAPa1Ys6a84qj8enPQPTYNBaQNpXkjyxmAo5w7SiDr20jkvAzJE 7 | U+ebo6qFa49MojVrLiZXPzWw+m0tenyY7izrSQIDAQABAoIBAQCNasAwy2/nmKjw 8 | IUS/l44lru1oXncocdMZWvCw1c1a9IEzs1MLHKfRSBTyBDyNEy7v7pyfUQAiaku5 9 | y5DMYDB65BkuL+lWXuypCvxYOEL6ZvMmUdiUHdZE8vh5xsz4u788S+qCQpy+5uX6 10 | 1s+r4PIt2tekuxjfgs4y7YqfHn66PmxAWXTV+jnWpUWYjpRTgDWeU6ikbk2fHjvG 11 | ytF4AV4lEKNnGBC5F+5lk3QJKjb1IW/o5q42TCeERk1k5Ruc5kfzq72hdFYrQAF1 12 | PmstEXMExMS/K6AerYtPfiWWFWH2gCTZMf/C8Jwr0zPVpHM1PF+Ien3IkgiOTfZE 13 | 1+/wrx4BAoGBAOnpnxFnnqhZdDRi+DALfY3mXPwOizFYnAhuuuzO87zoZsps529E 14 | j5pEZQoYPTiww5rptjFhNjoV0gsh2GO5QHCiMxM8A76aWVc4YmK0GiHVudpAu2t+ 15 | aK8+0Xr03SA27cKJjp12NWijzEdTjSQYaFwD/8/dMrU3MCu30kdEJ6iJAoGBAN0s 16 | JXLv4CtB/DLTvifTZ/OAGSc1/Z+X0w2EqIugw7IV9YuAm2721IFhsFFXeANRRHY6 17 | zonLUEgsuQUc30NCz+susChs2GMEFypLem8D1c5ZZY+9sIsR1WFB97o7bTMvp5lH 18 | 2REpEDZKXVOB7UmrclLgTKRQgG3O73rMP6QXYPzBAoGBAJm8Gvi0crlQuagol9fz 19 | 5Vwa2GgtItyW0U5VgHNdfSJeWBiYxO8DT6Jja0jcL3iP7K9nBYCk1KAOcVMxtmes 20 | fKbKY+kzW36tMSS7ASbAGiC8uH6yZru6hBERp1o5jw+6Kj/eaqYg5+9TIFKMnknn 21 | 5Mb9NecnCUnC8Nz63rBKIgqJAoGBAJHUpeyfFaPwIiYxT1RbJFN9xxf/lXdBWDu1 22 | mJxYKDCoIfsVlWcZAQ0+KE+56LvnPcjnBX/9urWcJ3KjkuJ6jzV211gQTK0c6VlN 23 | 4zCHytYAQ+L/JATOgW9bW8hDnsD9TvjWUt3pwXLKnbaOGLNWhE747g/5tHSy2VyS 24 | h/PeJmkBAoGABMfEaiLHXXQhUK0BPYxT3T8i9IjAYwXlrgSLlnvOGslZef/45kP5 25 | CM1UbMSwAn+HvAziOFt2WmynFCysy/lCyTud+Fd/IZFcMThp7wvi7fSZo0NDM7ES 26 | 9JfgTmCY4Kwv6kT85poIka9bp4Nh47EVB9kDoqm/lSMkfYqcWH66DJA= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /id/.timeid.go.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imroc/imgo/2e78ecb2c1d201cc6d0a16e0f0cacefe925ad26c/id/.timeid.go.swp -------------------------------------------------------------------------------- /id/timeid.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package id 18 | 19 | import ( 20 | "time" 21 | ) 22 | 23 | /* 24 | type TimeID struct { 25 | lastID int64 26 | } 27 | 28 | // NewTimeID create a new TimeID struct 29 | func NewTimeID() *TimeID { 30 | return &TimeID{lastID: 0} 31 | } 32 | 33 | // ID generate a time ID 34 | func (t *TimeID) ID() int64 { 35 | for { 36 | s := time.Now().UnixNano() / 100 37 | if t.lastID >= s { 38 | // if last time id > current time, may be who change the system id, 39 | // so sleep last time id minus current time 40 | panic("time delay!!!!!!") 41 | } else { 42 | // save the current time id 43 | t.lastID = s 44 | return s 45 | } 46 | } 47 | return 0 48 | } 49 | */ 50 | 51 | // Get get a time id. 52 | func Get(long_timestamp bool) int64 { 53 | if long_timestamp { 54 | return time.Now().UnixNano() / 100 55 | } else { 56 | return time.Now().Unix() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /id/timeid_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package id 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | func TestTimeID(t *testing.T) { 24 | tid := NewTimeID() 25 | a := tid.ID() 26 | b := tid.ID() 27 | if a > b { 28 | t.Error("time a > b") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /libs/bytes/buffer.go: -------------------------------------------------------------------------------- 1 | package bytes 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type Buffer struct { 8 | buf []byte 9 | next *Buffer // next free buffer 10 | } 11 | 12 | func (b *Buffer) Bytes() []byte { 13 | return b.buf 14 | } 15 | 16 | // Pool is a buffer pool. 17 | type Pool struct { 18 | lock sync.Mutex 19 | free *Buffer 20 | max int 21 | num int 22 | size int 23 | } 24 | 25 | // NewPool new a memory buffer pool struct. 26 | func NewPool(num, size int) (p *Pool) { 27 | p = new(Pool) 28 | p.init(num, size) 29 | return 30 | } 31 | 32 | // Init init the memory buffer. 33 | func (p *Pool) Init(num, size int) { 34 | p.init(num, size) 35 | return 36 | } 37 | 38 | // init init the memory buffer. 39 | func (p *Pool) init(num, size int) { 40 | p.num = num 41 | p.size = size 42 | p.max = num * size 43 | p.grow() 44 | } 45 | 46 | // grow grow the memory buffer size, and update free pointer. 47 | func (p *Pool) grow() { 48 | var ( 49 | i int 50 | b *Buffer 51 | bs []Buffer 52 | buf []byte 53 | ) 54 | buf = make([]byte, p.max) 55 | bs = make([]Buffer, p.num) 56 | p.free = &bs[0] 57 | b = p.free 58 | for i = 1; i < p.num; i++ { 59 | b.buf = buf[(i-1)*p.size : i*p.size] 60 | b.next = &bs[i] 61 | b = b.next 62 | } 63 | b.buf = buf[(i-1)*p.size : i*p.size] 64 | b.next = nil 65 | return 66 | } 67 | 68 | // Get get a free memory buffer. 69 | func (p *Pool) Get() (b *Buffer) { 70 | p.lock.Lock() 71 | if b = p.free; b == nil { 72 | p.grow() 73 | b = p.free 74 | } 75 | p.free = b.next 76 | p.lock.Unlock() 77 | return 78 | } 79 | 80 | // Put put back a memory buffer to free. 81 | func (p *Pool) Put(b *Buffer) { 82 | p.lock.Lock() 83 | b.next = p.free 84 | p.free = b 85 | p.lock.Unlock() 86 | return 87 | } 88 | -------------------------------------------------------------------------------- /libs/bytes/buffer_test.go: -------------------------------------------------------------------------------- 1 | package bytes 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestBuffer(t *testing.T) { 8 | p := NewPool(2, 10) 9 | b := p.Get() 10 | if b.Bytes() == nil || len(b.Bytes()) == 0 { 11 | t.FailNow() 12 | } 13 | b = p.Get() 14 | if b.Bytes() == nil || len(b.Bytes()) == 0 { 15 | t.FailNow() 16 | } 17 | b = p.Get() 18 | if b.Bytes() == nil || len(b.Bytes()) == 0 { 19 | t.FailNow() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /libs/bytes/writer.go: -------------------------------------------------------------------------------- 1 | package bytes 2 | 3 | type Writer struct { 4 | n int 5 | buf []byte 6 | } 7 | 8 | func NewWriterSize(n int) *Writer { 9 | return &Writer{buf: make([]byte, n)} 10 | } 11 | 12 | func (w *Writer) Size() int { 13 | return len(w.buf) 14 | } 15 | 16 | func (w *Writer) Reset() { 17 | w.n = 0 18 | } 19 | 20 | func (w *Writer) Buffer() []byte { 21 | return w.buf[:w.n] 22 | } 23 | 24 | func (w *Writer) Peek(n int) []byte { 25 | var buf []byte 26 | w.grow(n) 27 | buf = w.buf[w.n : w.n+n] 28 | w.n += n 29 | return buf 30 | } 31 | 32 | func (w *Writer) Write(p []byte) { 33 | w.grow(len(p)) 34 | w.n += copy(w.buf[w.n:], p) 35 | } 36 | 37 | func (w *Writer) grow(n int) { 38 | var buf []byte 39 | if w.n+n < len(w.buf) { 40 | return 41 | } 42 | buf = make([]byte, 2*len(w.buf)+n) 43 | copy(buf, w.buf[:w.n]) 44 | w.buf = buf 45 | return 46 | } 47 | -------------------------------------------------------------------------------- /libs/crypto/aes/aes.go: -------------------------------------------------------------------------------- 1 | package aes 2 | 3 | import ( 4 | "crypto/cipher" 5 | "errors" 6 | ) 7 | 8 | var ( 9 | ErrBlockSize = errors.New("input not full blocks") 10 | ErrOutputSize = errors.New("output smaller than input") 11 | ) 12 | 13 | func encryptBlocks(b cipher.Block, src, dst []byte) error { 14 | if len(src)%b.BlockSize() != 0 { 15 | return ErrBlockSize 16 | } 17 | if len(dst) < len(src) { 18 | return ErrOutputSize 19 | } 20 | for len(src) > 0 { 21 | b.Encrypt(dst, src[:b.BlockSize()]) 22 | src = src[b.BlockSize():] 23 | dst = dst[b.BlockSize():] 24 | } 25 | return nil 26 | } 27 | 28 | func decryptBlocks(b cipher.Block, dst, src []byte) error { 29 | if len(src)%b.BlockSize() != 0 { 30 | return ErrBlockSize 31 | } 32 | if len(dst) < len(src) { 33 | return ErrOutputSize 34 | } 35 | for len(src) > 0 { 36 | b.Decrypt(dst, src[:b.BlockSize()]) 37 | src = src[b.BlockSize():] 38 | dst = dst[b.BlockSize():] 39 | } 40 | return nil 41 | } 42 | 43 | func ECBEncrypt(b cipher.Block, src []byte) (dst []byte, err error) { 44 | // use same buf 45 | dst = src 46 | err = encryptBlocks(b, dst, src) 47 | return 48 | } 49 | 50 | func ECBDecrypt(b cipher.Block, src []byte) (dst []byte, err error) { 51 | // use same buf 52 | dst = src 53 | err = decryptBlocks(b, dst, src) 54 | return 55 | } 56 | -------------------------------------------------------------------------------- /libs/crypto/aes/aes_test.go: -------------------------------------------------------------------------------- 1 | package aes 2 | 3 | import ( 4 | "crypto/aes" 5 | "encoding/hex" 6 | "fmt" 7 | "testing" 8 | ) 9 | 10 | func TestAes(t *testing.T) { 11 | a := []byte("1111111111111111") 12 | block, err := aes.NewCipher(a) 13 | if err != nil { 14 | t.Error(err) 15 | t.FailNow() 16 | } 17 | b, err := ECBEncrypt(block, a) 18 | if err != nil { 19 | t.Error(err) 20 | } 21 | fmt.Printf("%s\n", hex.EncodeToString(a)) 22 | fmt.Printf("%s\n", hex.EncodeToString(b)) 23 | if string(a) != string(b) { 24 | t.FailNow() 25 | } 26 | } 27 | 28 | /* 29 | func BenchmarkAES(b *testing.B) { 30 | a := []byte("1111111111111111") 31 | o := make([]byte, 50) 32 | d, err := ECBEncrypt(a, o, a, padding.PKCS5) 33 | if err != nil { 34 | b.Error(err) 35 | b.FailNow() 36 | } 37 | for i := 0; i < b.N; i++ { 38 | _, err := ECBDecrypt(d, o, a, padding.PKCS5) 39 | if err != nil { 40 | b.Error(err) 41 | b.FailNow() 42 | } 43 | } 44 | } 45 | */ 46 | -------------------------------------------------------------------------------- /libs/crypto/cipher/ecb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Electronic Code Book (ECB) mode. 6 | 7 | // ECB provides confidentiality by assigning a fixed ciphertext block to each 8 | // plaintext block. 9 | 10 | // See NIST SP 800-38A, pp 08-09 11 | 12 | package cipher 13 | 14 | import ( 15 | "crypto/cipher" 16 | ) 17 | 18 | type ecb struct { 19 | b cipher.Block 20 | blockSize int 21 | } 22 | 23 | func newECB(b cipher.Block) *ecb { 24 | return &ecb{ 25 | b: b, 26 | blockSize: b.BlockSize(), 27 | } 28 | } 29 | 30 | type ecbEncrypter ecb 31 | 32 | // NewECBEncrypter returns a BlockMode which encrypts in electronic code book 33 | // mode, using the given Block. 34 | func NewECBEncrypter(b cipher.Block) cipher.BlockMode { 35 | return (*ecbEncrypter)(newECB(b)) 36 | } 37 | 38 | func (x *ecbEncrypter) BlockSize() int { return x.blockSize } 39 | 40 | func (x *ecbEncrypter) CryptBlocks(dst, src []byte) { 41 | if len(src)%x.blockSize != 0 { 42 | panic("crypto/cipher: input not full blocks") 43 | } 44 | if len(dst) < len(src) { 45 | panic("crypto/cipher: output smaller than input") 46 | } 47 | for len(src) > 0 { 48 | x.b.Encrypt(dst, src[:x.blockSize]) 49 | src = src[x.blockSize:] 50 | dst = dst[x.blockSize:] 51 | } 52 | } 53 | 54 | type ecbDecrypter ecb 55 | 56 | // NewECBDecrypter returns a BlockMode which decrypts in electronic code book 57 | // mode, using the given Block. 58 | func NewECBDecrypter(b cipher.Block) cipher.BlockMode { 59 | return (*ecbDecrypter)(newECB(b)) 60 | } 61 | 62 | func (x *ecbDecrypter) BlockSize() int { return x.blockSize } 63 | 64 | func (x *ecbDecrypter) CryptBlocks(dst, src []byte) { 65 | if len(src)%x.blockSize != 0 { 66 | panic("crypto/cipher: input not full blocks") 67 | } 68 | if len(dst) < len(src) { 69 | panic("crypto/cipher: output smaller than input") 70 | } 71 | for len(src) > 0 { 72 | x.b.Decrypt(dst, src[:x.blockSize]) 73 | src = src[x.blockSize:] 74 | dst = dst[x.blockSize:] 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /libs/crypto/padding/padding.go: -------------------------------------------------------------------------------- 1 | package padding 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrPaddingSize = errors.New("padding size error") 9 | ) 10 | 11 | // Padding is interface used for crypto. 12 | type Padding interface { 13 | Padding(src []byte, blockSize int) []byte 14 | Unpadding(src []byte, blockSize int) ([]byte, error) 15 | } 16 | -------------------------------------------------------------------------------- /libs/crypto/padding/pkcs5.go: -------------------------------------------------------------------------------- 1 | package padding 2 | 3 | import ( 4 | "bytes" 5 | ) 6 | 7 | var ( 8 | PKCS5 = &pkcs5{} 9 | ) 10 | 11 | // pkcs5Padding is a pkcs5 padding struct. 12 | type pkcs5 struct{} 13 | 14 | // Padding implements the Padding interface Padding method. 15 | func (p *pkcs5) Padding(src []byte, blockSize int) []byte { 16 | srcLen := len(src) 17 | padLen := blockSize - (srcLen % blockSize) 18 | padText := bytes.Repeat([]byte{byte(padLen)}, padLen) 19 | return append(src, padText...) 20 | } 21 | 22 | // Unpadding implements the Padding interface Unpadding method. 23 | func (p *pkcs5) Unpadding(src []byte, blockSize int) ([]byte, error) { 24 | srcLen := len(src) 25 | paddingLen := int(src[srcLen-1]) 26 | if paddingLen >= srcLen || paddingLen > blockSize { 27 | return nil, ErrPaddingSize 28 | } 29 | return src[:srcLen-paddingLen], nil 30 | } 31 | -------------------------------------------------------------------------------- /libs/crypto/padding/pkcs5_test.go: -------------------------------------------------------------------------------- 1 | package padding 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestPKCS5(t *testing.T) { 8 | a := []byte{1} 9 | b := PKCS5.Padding(a, 16) 10 | // pad 15 length 11 | for i := 15; i > 0; i-- { 12 | if int(b[i]) != 15 { 13 | t.Error("padding error") 14 | } 15 | } 16 | if b[0] != 1 { 17 | t.Error("padding error") 18 | } 19 | c, err := PKCS5.Unpadding(b, 16) 20 | if err != nil { 21 | t.Error(err) 22 | } 23 | if len(c) != 1 { 24 | t.Error("padding error") 25 | } 26 | if c[0] != 1 { 27 | t.Error("padding error") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /libs/crypto/padding/pkcs7.go: -------------------------------------------------------------------------------- 1 | package padding 2 | 3 | var ( 4 | // difference with pkcs5 only block must be 8 5 | PKCS7 = &pkcs5{} 6 | ) 7 | -------------------------------------------------------------------------------- /libs/crypto/rsa/rsa.go: -------------------------------------------------------------------------------- 1 | package rsa 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "encoding/pem" 8 | "errors" 9 | ) 10 | 11 | var ( 12 | ErrPrivateKey = errors.New("private key error") 13 | ErrPublicKey = errors.New("public key error") 14 | ) 15 | 16 | func PrivateKey(pri []byte) (*rsa.PrivateKey, error) { 17 | block, _ := pem.Decode(pri) 18 | if block == nil { 19 | return nil, ErrPrivateKey 20 | } 21 | return x509.ParsePKCS1PrivateKey(block.Bytes) 22 | } 23 | 24 | func PublicKey(pub []byte) (*rsa.PublicKey, error) { 25 | block, _ := pem.Decode(pub) 26 | if block == nil { 27 | return nil, ErrPublicKey 28 | } 29 | pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) 30 | if err != nil { 31 | return nil, err 32 | } 33 | rsaPub := pubInterface.(*rsa.PublicKey) 34 | return rsaPub, nil 35 | } 36 | 37 | func Encrypt(orig []byte, pub *rsa.PublicKey) ([]byte, error) { 38 | return rsa.EncryptPKCS1v15(rand.Reader, pub, orig) 39 | } 40 | 41 | func Decrypt(cipher []byte, pri *rsa.PrivateKey) ([]byte, error) { 42 | return rsa.DecryptPKCS1v15(nil, pri, cipher) 43 | } 44 | -------------------------------------------------------------------------------- /libs/crypto/rsa/rsa_test.go: -------------------------------------------------------------------------------- 1 | package rsa 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | const ( 8 | priKey = ` 9 | -----BEGIN RSA PRIVATE KEY----- 10 | MFECAQACDQDt0G4B3JeeHjLWvX0CAwEAAQINANmKZncRf2SzCt/qiQIHAP1hu7hC 11 | NwIHAPBFhAcz6wIHAMKsRD3dIQIGDn4S7aBLAgY5OcfnuCQ= 12 | -----END RSA PRIVATE KEY----- 13 | ` 14 | pubKey = ` 15 | -----BEGIN PUBLIC KEY----- 16 | MCgwDQYJKoZIhvcNAQEBBQADFwAwFAINAO3QbgHcl54eMta9fQIDAQAB 17 | -----END PUBLIC KEY----- 18 | ` 19 | ) 20 | 21 | func TestRSA(t *testing.T) { 22 | pri, err := PrivateKey([]byte(priKey)) 23 | if err != nil { 24 | t.Error(err) 25 | t.FailNow() 26 | } 27 | pub, err := PublicKey([]byte(pubKey)) 28 | if err != nil { 29 | t.Error(err) 30 | t.FailNow() 31 | } 32 | msg := "1" 33 | cipher, err := Encrypt([]byte(msg), pub) 34 | if err != nil { 35 | t.Error(err) 36 | t.FailNow() 37 | } 38 | ori, err := Decrypt(cipher, pri) 39 | if err != nil { 40 | t.Error(err) 41 | t.FailNow() 42 | } 43 | if string(ori) != msg { 44 | t.FailNow() 45 | } 46 | } 47 | 48 | func BenchmarkRSA(b *testing.B) { 49 | pri, err := PrivateKey([]byte(priKey)) 50 | if err != nil { 51 | b.Error(err) 52 | b.FailNow() 53 | } 54 | pub, err := PublicKey([]byte(pubKey)) 55 | if err != nil { 56 | b.Error(err) 57 | b.FailNow() 58 | } 59 | msg := "1" 60 | cipher, err := Encrypt([]byte(msg), pub) 61 | if err != nil { 62 | b.Error(err) 63 | b.FailNow() 64 | } 65 | for i := 0; i < b.N; i++ { 66 | if _, err := Decrypt(cipher, pri); err != nil { 67 | b.Error(err) 68 | b.FailNow() 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /libs/define/kafka.go: -------------------------------------------------------------------------------- 1 | package define 2 | 3 | // Kafka message type Commands 4 | const ( 5 | KAFKA_MESSAGE_MULTI = "multiple" //multi-userid push 6 | KAFKA_MESSAGE_BROADCAST = "broadcast" //broadcast push 7 | KAFKA_MESSAGE_BROADCAST_ROOM = "broadcast_room" //broadcast room push 8 | ) 9 | -------------------------------------------------------------------------------- /libs/define/operation.go: -------------------------------------------------------------------------------- 1 | package define 2 | 3 | const ( 4 | // handshake 5 | OP_HANDSHAKE = int32(0) 6 | OP_HANDSHAKE_REPLY = int32(1) 7 | // heartbeat 8 | OP_HEARTBEAT = int32(2) 9 | OP_HEARTBEAT_REPLY = int32(3) 10 | // send text messgae 11 | OP_SEND_SMS = int32(4) 12 | OP_SEND_SMS_REPLY = int32(5) 13 | // kick user 14 | OP_DISCONNECT_REPLY = int32(6) 15 | // auth user 16 | OP_AUTH = int32(7) 17 | OP_AUTH_REPLY = int32(8) 18 | // handshake with sid 19 | OP_HANDSHAKE_SID = int32(9) 20 | OP_HANDSHAKE_SID_REPLY = int32(10) 21 | // raw message 22 | OP_RAW = int32(11) 23 | // room 24 | OP_ROOM_READY = int32(12) 25 | // proto 26 | OP_PROTO_READY = int32(13) 27 | OP_PROTO_FINISH = int32(14) 28 | 29 | // for test 30 | OP_TEST = int32(254) 31 | OP_TEST_REPLY = int32(255) 32 | ) 33 | -------------------------------------------------------------------------------- /libs/define/room.go: -------------------------------------------------------------------------------- 1 | package define 2 | 3 | const ( 4 | NoRoom = -1 5 | ) 6 | -------------------------------------------------------------------------------- /libs/define/user.go: -------------------------------------------------------------------------------- 1 | package define 2 | 3 | const ( 4 | NoUser = -1 5 | ) 6 | -------------------------------------------------------------------------------- /libs/encoding/binary/endian.go: -------------------------------------------------------------------------------- 1 | package binary 2 | 3 | var BigEndian bigEndian 4 | 5 | type bigEndian struct{} 6 | 7 | func (bigEndian) Int16(b []byte) int16 { return int16(b[1]) | int16(b[0])<<8 } 8 | 9 | func (bigEndian) PutInt16(b []byte, v int16) { 10 | b[0] = byte(v >> 8) 11 | b[1] = byte(v) 12 | } 13 | 14 | func (bigEndian) Int32(b []byte) int32 { 15 | return int32(b[3]) | int32(b[2])<<8 | int32(b[1])<<16 | int32(b[0])<<24 16 | } 17 | 18 | func (bigEndian) PutInt32(b []byte, v int32) { 19 | b[0] = byte(v >> 24) 20 | b[1] = byte(v >> 16) 21 | b[2] = byte(v >> 8) 22 | b[3] = byte(v) 23 | } 24 | -------------------------------------------------------------------------------- /libs/hash/ketama/ketama.go: -------------------------------------------------------------------------------- 1 | package ketama 2 | 3 | import ( 4 | "crypto/sha1" 5 | "sort" 6 | "strconv" 7 | ) 8 | 9 | const ( 10 | // TODO you can modify this get more virtual node 11 | Base = 255 12 | ) 13 | 14 | type node struct { 15 | node string 16 | hash uint 17 | } 18 | 19 | type tickArray []node 20 | 21 | func (p tickArray) Len() int { return len(p) } 22 | func (p tickArray) Less(i, j int) bool { return p[i].hash < p[j].hash } 23 | func (p tickArray) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 24 | func (p tickArray) Sort() { sort.Sort(p) } 25 | 26 | type HashRing struct { 27 | defaultSpots int 28 | ticks tickArray 29 | length int 30 | } 31 | 32 | func NewRing(n int) (h *HashRing) { 33 | h = new(HashRing) 34 | h.defaultSpots = n 35 | return 36 | } 37 | 38 | // Adds a new node to a hash ring 39 | // n: name of the server 40 | // s: multiplier for default number of ticks (useful when one cache node has more resources, like RAM, than another) 41 | func (h *HashRing) AddNode(n string, s int) { 42 | tSpots := h.defaultSpots * s 43 | hash := sha1.New() 44 | for i := 1; i <= tSpots; i++ { 45 | hash.Write([]byte(n + ":" + strconv.Itoa(i))) 46 | hashBytes := hash.Sum(nil) 47 | 48 | n := &node{ 49 | node: n, 50 | hash: uint(hashBytes[19]) | uint(hashBytes[18])<<8 | uint(hashBytes[17])<<16 | uint(hashBytes[16])<<24, 51 | } 52 | 53 | h.ticks = append(h.ticks, *n) 54 | hash.Reset() 55 | } 56 | } 57 | 58 | func (h *HashRing) Bake() { 59 | h.ticks.Sort() 60 | h.length = len(h.ticks) 61 | } 62 | 63 | func (h *HashRing) Hash(s string) string { 64 | hash := sha1.New() 65 | hash.Write([]byte(s)) 66 | hashBytes := hash.Sum(nil) 67 | v := uint(hashBytes[19]) | uint(hashBytes[18])<<8 | uint(hashBytes[17])<<16 | uint(hashBytes[16])<<24 68 | i := sort.Search(h.length, func(i int) bool { return h.ticks[i].hash >= v }) 69 | 70 | if i == h.length { 71 | i = 0 72 | } 73 | 74 | return h.ticks[i].node 75 | } 76 | -------------------------------------------------------------------------------- /libs/hash/ketama/ketama_test.go: -------------------------------------------------------------------------------- 1 | package ketama 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | ) 7 | 8 | func Benchmark_Hash(b *testing.B) { 9 | ring := NewRing(255) 10 | ring.AddNode("node1", 1) 11 | ring.AddNode("node2", 1) 12 | ring.AddNode("node3", 1) 13 | ring.AddNode("node4", 1) 14 | ring.AddNode("node5", 1) 15 | b.StartTimer() 16 | for i := 0; i < b.N; i++ { 17 | ring.Hash(strconv.Itoa(i)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libs/hash/murmurhash3/mmhash3_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 The Go Authors. All rights reserved. 2 | // refer https://github.com/gwenn/murmurhash3 3 | 4 | package main 5 | 6 | import ( 7 | "hash" 8 | "testing" 9 | ) 10 | 11 | const testDataSize = 40 12 | 13 | func TestMurmur3A(t *testing.T) { 14 | expected := uint32(3127628307) 15 | hash := Murmur3A([]byte("test"), 0) 16 | if hash != expected { 17 | t.Errorf("Expected %d but was %d for Murmur3A\n", expected, hash) 18 | } 19 | } 20 | 21 | func TestMurmur3C(t *testing.T) { 22 | expected := []uint32{1862463280, 1426881896, 1426881896, 1426881896} 23 | hash := Murmur3C([]byte("test"), 0) 24 | for i, e := range expected { 25 | if hash[i] != e { 26 | t.Errorf("Expected %d but was %d for Murmur3C[%d]\n", e, hash[i], i) 27 | } 28 | } 29 | } 30 | 31 | func TestMurmur3F(t *testing.T) { 32 | expected := []uint64{12429135405209477533, 11102079182576635266} 33 | hash := Murmur3F([]byte("test"), 0) 34 | for i, e := range expected { 35 | if hash[i] != e { 36 | t.Errorf("Expected %d but was %d for Murmur3F[%d]\n", e, hash[i], i) 37 | } 38 | } 39 | } 40 | 41 | func Benchmark3A(b *testing.B) { 42 | benchmark(b, NewMurmur3A()) 43 | } 44 | func Benchmark3C(b *testing.B) { 45 | benchmark(b, NewMurmur3C()) 46 | } 47 | func Benchmark3F(b *testing.B) { 48 | benchmark(b, NewMurmur3F()) 49 | } 50 | 51 | func benchmark(b *testing.B, h hash.Hash) { 52 | b.ResetTimer() 53 | b.SetBytes(testDataSize) 54 | data := make([]byte, testDataSize) 55 | for i := range data { 56 | data[i] = byte(i + 'a') 57 | } 58 | 59 | b.StartTimer() 60 | for todo := b.N; todo != 0; todo-- { 61 | h.Reset() 62 | h.Write(data) 63 | h.Sum(nil) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /libs/io/ioutil/ioutil.go: -------------------------------------------------------------------------------- 1 | package ioutil 2 | 3 | import ( 4 | "bufio" 5 | ) 6 | 7 | func ReadAll(rd *bufio.Reader, d []byte) (err error) { 8 | tl, n, t := len(d), 0, 0 9 | for { 10 | if t, err = rd.Read(d[n:]); err != nil { 11 | return 12 | } 13 | if n += t; n == tl { 14 | break 15 | } 16 | } 17 | return 18 | } 19 | -------------------------------------------------------------------------------- /libs/net/network.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | networkSpliter = "@" 10 | ) 11 | 12 | func ParseNetwork(str string) (network, addr string, err error) { 13 | if idx := strings.Index(str, networkSpliter); idx == -1 { 14 | err = fmt.Errorf("addr: \"%s\" error, must be network@tcp:port or network@unixsocket", str) 15 | return 16 | } else { 17 | network = str[:idx] 18 | addr = str[idx+1:] 19 | return 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /libs/net/xrpc/client.go: -------------------------------------------------------------------------------- 1 | package xrpc 2 | 3 | import ( 4 | "errors" 5 | "imgo/libs/proto" 6 | "net" 7 | "net/rpc" 8 | "time" 9 | 10 | log "github.com/thinkboy/log4go" 11 | ) 12 | 13 | const ( 14 | dialTimeout = 5 * time.Second 15 | callTimeout = 3 * time.Second 16 | pingDuration = 1 * time.Second 17 | ) 18 | 19 | var ( 20 | ErrRpc = errors.New("rpc is not available") 21 | ErrRpcTimeout = errors.New("rpc call timeout") 22 | ) 23 | 24 | // Rpc client options. 25 | type ClientOptions struct { 26 | Proto string 27 | Addr string 28 | } 29 | 30 | // Client is rpc client. 31 | type Client struct { 32 | *rpc.Client 33 | options ClientOptions 34 | quit chan struct{} 35 | err error 36 | } 37 | 38 | // Dial connects to an RPC server at the specified network address. 39 | func Dial(options ClientOptions) (c *Client) { 40 | c = new(Client) 41 | c.options = options 42 | c.dial() 43 | return 44 | } 45 | 46 | // Dial connects to an RPC server at the specified network address. 47 | func (c *Client) dial() (err error) { 48 | var conn net.Conn 49 | conn, err = net.DialTimeout(c.options.Proto, c.options.Addr, dialTimeout) 50 | if err != nil { 51 | log.Error("net.Dial(%s, %s), error(%v)", c.options.Proto, c.options.Addr, err) 52 | } else { 53 | c.Client = rpc.NewClient(conn) 54 | } 55 | return 56 | } 57 | 58 | // Call invokes the named function, waits for it to complete, and returns its error status. 59 | func (c *Client) Call(serviceMethod string, args interface{}, reply interface{}) (err error) { 60 | if c.Client == nil { 61 | err = ErrRpc 62 | return 63 | } 64 | select { 65 | case call := <-c.Client.Go(serviceMethod, args, reply, make(chan *rpc.Call, 1)).Done: 66 | err = call.Error 67 | case <-time.After(callTimeout): 68 | err = ErrRpcTimeout 69 | } 70 | return 71 | } 72 | 73 | // Return client error. 74 | func (c *Client) Error() error { 75 | return c.err 76 | } 77 | 78 | // Close client connection. 79 | func (c *Client) Close() { 80 | c.quit <- struct{}{} 81 | } 82 | 83 | // ping ping the rpc connect and reconnect when has an error. 84 | func (c *Client) Ping(serviceMethod string) { 85 | var ( 86 | arg = proto.NoArg{} 87 | reply = proto.NoReply{} 88 | err error 89 | ) 90 | for { 91 | select { 92 | case <-c.quit: 93 | goto closed 94 | return 95 | default: 96 | } 97 | if c.Client != nil && c.err == nil { 98 | // ping 99 | if err = c.Call(serviceMethod, &arg, &reply); err != nil { 100 | c.err = err 101 | if err != rpc.ErrShutdown { 102 | c.Client.Close() 103 | } 104 | log.Error("client.Call(%s, arg, reply) error(%v)", serviceMethod, err) 105 | } 106 | } else { 107 | // reconnect 108 | if err = c.dial(); err == nil { 109 | // reconnect ok 110 | c.err = nil 111 | log.Info("client reconnect %s ok", c.options.Addr) 112 | } 113 | } 114 | time.Sleep(pingDuration) 115 | } 116 | closed: 117 | if c.Client != nil { 118 | c.Client.Close() 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /libs/net/xrpc/clients.go: -------------------------------------------------------------------------------- 1 | package xrpc 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrNoClient = errors.New("rpc is not available") 9 | ) 10 | 11 | type Clients struct { 12 | clients []*Client 13 | } 14 | 15 | // Dials connects to RPC servers at the specified network address. 16 | func Dials(options []ClientOptions) *Clients { 17 | clients := new(Clients) 18 | for _, op := range options { 19 | clients.clients = append(clients.clients, Dial(op)) 20 | } 21 | return clients 22 | } 23 | 24 | // get get a available client. 25 | func (c *Clients) get() (*Client, error) { 26 | for _, cli := range c.clients { 27 | if cli != nil && cli.Client != nil && cli.Error() == nil { 28 | return cli, nil 29 | } 30 | } 31 | return nil, ErrNoClient 32 | } 33 | 34 | // Call invokes the named function, waits for it to complete, and returns its error status. 35 | // this include rpc.Client.Call method, and takes a timeout. 36 | func (c *Clients) Call(serviceMethod string, args interface{}, reply interface{}) (err error) { 37 | var cli *Client 38 | if cli, err = c.get(); err == nil { 39 | err = cli.Call(serviceMethod, args, reply) 40 | } 41 | return 42 | } 43 | 44 | // Ping the rpc connect and reconnect when has an error. 45 | func (c *Clients) Ping(serviceMethod string) { 46 | for _, cli := range c.clients { 47 | go cli.Ping(serviceMethod) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /libs/perf/perf.go: -------------------------------------------------------------------------------- 1 | package perf 2 | 3 | import ( 4 | "net/http" 5 | "net/http/pprof" 6 | 7 | log "github.com/thinkboy/log4go" 8 | ) 9 | 10 | // StartPprof start http pprof. 11 | func Init(pprofBind []string) { 12 | pprofServeMux := http.NewServeMux() 13 | pprofServeMux.HandleFunc("/debug/pprof/", pprof.Index) 14 | pprofServeMux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 15 | pprofServeMux.HandleFunc("/debug/pprof/profile", pprof.Profile) 16 | pprofServeMux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 17 | for _, addr := range pprofBind { 18 | go func() { 19 | if err := http.ListenAndServe(addr, pprofServeMux); err != nil { 20 | log.Error("http.ListenAndServe(\"%s\", pprofServeMux) error(%v)", addr, err) 21 | panic(err) 22 | } 23 | }() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /libs/proto/comet.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | type NoArg struct { 4 | } 5 | 6 | type NoReply struct { 7 | } 8 | 9 | type PushMsgArg struct { 10 | Key string 11 | P Proto 12 | } 13 | 14 | type PushMsgsArg struct { 15 | Key string 16 | PMArgs []*PushMsgArg 17 | } 18 | 19 | type PushMsgsReply struct { 20 | Index int32 21 | } 22 | 23 | type MPushMsgArg struct { 24 | Keys []string 25 | P Proto 26 | } 27 | 28 | type MPushMsgReply struct { 29 | Index int32 30 | } 31 | 32 | type MPushMsgsArg struct { 33 | PMArgs []*PushMsgArg 34 | } 35 | 36 | type MPushMsgsReply struct { 37 | Index int32 38 | } 39 | 40 | type BoardcastArg struct { 41 | P Proto 42 | } 43 | 44 | type BoardcastRoomArg struct { 45 | RoomId int32 46 | P Proto 47 | } 48 | 49 | type RoomsReply struct { 50 | RoomIds []int32 51 | } 52 | -------------------------------------------------------------------------------- /libs/proto/job.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | // TODO optimize struct after replace kafka 4 | type KafkaMsg struct { 5 | OP string `json:"op"` 6 | RoomId int32 `json:"roomid,omitempty"` 7 | ServerId int32 `json:"server,omitempty"` 8 | SubKeys []string `json:"subkeys,omitempty"` 9 | Msg []byte `json:"msg"` 10 | Ensure bool `json:"ensure,omitempty"` 11 | } 12 | -------------------------------------------------------------------------------- /libs/proto/logic.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | type ConnArg struct { 4 | Token string 5 | Server int32 6 | } 7 | 8 | type ConnReply struct { 9 | Key string 10 | RoomId int32 11 | } 12 | 13 | type DisconnArg struct { 14 | Key string 15 | RoomId int32 16 | } 17 | 18 | type DisconnReply struct { 19 | Has bool 20 | } 21 | -------------------------------------------------------------------------------- /libs/proto/message.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | // Message SavePrivate args 4 | import "encoding/json" 5 | 6 | type MessageSavePrivateArgs struct { 7 | Key string // subscriber key 8 | Msg []byte // message content 9 | MsgId int64 // message id 10 | Expire uint // message expire second 11 | } 12 | 13 | // Message SavePrivates args 14 | type MessageSavePrivatesArgs struct { 15 | Keys []string // subscriber keys 16 | Msg json.RawMessage // message content 17 | MsgId int64 // message id 18 | Expire uint // message expire second 19 | } 20 | 21 | // Message SavePrivates response 22 | type MessageSavePrivatesResp struct { 23 | FKeys []string // failed key 24 | } 25 | 26 | // Message SavePublish args 27 | type MessageSavePublishArgs struct { 28 | MsgID int64 // message id 29 | Msg string // message content 30 | Expire int64 // message expire second 31 | } 32 | 33 | //type MessageSaveTokenArgs struct { 34 | //Key, Value string 35 | //Expire int64 36 | //} 37 | type Token struct { 38 | Token string 39 | Uid, Expire int64 40 | } 41 | 42 | // Message Get args 43 | type MessageGetPrivateArgs struct { 44 | MsgId int64 // message id 45 | Key string // subscriber key 46 | } 47 | 48 | // Message Get Response 49 | type MessageGetResp struct { 50 | Msgs []*Message // messages 51 | } 52 | 53 | // The Message struct 54 | type Message struct { 55 | Msg []byte `json:"msg"` // message content 56 | MsgId int64 `json:"mid"` // message id 57 | GroupId uint `json:"gid"` // group id 58 | } 59 | 60 | const ( 61 | // group id 62 | PrivateGroupId = 0 63 | PublicGroupId = 1 64 | ) 65 | -------------------------------------------------------------------------------- /libs/proto/ret.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrParam = errors.New("parameter error") 7 | ) 8 | -------------------------------------------------------------------------------- /libs/proto/router.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | type PutArg struct { 4 | UserId int64 5 | Server int32 6 | RoomId int32 7 | } 8 | 9 | type PutReply struct { 10 | Seq int32 11 | } 12 | 13 | type DelArg struct { 14 | UserId int64 15 | Seq int32 16 | RoomId int32 17 | } 18 | 19 | type DelReply struct { 20 | Has bool 21 | } 22 | 23 | type DelServerArg struct { 24 | Server int32 25 | } 26 | 27 | type GetArg struct { 28 | UserId int64 29 | } 30 | 31 | type GetReply struct { 32 | Seqs []int32 33 | Servers []int32 34 | } 35 | 36 | type GetAllReply struct { 37 | UserIds []int64 38 | Sessions []*GetReply 39 | } 40 | 41 | type MGetArg struct { 42 | UserIds []int64 43 | } 44 | 45 | type MGetReply struct { 46 | UserIds []int64 47 | Sessions []*GetReply 48 | } 49 | 50 | type CountReply struct { 51 | Count int32 52 | } 53 | 54 | type RoomCountArg struct { 55 | RoomId int32 56 | } 57 | 58 | type RoomCountReply struct { 59 | Count int32 60 | } 61 | 62 | type AllRoomCountReply struct { 63 | Counter map[int32]int32 64 | } 65 | 66 | type AllServerCountReply struct { 67 | Counter map[int32]int32 68 | } 69 | 70 | type UserCountArg struct { 71 | UserId int64 72 | } 73 | 74 | type UserCountReply struct { 75 | Count int32 76 | } 77 | -------------------------------------------------------------------------------- /libs/time/debug.go: -------------------------------------------------------------------------------- 1 | package time 2 | 3 | const ( 4 | Debug = false 5 | ) 6 | -------------------------------------------------------------------------------- /libs/time/timer_test.go: -------------------------------------------------------------------------------- 1 | package time 2 | 3 | import ( 4 | log "code.google.com/p/log4go" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestTimer(t *testing.T) { 10 | timer := NewTimer(100) 11 | tds := make([]*TimerData, 100) 12 | for i := 0; i < 100; i++ { 13 | tds[i] = timer.Add(time.Duration(i)*time.Second+5*time.Minute, nil) 14 | } 15 | printTimer(timer) 16 | for i := 0; i < 100; i++ { 17 | log.Debug("td: %s, %s, %d", tds[i].Key, tds[i].ExpireString(), tds[i].index) 18 | timer.Del(tds[i]) 19 | } 20 | printTimer(timer) 21 | for i := 0; i < 100; i++ { 22 | tds[i] = timer.Add(time.Duration(i)*time.Second+5*time.Minute, nil) 23 | } 24 | printTimer(timer) 25 | for i := 0; i < 100; i++ { 26 | timer.Del(tds[i]) 27 | } 28 | printTimer(timer) 29 | timer.Add(time.Second, nil) 30 | time.Sleep(time.Second * 2) 31 | if len(timer.timers) != 0 { 32 | t.FailNow() 33 | } 34 | } 35 | 36 | func printTimer(timer *Timer) { 37 | log.Debug("----------timers: %d ----------", len(timer.timers)) 38 | for i := 0; i < len(timer.timers); i++ { 39 | log.Debug("timer: %s, %s, index: %d", timer.timers[i].Key, timer.timers[i].ExpireString(), timer.timers[i].index) 40 | } 41 | log.Debug("--------------------") 42 | } 43 | -------------------------------------------------------------------------------- /logic/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /logic/.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /logic/.idea/libraries/GOPATH__logic_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /logic/.idea/logic.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /logic/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | Go 1.7beta2 45 | 46 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /logic/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /logic/auth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "imgo/libs/define" 4 | 5 | // developer could implement "Auth" interface for decide how get userId, or roomId 6 | type Auther interface { 7 | Auth(token string) (AuthRes, error) 8 | } 9 | 10 | type AuthRes struct { 11 | Uid int64 12 | RoomId int32 13 | } 14 | 15 | type DefaultAuther struct { 16 | } 17 | 18 | func NewDefaultAuther() *DefaultAuther { 19 | return &DefaultAuther{} 20 | } 21 | 22 | func (a *DefaultAuther) Auth(token string) (res AuthRes, err error) { 23 | res.RoomId = define.NoRoom 24 | res.Uid, err = getUid(token) 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /logic/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "runtime" 22 | "time" 23 | 24 | "github.com/Terry-Mao/goconf" 25 | ) 26 | 27 | var ( 28 | gconf *goconf.Config 29 | Conf *Config 30 | confFile string 31 | ) 32 | 33 | func init() { 34 | flag.StringVar(&confFile, "c", "./logic.conf", " set logic config file path") 35 | } 36 | 37 | type Config struct { 38 | // base section 39 | PidFile string `goconf:"base:pidfile"` 40 | Dir string `goconf:"base:dir"` 41 | Log string `goconf:"base:log"` 42 | MaxProc int `goconf:"base:maxproc"` 43 | PprofAddrs []string `goconf:"base:pprof.addrs:,"` 44 | RPCAddrs []string `goconf:"base:rpc.addrs:,"` 45 | HTTPAddrs []string `goconf:"base:http.addrs:,"` 46 | HTTPReadTimeout time.Duration `goconf:"base:http.read.timeout:time"` 47 | HTTPWriteTimeout time.Duration `goconf:"base:http.write.timeout:time"` 48 | // router RPC 49 | RouterRPCAddrs map[string]string `-` 50 | // kafka 51 | KafkaAddrs []string `goconf:"kafka:addrs"` 52 | // message 53 | MessageAddr string `goconf:"message:rpc.bind"` 54 | } 55 | 56 | func NewConfig() *Config { 57 | return &Config{ 58 | // base section 59 | PidFile: "/tmp/imgo-logic.pid", 60 | Dir: "./", 61 | Log: "./logic-log.xml", 62 | MaxProc: runtime.NumCPU(), 63 | PprofAddrs: []string{"localhost:6971"}, 64 | HTTPAddrs: []string{"7172"}, 65 | RouterRPCAddrs: make(map[string]string), 66 | } 67 | } 68 | 69 | // InitConfig init the global config. 70 | func InitConfig() (err error) { 71 | Conf = NewConfig() 72 | gconf = goconf.New() 73 | if err = gconf.Parse(confFile); err != nil { 74 | return err 75 | } 76 | if err := gconf.Unmarshal(Conf); err != nil { 77 | return err 78 | } 79 | for _, serverID := range gconf.Get("router.addrs").Keys() { 80 | addr, err := gconf.Get("router.addrs").String(serverID) 81 | if err != nil { 82 | return err 83 | } 84 | Conf.RouterRPCAddrs[serverID] = addr 85 | } 86 | return nil 87 | } 88 | 89 | func ReloadConfig() (*Config, error) { 90 | conf := NewConfig() 91 | ngconf, err := gconf.Reload() 92 | if err != nil { 93 | return nil, err 94 | } 95 | if err := ngconf.Unmarshal(conf); err != nil { 96 | return nil, err 97 | } 98 | gconf = ngconf 99 | return conf, nil 100 | } 101 | -------------------------------------------------------------------------------- /logic/counter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "imgo/libs/net/xrpc" 5 | "time" 6 | ) 7 | 8 | const ( 9 | syncCountDelay = 1 * time.Second 10 | ) 11 | 12 | var ( 13 | RoomCountMap = make(map[int32]int32) // roomid:count 14 | ServerCountMap = make(map[int32]int32) // server:count 15 | ) 16 | 17 | func MergeCount() { 18 | var ( 19 | c *xrpc.Clients 20 | err error 21 | roomId, server, count int32 22 | counter map[int32]int32 23 | roomCount = make(map[int32]int32) 24 | serverCount = make(map[int32]int32) 25 | ) 26 | // all comet nodes 27 | for _, c = range routerServiceMap { 28 | if c != nil { 29 | if counter, err = allRoomCount(c); err != nil { 30 | continue 31 | } 32 | for roomId, count = range counter { 33 | roomCount[roomId] += count 34 | } 35 | if counter, err = allServerCount(c); err != nil { 36 | continue 37 | } 38 | for server, count = range counter { 39 | serverCount[server] += count 40 | } 41 | } 42 | } 43 | RoomCountMap = roomCount 44 | ServerCountMap = serverCount 45 | } 46 | 47 | /* 48 | func RoomCount(roomId int32) (count int32) { 49 | count = RoomCountMap[roomId] 50 | return 51 | } 52 | */ 53 | 54 | func SyncCount() { 55 | for { 56 | MergeCount() 57 | time.Sleep(syncCountDelay) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /logic/encoder.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | func encode(userId int64, seq int32) string { 10 | return fmt.Sprintf("%d_%d", userId, seq) 11 | } 12 | 13 | func decode(key string) (userId int64, seq int32, err error) { 14 | var ( 15 | idx int 16 | t int64 17 | ) 18 | if idx = strings.IndexByte(key, '_'); idx == -1 { 19 | err = ErrDecodeKey 20 | return 21 | } 22 | if userId, err = strconv.ParseInt(key[:idx], 10, 64); err != nil { 23 | return 24 | } 25 | if t, err = strconv.ParseInt(key[idx+1:], 10, 32); err != nil { 26 | return 27 | } 28 | seq = int32(t) 29 | return 30 | } 31 | -------------------------------------------------------------------------------- /logic/errors.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrRouter = errors.New("router rpc is not available") 9 | ErrDecodeKey = errors.New("decode key error") 10 | ErrNetworkAddr = errors.New("network addrs error, must network@address") 11 | ErrConnectArgs = errors.New("connect rpc args error") 12 | ErrDisconnectArgs = errors.New("disconnect rpc args error") 13 | ErrMessage = errors.New("message rpc error") 14 | ) 15 | -------------------------------------------------------------------------------- /logic/job/comet_info.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | const ( 8 | syncRoomServersDelay = 1 * time.Second 9 | ) 10 | 11 | var ( 12 | RoomServersMap = make(map[int32]map[int32]struct{}) // roomid:servers 13 | ) 14 | 15 | func MergeRoomServers() { 16 | var ( 17 | c *Comet 18 | ok bool 19 | roomId int32 20 | serverId int32 21 | roomIds []int32 22 | servers map[int32]struct{} 23 | roomServers = make(map[int32]map[int32]struct{}) 24 | ) 25 | // all comet nodes 26 | for serverId, c = range cometServiceMap { 27 | if c.rpcClient != nil { 28 | if roomIds = roomsComet(c.rpcClient); roomIds != nil { 29 | // merge room's servers 30 | for _, roomId = range roomIds { 31 | if servers, ok = roomServers[roomId]; !ok { 32 | servers = make(map[int32]struct{}) 33 | roomServers[roomId] = servers 34 | } 35 | servers[serverId] = struct{}{} 36 | } 37 | } 38 | } 39 | } 40 | RoomServersMap = roomServers 41 | } 42 | 43 | func SyncRoomServers() { 44 | for { 45 | MergeRoomServers() 46 | time.Sleep(syncRoomServersDelay) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /logic/job/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "runtime" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/Terry-Mao/goconf" 10 | ) 11 | 12 | var ( 13 | gconf *goconf.Config 14 | Conf *Config 15 | confFile string 16 | ) 17 | 18 | func init() { 19 | flag.StringVar(&confFile, "c", "./job.conf", " set config file path") 20 | } 21 | 22 | type Config struct { 23 | Log string `goconf:"base:log"` 24 | ZKAddrs []string `goconf:"kafka:zookeeper.list:,"` 25 | ZKRoot string `goconf:"kafka:zkroot"` 26 | KafkaTopic string `goconf:"kafka:topic"` 27 | // comet 28 | Comets map[int32]string `goconf:"-"` 29 | DialTimeout time.Duration `goconf:"comet:dial.timeout:time"` 30 | CallTimeout time.Duration `goconf:"comet:call.timeout:time"` 31 | RoutineSize int64 `goconf:"comet:routine.size"` 32 | RoutineChan int `goconf:"comet:routine.chan"` 33 | // push 34 | PushChan int `goconf:"push:chan"` 35 | PushChanSize int `goconf:"push:chan.size"` 36 | // timer 37 | Timer int `goconf:"timer:num"` 38 | TimerSize int `goconf:"timer:size"` 39 | // room 40 | RoomBatch int `goconf:"room:batch"` 41 | RoomSignal time.Duration `goconf:"room:signal:time"` 42 | } 43 | 44 | func NewConfig() *Config { 45 | return &Config{ 46 | Comets: make(map[int32]string), 47 | ZKRoot: "", 48 | KafkaTopic: "kafka_topic_push", 49 | RoutineSize: 16, 50 | RoutineChan: 64, 51 | DialTimeout: 5 * time.Second, 52 | CallTimeout: 3 * time.Second, 53 | PushChan: 4, 54 | PushChanSize: 100, 55 | //timer 56 | // timer 57 | Timer: runtime.NumCPU(), 58 | TimerSize: 1000, 59 | } 60 | } 61 | 62 | // InitConfig init the global config. 63 | func InitConfig() (err error) { 64 | Conf = NewConfig() 65 | gconf = goconf.New() 66 | if err = gconf.Parse(confFile); err != nil { 67 | return err 68 | } 69 | if err = gconf.Unmarshal(Conf); err != nil { 70 | return err 71 | } 72 | var serverIDi int64 73 | for _, serverID := range gconf.Get("comets").Keys() { 74 | addr, err := gconf.Get("comets").String(serverID) 75 | if err != nil { 76 | return err 77 | } 78 | serverIDi, err = strconv.ParseInt(serverID, 10, 32) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | Conf.Comets[int32(serverIDi)] = addr 84 | } 85 | return 86 | } 87 | -------------------------------------------------------------------------------- /logic/job/errors.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | // comet 9 | ErrComet = errors.New("comet rpc is not available") 10 | ErrCometFull = errors.New("comet proto chan full") 11 | // room 12 | ErrRoomFull = errors.New("room proto chan full") 13 | ) 14 | -------------------------------------------------------------------------------- /logic/job/job-log.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | stdout 4 | console 5 | 6 | DEBUG 7 | 8 | 9 | debug_file 10 | file 11 | DEBUG 12 | /tmp/pushs_job_debug.log 13 | [%D %T] [%L] [%S] %M 14 | true 15 | 0M 16 | 0K 17 | true 18 | 19 | 20 | info_file 21 | file 22 | INFO 23 | /tmp/pushs_job_info.log 24 | 35 | [%D %T] [%L] [%S] %M 36 | true 37 | 0M 38 | 0K 39 | true 40 | 41 | 42 | warn_file 43 | file 44 | WARNING 45 | /tmp/pushs_job_warn.log 46 | [%D %T] [%L] [%S] %M 47 | true 48 | 0M 49 | 0K 50 | true 51 | 52 | 53 | error_file 54 | file 55 | ERROR 56 | /tmp/pushs_job_error.log 57 | [%D %T] [%L] [%S] %M 58 | true 59 | 0M 60 | 0K 61 | true 62 | 63 | 64 | -------------------------------------------------------------------------------- /logic/job/job.conf: -------------------------------------------------------------------------------- 1 | [base] 2 | log ./job-log.xml 3 | 4 | [kafka] 5 | zookeeper.list 127.0.0.1:2181 6 | #zkroot /goim_job 7 | topic KafkaPushsTopic 8 | 9 | [comets] 10 | # comet server address list 11 | # 12 | # Examples: 13 | # 14 | # 1 tcp@127.0.0.1:8092,tcp@127.0.0.1:8092 15 | 1 tcp@127.0.0.1:8092 16 | #2 127.0.0.2:8092 17 | 18 | [comet] 19 | # comet rpc go routines size in per comet 20 | # 21 | # Examples: 22 | # 23 | # routine.size 16 24 | routine.size 16 25 | 26 | # comet rpc go routines chan size 27 | # 28 | # Examples: 29 | # 30 | # routine.chan 64 31 | routine.chan 64 32 | 33 | [push] 34 | chan 16 35 | chan.size 100 36 | 37 | [timer] 38 | # timer instance 39 | # 40 | # Examples: 41 | # 42 | # num 8 43 | num 8 44 | 45 | # timer instance size 46 | # 47 | # Examples: 48 | # 49 | # size 1024 50 | size 1000 51 | 52 | [room] 53 | # room's batch push num 54 | # 55 | # Examples: 56 | # 57 | # batch 40 58 | batch 40 59 | 60 | # room's signal push msgs duration 61 | # Examples: 62 | # 63 | # signal 1s 64 | signal 1s -------------------------------------------------------------------------------- /logic/job/kafka.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | llog "log" 5 | "os" 6 | "time" 7 | 8 | "github.com/Shopify/sarama" 9 | log "github.com/thinkboy/log4go" 10 | "github.com/wvanbergen/kafka/consumergroup" 11 | ) 12 | 13 | const ( 14 | KAFKA_GROUP_NAME = "kafka_topic_push_group" 15 | OFFSETS_PROCESSING_TIMEOUT_SECONDS = 10 * time.Second 16 | OFFSETS_COMMIT_INTERVAL = 10 * time.Second 17 | ) 18 | 19 | func InitKafka() error { 20 | log.Info("start topic:%s consumer", Conf.KafkaTopic) 21 | log.Info("consumer group name:%s", KAFKA_GROUP_NAME) 22 | sarama.Logger = llog.New(os.Stdout, "[Sarama] ", llog.LstdFlags) 23 | config := consumergroup.NewConfig() 24 | config.Version = sarama.V0_9_0_1 25 | config.Offsets.Initial = sarama.OffsetNewest 26 | //config.Offsets.ProcessingTimeout = OFFSETS_PROCESSING_TIMEOUT_SECONDS 27 | //config.Offsets.CommitInterval = OFFSETS_COMMIT_INTERVAL 28 | config.Zookeeper.Chroot = Conf.ZKRoot 29 | kafkaTopics := []string{Conf.KafkaTopic} 30 | cg, err := consumergroup.JoinConsumerGroup(KAFKA_GROUP_NAME, kafkaTopics, Conf.ZKAddrs, config) 31 | if err != nil { 32 | return err 33 | } 34 | go func() { 35 | for err := range cg.Errors() { 36 | log.Error("consumer error(%v)", err) 37 | } 38 | }() 39 | go func() { 40 | for msg := range cg.Messages() { 41 | log.Info("deal with topic:%s, partitionId:%d, Offset:%d, Key:%s msg:%s", msg.Topic, msg.Partition, msg.Offset, msg.Key, msg.Value) 42 | push(msg.Value) 43 | cg.CommitUpto(msg) 44 | } 45 | }() 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /logic/job/logic.conf: -------------------------------------------------------------------------------- 1 | # Comet configuration file example 2 | 3 | # Note on units: when memory size is needed, it is possible to specify 4 | # it in the usual form of 1k 5GB 4M and so forth: 5 | # 6 | # 1kb => 1024 bytes 7 | # 1mb => 1024*1024 bytes 8 | # 1gb => 1024*1024*1024 bytes 9 | # 10 | # units are case insensitive so 1GB 1Gb 1gB are all the same. 11 | 12 | # Note on units: when time duration is needed, it is possible to specify 13 | # it in the usual form of 1s 5M 4h and so forth: 14 | # 15 | # 1s => 1000 * 1000 * 1000 nanoseconds 16 | # 1m => 60 seconds 17 | # 1h => 60 minutes 18 | # 19 | # units are case insensitive so 1h 1H are all the same. 20 | 21 | [base] 22 | # When running daemonized, Comet writes a pid file in 23 | # /tmp/logic.pid by default. You can specify a custom pid file 24 | # location here. 25 | pidfile ./logic.pid 26 | 27 | # Sets the maximum number of CPUs that can be executing simultaneously. 28 | # This call will go away when the scheduler improves. By default the number of 29 | # logical CPUs is set. 30 | # 31 | # maxproc 4 32 | 33 | # This is used by logic service profiling (pprof). 34 | # By default logic pprof listens for connections from local interfaces on 7171 35 | # port. It's not safty for listening internet IP addresses. 36 | # 37 | # Examples: 38 | # 39 | # pprof.addrs 192.168.1.100:7171,10.0.0.1:7171 40 | # pprof.addrs 127.0.0.1:7171 41 | # pprof.addrs 0.0.0.0:7171 42 | pprof.addrs localhost:7171 43 | 44 | # The rpc server ip:port bind. 45 | # 46 | # Examples: 47 | # 48 | # rpc.addrs 192.168.1.100:7171,10.0.0.1:7172 49 | # rpc.addrs 127.0.0.1:7171 50 | # rpc.addrs 0.0.0.0:7171 51 | rpc.addrs tcp@localhost:7170 52 | 53 | http.addrs tcp@0.0.0.0:7172 54 | 55 | http.read.timeout 5s 56 | http.write.timeout 5s 57 | 58 | # The working directory. 59 | # 60 | # The log will be written inside this directory, with the filename specified 61 | # above using the 'logfile' configuration directive. 62 | # 63 | # Note that you must specify a directory here, not a file name. 64 | dir ./ 65 | 66 | # Log4go configuration xml path. 67 | # 68 | # Examples: 69 | # 70 | # log /xxx/xxx/log.xml 71 | log ./logic-log.xml 72 | 73 | [router.addrs] 74 | # router service rpc address 75 | # 76 | # Examples: 77 | # 78 | # rpc.addrs tcp@localhost:7270,tcp@localhost:7270 79 | 1 tcp@localhost:7270 80 | #2 localhost:7271 81 | 82 | [kafka] 83 | addrs 127.0.0.1:9092,127.0.0.2:9092 84 | 85 | 86 | [message] 87 | # The message rpc server ip:port bind. 88 | # 89 | # Example: 90 | # 91 | # rpc.addr 0.0.0.0:7171 92 | rpc.bind tcp@localhost:8070 93 | -------------------------------------------------------------------------------- /logic/job/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "runtime" 6 | 7 | log "github.com/thinkboy/log4go" 8 | ) 9 | 10 | func main() { 11 | flag.Parse() 12 | if err := InitConfig(); err != nil { 13 | panic(err) 14 | } 15 | log.LoadConfiguration(Conf.Log) 16 | runtime.GOMAXPROCS(runtime.NumCPU()) 17 | //comet 18 | err := InitComet(Conf.Comets, 19 | CometOptions{ 20 | RoutineSize: Conf.RoutineSize, 21 | RoutineChan: Conf.RoutineChan, 22 | }) 23 | if err != nil { 24 | log.Warn("comet rpc current can't connect, retry") 25 | } 26 | //round 27 | round := NewRound(RoundOptions{ 28 | Timer: Conf.Timer, 29 | TimerSize: Conf.TimerSize, 30 | }) 31 | //room 32 | InitRoomBucket(round, 33 | RoomOptions{ 34 | BatchNum: Conf.RoomBatch, 35 | SignalTime: Conf.RoomSignal, 36 | }) 37 | //room info 38 | MergeRoomServers() 39 | go SyncRoomServers() 40 | InitPush() 41 | if err := InitKafka(); err != nil { 42 | panic(err) 43 | } 44 | // block until a signal is received. 45 | InitSignal() 46 | } 47 | -------------------------------------------------------------------------------- /logic/job/push.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "imgo/libs/define" 6 | "imgo/libs/proto" 7 | "math/rand" 8 | 9 | log "github.com/thinkboy/log4go" 10 | ) 11 | 12 | type pushArg struct { 13 | ServerId int32 14 | SubKeys []string 15 | Msg []byte 16 | RoomId int32 17 | } 18 | 19 | var ( 20 | pushChs []chan *pushArg 21 | ) 22 | 23 | func InitPush() { 24 | pushChs = make([]chan *pushArg, Conf.PushChan) 25 | for i := 0; i < Conf.PushChan; i++ { 26 | pushChs[i] = make(chan *pushArg, Conf.PushChanSize) 27 | go processPush(pushChs[i]) 28 | } 29 | } 30 | 31 | // push routine 32 | func processPush(ch chan *pushArg) { 33 | var arg *pushArg 34 | for { 35 | arg = <-ch 36 | mPushComet(arg.ServerId, arg.SubKeys, arg.Msg) 37 | } 38 | } 39 | 40 | func push(msg []byte) (err error) { 41 | m := &proto.KafkaMsg{} 42 | if err = json.Unmarshal(msg, m); err != nil { 43 | log.Error("json.Unmarshal(%s) error(%s)", msg, err) 44 | return 45 | } 46 | switch m.OP { 47 | case define.KAFKA_MESSAGE_MULTI: 48 | pushChs[rand.Int()%Conf.PushChan] <- &pushArg{ServerId: m.ServerId, SubKeys: m.SubKeys, Msg: m.Msg, RoomId: define.NoRoom} 49 | case define.KAFKA_MESSAGE_BROADCAST: 50 | broadcast(m.Msg) 51 | case define.KAFKA_MESSAGE_BROADCAST_ROOM: 52 | room := roomBucket.Get(int32(m.RoomId)) 53 | if m.Ensure { 54 | go room.EPush(0, define.OP_SEND_SMS_REPLY, m.Msg) 55 | } else { 56 | err = room.Push(0, define.OP_SEND_SMS_REPLY, m.Msg) 57 | if err != nil { 58 | log.Error("room.Push(%s) roomId:%d error(%v)", m.Msg, err) 59 | } 60 | } 61 | default: 62 | log.Error("unknown operation:%s", m.OP) 63 | } 64 | return 65 | } 66 | -------------------------------------------------------------------------------- /logic/job/room.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "imgo/libs/bytes" 5 | "imgo/libs/define" 6 | "imgo/libs/proto" 7 | itime "imgo/libs/time" 8 | "sync" 9 | "time" 10 | 11 | log "github.com/thinkboy/log4go" 12 | ) 13 | 14 | const ( 15 | roomMapCup = 100 16 | ) 17 | 18 | var roomBucket *RoomBucket 19 | 20 | type RoomBucket struct { 21 | roomNum int 22 | rooms map[int32]*Room 23 | rwLock sync.RWMutex 24 | options RoomOptions 25 | round *Round 26 | } 27 | 28 | func InitRoomBucket(r *Round, options RoomOptions) { 29 | roomBucket = &RoomBucket{ 30 | roomNum: 0, 31 | rooms: make(map[int32]*Room, roomMapCup), 32 | rwLock: sync.RWMutex{}, 33 | options: options, 34 | round: r, 35 | } 36 | } 37 | 38 | func (this *RoomBucket) Get(roomId int32) (r *Room) { 39 | this.rwLock.RLock() 40 | room, ok := this.rooms[roomId] 41 | if !ok { 42 | room = NewRoom(roomId, this.round.Timer(this.roomNum), this.options) 43 | this.rooms[roomId] = room 44 | this.roomNum++ 45 | log.Debug("new roomId:%d num:%d", roomId, this.roomNum) 46 | } 47 | this.rwLock.RUnlock() 48 | return room 49 | } 50 | 51 | type RoomOptions struct { 52 | BatchNum int 53 | SignalTime time.Duration 54 | } 55 | 56 | type Room struct { 57 | id int32 58 | rLock sync.RWMutex 59 | proto chan *proto.Proto 60 | } 61 | 62 | var ( 63 | roomReadyProto = &proto.Proto{Operation: define.OP_ROOM_READY} 64 | ) 65 | 66 | // NewRoom new a room struct, store channel room info. 67 | func NewRoom(id int32, t *itime.Timer, options RoomOptions) (r *Room) { 68 | r = new(Room) 69 | r.id = id 70 | r.proto = make(chan *proto.Proto, options.BatchNum*2) 71 | go r.pushproc(t, options.BatchNum, options.SignalTime) 72 | return 73 | } 74 | 75 | // Push push msg to the room, if chan full discard it. 76 | func (r *Room) Push(ver int16, operation int32, msg []byte) (err error) { 77 | var p = &proto.Proto{Ver: ver, Operation: operation, Body: msg} 78 | select { 79 | case r.proto <- p: 80 | default: 81 | err = ErrRoomFull 82 | } 83 | return 84 | } 85 | 86 | // EPush ensure push msg to the room. 87 | func (r *Room) EPush(ver int16, operation int32, msg []byte) { 88 | var p = &proto.Proto{Ver: ver, Operation: operation, Body: msg} 89 | r.proto <- p 90 | return 91 | } 92 | 93 | // pushproc merge proto and push msgs in batch. 94 | func (r *Room) pushproc(timer *itime.Timer, batch int, sigTime time.Duration) { 95 | var ( 96 | n int 97 | last time.Time 98 | p *proto.Proto 99 | td *itime.TimerData 100 | buf = bytes.NewWriterSize(int(proto.MaxBodySize)) 101 | ) 102 | log.Debug("start room: %d goroutine", r.id) 103 | td = timer.Add(sigTime, func() { 104 | select { 105 | case r.proto <- roomReadyProto: 106 | default: 107 | } 108 | }) 109 | for { 110 | if p = <-r.proto; p == nil { 111 | break // exit 112 | } else if p != roomReadyProto { 113 | // merge buffer ignore error, always nil 114 | p.WriteTo(buf) 115 | if n++; n == 1 { 116 | last = time.Now() 117 | timer.Set(td, sigTime) 118 | continue 119 | } else if n < batch { 120 | if sigTime > time.Now().Sub(last) { 121 | continue 122 | } 123 | } 124 | } else { 125 | if n == 0 { 126 | continue 127 | } 128 | } 129 | broadcastRoomBytes(r.id, buf.Buffer()) 130 | n = 0 131 | // TODO use reset buffer 132 | // after push to room channel, renew a buffer, let old buffer gc 133 | buf = bytes.NewWriterSize(buf.Size()) 134 | } 135 | timer.Del(td) 136 | log.Debug("room: %d goroutine exit", r.id) 137 | } 138 | -------------------------------------------------------------------------------- /logic/job/round.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "imgo/libs/time" 5 | ) 6 | 7 | type RoundOptions struct { 8 | Timer int 9 | TimerSize int 10 | } 11 | 12 | // Ronnd userd for connection round-robin get a timer for split big lock. 13 | type Round struct { 14 | timers []time.Timer 15 | options RoundOptions 16 | timerIdx int 17 | } 18 | 19 | // NewRound new a round struct. 20 | func NewRound(options RoundOptions) (r *Round) { 21 | var i int 22 | r = new(Round) 23 | r.options = options 24 | // timer 25 | r.timers = make([]time.Timer, options.Timer) 26 | for i = 0; i < options.Timer; i++ { 27 | r.timers[i].Init(options.TimerSize) 28 | } 29 | return 30 | } 31 | 32 | // Timer get a timer. 33 | func (r *Round) Timer(rn int) *time.Timer { 34 | return &(r.timers[rn%r.options.Timer]) 35 | } 36 | -------------------------------------------------------------------------------- /logic/job/signal.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "syscall" 7 | 8 | log "github.com/thinkboy/log4go" 9 | ) 10 | 11 | // InitSignal register signals handler. 12 | func InitSignal() { 13 | c := make(chan os.Signal, 1) 14 | signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGSTOP) 15 | for { 16 | s := <-c 17 | log.Info("job[%s] get a signal %s", Ver, s.String()) 18 | switch s { 19 | case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGINT: 20 | return 21 | case syscall.SIGHUP: 22 | continue 23 | default: 24 | return 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /logic/job/ver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | Ver = "0.1" 5 | ) 6 | -------------------------------------------------------------------------------- /logic/kafka.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "imgo/libs/define" 6 | "imgo/libs/encoding/binary" 7 | "imgo/libs/proto" 8 | 9 | "github.com/Shopify/sarama" 10 | log "github.com/thinkboy/log4go" 11 | ) 12 | 13 | const ( 14 | KafkaPushsTopic = "KafkaPushsTopic" 15 | ) 16 | 17 | var ( 18 | producer sarama.AsyncProducer 19 | ) 20 | 21 | func InitKafka(kafkaAddrs []string) (err error) { 22 | config := sarama.NewConfig() 23 | config.Version = sarama.V0_9_0_1 24 | //config.Producer.RequiredAcks = sarama.NoResponse 25 | config.Producer.Partitioner = sarama.NewHashPartitioner 26 | config.Producer.Return.Successes = true 27 | config.Producer.Return.Errors = true 28 | producer, err = sarama.NewAsyncProducer(kafkaAddrs, config) 29 | go handleSuccess() 30 | go handleError() 31 | return 32 | } 33 | 34 | func handleSuccess() { 35 | var ( 36 | pm *sarama.ProducerMessage 37 | ) 38 | for { 39 | pm = <-producer.Successes() 40 | if pm != nil { 41 | log.Info("producer message success, partition:%d offset:%d key:%v valus:%s", pm.Partition, pm.Offset, pm.Key, pm.Value) 42 | } 43 | } 44 | } 45 | 46 | func handleError() { 47 | var ( 48 | err *sarama.ProducerError 49 | ) 50 | for { 51 | err = <-producer.Errors() 52 | if err != nil { 53 | log.Error("producer message error, partition:%d offset:%d key:%v valus:%s error(%v)", err.Msg.Partition, err.Msg.Offset, err.Msg.Key, err.Msg.Value, err.Err) 54 | } 55 | } 56 | } 57 | 58 | func mpushKafka(serverId int32, keys []string, msg []byte) (err error) { 59 | var ( 60 | vBytes []byte 61 | v = &proto.KafkaMsg{OP: define.KAFKA_MESSAGE_MULTI, ServerId: serverId, SubKeys: keys, Msg: msg} 62 | ) 63 | if vBytes, err = json.Marshal(v); err != nil { 64 | return 65 | } 66 | producer.Input() <- &sarama.ProducerMessage{Topic: KafkaPushsTopic, Value: sarama.ByteEncoder(vBytes)} 67 | return 68 | } 69 | 70 | func broadcastKafka(msg []byte) (err error) { 71 | var ( 72 | vBytes []byte 73 | v = &proto.KafkaMsg{OP: define.KAFKA_MESSAGE_BROADCAST, Msg: msg} 74 | ) 75 | if vBytes, err = json.Marshal(v); err != nil { 76 | return 77 | } 78 | producer.Input() <- &sarama.ProducerMessage{Topic: KafkaPushsTopic, Value: sarama.ByteEncoder(vBytes)} 79 | return 80 | } 81 | 82 | func broadcastRoomKafka(rid int32, msg []byte, ensure bool) (err error) { 83 | var ( 84 | vBytes []byte 85 | ridBytes [4]byte 86 | v = &proto.KafkaMsg{OP: define.KAFKA_MESSAGE_BROADCAST_ROOM, RoomId: rid, Msg: msg, Ensure: ensure} 87 | ) 88 | if vBytes, err = json.Marshal(v); err != nil { 89 | return 90 | } 91 | binary.BigEndian.PutInt32(ridBytes[:], rid) 92 | producer.Input() <- &sarama.ProducerMessage{Topic: KafkaPushsTopic, Key: sarama.ByteEncoder(ridBytes[:]), Value: sarama.ByteEncoder(vBytes)} 93 | return 94 | } 95 | -------------------------------------------------------------------------------- /logic/logic-log.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | stdout 4 | console 5 | 6 | DEBUG 7 | 8 | 9 | debug_file 10 | file 11 | DEBUG 12 | /tmp/logic_debug.log 13 | [%D %T] [%L] [%S] %M 14 | true 15 | 0M 16 | 0K 17 | true 18 | 19 | 20 | info_file 21 | file 22 | INFO 23 | /tmp/logic_info.log 24 | 35 | [%D %T] [%L] [%S] %M 36 | true 37 | 0M 38 | 0K 39 | true 40 | 41 | 42 | warn_file 43 | file 44 | WARNING 45 | /tmp/logic_warn.log 46 | [%D %T] [%L] [%S] %M 47 | true 48 | 0M 49 | 0K 50 | true 51 | 52 | 53 | error_file 54 | file 55 | ERROR 56 | /tmp/logic_error.log 57 | [%D %T] [%L] [%S] %M 58 | true 59 | 0M 60 | 0K 61 | true 62 | 63 | 64 | -------------------------------------------------------------------------------- /logic/logic.conf: -------------------------------------------------------------------------------- 1 | # Comet configuration file example 2 | 3 | # Note on units: when memory size is needed, it is possible to specify 4 | # it in the usual form of 1k 5GB 4M and so forth: 5 | # 6 | # 1kb => 1024 bytes 7 | # 1mb => 1024*1024 bytes 8 | # 1gb => 1024*1024*1024 bytes 9 | # 10 | # units are case insensitive so 1GB 1Gb 1gB are all the same. 11 | 12 | # Note on units: when time duration is needed, it is possible to specify 13 | # it in the usual form of 1s 5M 4h and so forth: 14 | # 15 | # 1s => 1000 * 1000 * 1000 nanoseconds 16 | # 1m => 60 seconds 17 | # 1h => 60 minutes 18 | # 19 | # units are case insensitive so 1h 1H are all the same. 20 | 21 | [base] 22 | # When running daemonized, Comet writes a pid file in 23 | # /tmp/logic.pid by default. You can specify a custom pid file 24 | # location here. 25 | pidfile ./logic.pid 26 | 27 | # Sets the maximum number of CPUs that can be executing simultaneously. 28 | # This call will go away when the scheduler improves. By default the number of 29 | # logical CPUs is set. 30 | # 31 | # maxproc 4 32 | 33 | # This is used by logic service profiling (pprof). 34 | # By default logic pprof listens for connections from local interfaces on 7171 35 | # port. It's not safty for listening internet IP addresses. 36 | # 37 | # Examples: 38 | # 39 | # pprof.addrs 192.168.1.100:7171,10.0.0.1:7171 40 | # pprof.addrs 127.0.0.1:7171 41 | # pprof.addrs 0.0.0.0:7171 42 | pprof.addrs localhost:7171 43 | 44 | # The rpc server ip:port bind. 45 | # 46 | # Examples: 47 | # 48 | # rpc.addrs 192.168.1.100:7171,10.0.0.1:7172 49 | # rpc.addrs 127.0.0.1:7171 50 | # rpc.addrs 0.0.0.0:7171 51 | rpc.addrs tcp@localhost:7170 52 | 53 | http.addrs tcp@0.0.0.0:7172 54 | 55 | http.read.timeout 5s 56 | http.write.timeout 5s 57 | 58 | # The working directory. 59 | # 60 | # The log will be written inside this directory, with the filename specified 61 | # above using the 'logfile' configuration directive. 62 | # 63 | # Note that you must specify a directory here, not a file name. 64 | dir ./ 65 | 66 | # Log4go configuration xml path. 67 | # 68 | # Examples: 69 | # 70 | # log /xxx/xxx/log.xml 71 | log ./logic-log.xml 72 | 73 | [router.addrs] 74 | # router service rpc address 75 | # 76 | # Examples: 77 | # 78 | # rpc.addrs tcp@localhost:7270,tcp@localhost:7270 79 | 1 tcp@localhost:7270 80 | #2 localhost:7271 81 | 82 | [kafka] 83 | addrs 127.0.0.1:9092,127.0.0.2:9092 84 | 85 | 86 | [message] 87 | # The message rpc server ip:port bind. 88 | # 89 | # Example: 90 | # 91 | # rpc.addr 0.0.0.0:7171 92 | rpc.bind tcp@localhost:8070 93 | -------------------------------------------------------------------------------- /logic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "imgo/libs/perf" 6 | "runtime" 7 | 8 | log "github.com/thinkboy/log4go" 9 | ) 10 | 11 | func main() { 12 | flag.Parse() 13 | if err := InitConfig(); err != nil { 14 | panic(err) 15 | } 16 | runtime.GOMAXPROCS(Conf.MaxProc) 17 | log.LoadConfiguration(Conf.Log) 18 | defer log.Close() 19 | log.Info("logic[%s] start", Ver) 20 | perf.Init(Conf.PprofAddrs) 21 | // router rpc 22 | if err := InitRouter(Conf.RouterRPCAddrs); err != nil { 23 | log.Warn("router rpc current can't connect, retry") 24 | } 25 | MergeCount() 26 | go SyncCount() 27 | // logic rpc 28 | if err := InitRPC(NewDefaultAuther()); err != nil { 29 | panic(err) 30 | } 31 | if err := InitKafka(Conf.KafkaAddrs); err != nil { 32 | panic(err) 33 | } 34 | if err := InitMessage(Conf.MessageAddr); err != nil { 35 | panic(err) 36 | } 37 | if err := InitHTTP(); err != nil { 38 | panic(err) 39 | } 40 | // block until a signal is received. 41 | InitSignal() 42 | } 43 | -------------------------------------------------------------------------------- /logic/ret.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | OK = 1 5 | InternalErr = 65535 6 | NotFoundServer = 1001 7 | ParamErr = 65534 8 | TokenErr = 65533 9 | ) 10 | -------------------------------------------------------------------------------- /logic/rpc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | inet "imgo/libs/net" 5 | "imgo/libs/proto" 6 | "net" 7 | "net/rpc" 8 | 9 | log "github.com/thinkboy/log4go" 10 | ) 11 | 12 | func InitRPC(auther Auther) (err error) { 13 | var ( 14 | network, addr string 15 | c = &RPC{auther: auther} 16 | ) 17 | rpc.Register(c) 18 | for i := 0; i < len(Conf.RPCAddrs); i++ { 19 | log.Info("start listen rpc addr: \"%s\"", Conf.RPCAddrs[i]) 20 | if network, addr, err = inet.ParseNetwork(Conf.RPCAddrs[i]); err != nil { 21 | log.Error("inet.ParseNetwork() error(%v)", err) 22 | return 23 | } 24 | go rpcListen(network, addr) 25 | } 26 | return 27 | } 28 | 29 | func rpcListen(network, addr string) { 30 | l, err := net.Listen(network, addr) 31 | if err != nil { 32 | log.Error("net.Listen(\"%s\", \"%s\") error(%v)", network, addr, err) 33 | panic(err) 34 | } 35 | // if process exit, then close the rpc bind 36 | defer func() { 37 | log.Info("rpc addr: \"%s\" close", addr) 38 | if err := l.Close(); err != nil { 39 | log.Error("listener.Close() error(%v)", err) 40 | } 41 | }() 42 | rpc.Accept(l) 43 | } 44 | 45 | // RPC 46 | type RPC struct { 47 | auther Auther 48 | } 49 | 50 | func (r *RPC) Ping(arg *proto.NoArg, reply *proto.NoReply) error { 51 | return nil 52 | } 53 | 54 | // Connect auth and registe login 55 | func (r *RPC) Connect(arg *proto.ConnArg, reply *proto.ConnReply) (err error) { 56 | if arg == nil { 57 | err = ErrConnectArgs 58 | log.Error("Connect() error(%v)", err) 59 | return 60 | } 61 | var ( 62 | seq int32 63 | ) 64 | res, err := r.auther.Auth(arg.Token) 65 | if err != nil { 66 | return 67 | } 68 | reply.RoomId = res.RoomId 69 | if seq, err = connect(res.Uid, arg.Server, reply.RoomId); err == nil { 70 | reply.Key = encode(res.Uid, seq) 71 | go checkOfflineMsg(res.Uid, reply.Key, arg.Server) 72 | } 73 | return 74 | } 75 | 76 | func checkOfflineMsg(uid int64, key string, serverId int32) { 77 | msgs, err := getOfflineMsg(uid) 78 | if err != nil { 79 | log.Error("checkOfflineMsg error:%v", err) 80 | return 81 | } 82 | keyArr := []string{key} 83 | for _, msg := range msgs { 84 | mpushKafka(serverId, keyArr, msg) 85 | } 86 | 87 | } 88 | 89 | // Disconnect notice router offline 90 | func (r *RPC) Disconnect(arg *proto.DisconnArg, reply *proto.DisconnReply) (err error) { 91 | if arg == nil { 92 | err = ErrDisconnectArgs 93 | log.Error("Disconnect() error(%v)", err) 94 | return 95 | } 96 | var ( 97 | uid int64 98 | seq int32 99 | ) 100 | if uid, seq, err = decode(arg.Key); err != nil { 101 | log.Error("decode(\"%s\") error(%s)", arg.Key, err) 102 | return 103 | } 104 | reply.Has, err = disconnect(uid, seq, arg.RoomId) 105 | return 106 | } 107 | -------------------------------------------------------------------------------- /logic/signal.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "syscall" 7 | 8 | log "github.com/thinkboy/log4go" 9 | ) 10 | 11 | // InitSignal register signals handler. 12 | func InitSignal() { 13 | c := make(chan os.Signal, 1) 14 | signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGSTOP) 15 | for { 16 | s := <-c 17 | log.Info("comet[%s] get a signal %s", Ver, s.String()) 18 | switch s { 19 | case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGINT: 20 | return 21 | case syscall.SIGHUP: 22 | reload() 23 | default: 24 | return 25 | } 26 | } 27 | } 28 | 29 | func reload() { 30 | newConf, err := ReloadConfig() 31 | if err != nil { 32 | log.Error("ReloadConfig() error(%v)", err) 33 | return 34 | } 35 | Conf = newConf 36 | } 37 | -------------------------------------------------------------------------------- /logic/store.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | inet "imgo/libs/net" 5 | "imgo/libs/net/xrpc" 6 | "imgo/libs/proto" 7 | "strconv" 8 | 9 | log "github.com/thinkboy/log4go" 10 | ) 11 | 12 | const ( 13 | messageService = "MessageRPC" 14 | messageServicePing = "MessageRPC.Ping" 15 | messageServiceGetPrivate = "MessageRPC.GetPrivate" 16 | messageServiceSavePrivate = "MessageRPC.SavePrivate" 17 | messageServiceSavePrivates = "MessageRPC.SavePrivates" 18 | messageServiceDelPrivate = "MessageRPC.DelPrivate" 19 | messageServiceSaveToken = "MessageRPC.SaveToken" 20 | messageServiceGetUid = "MessageRPC.GetUid" 21 | ) 22 | 23 | var ( 24 | rpcClient *xrpc.Client 25 | ) 26 | 27 | //example:tcp@localhost:8072 28 | func InitMessage(bind string) (err error) { 29 | var ( 30 | network, addr string 31 | ) 32 | 33 | if network, addr, err = inet.ParseNetwork(bind); err != nil { 34 | log.Error("inet.ParseNetwork() error(%v)", err) 35 | return 36 | } 37 | 38 | option := xrpc.ClientOptions{ 39 | Proto: network, 40 | Addr: addr, 41 | } 42 | 43 | rpcClient = xrpc.Dial(option) 44 | 45 | go rpcClient.Ping(messageServicePing) 46 | return 47 | } 48 | 49 | func saveToken(t *proto.Token) error { 50 | ret := 0 51 | if err := rpcClient.Call(messageServiceSaveToken, t, &ret); err != nil { 52 | log.Error("c.Call(\"%s\",\"%v\") error(%v)", messageServiceSaveToken, t, err) 53 | return err 54 | } 55 | return nil 56 | } 57 | 58 | func getUid(token string) (uid int64, err error) { 59 | if err = rpcClient.Call(messageServiceGetUid, token, &uid); err != nil { 60 | log.Error("c.Call(\"%s\",\"%v\") error(%v)", messageServiceGetUid, token, err) 61 | return 62 | } 63 | return 64 | } 65 | 66 | func getOfflineMsg(uid int64) (msgs [][]byte, err error) { 67 | arg := &proto.MessageGetPrivateArgs{ 68 | MsgId: 0, 69 | Key: strconv.FormatInt(uid, 10), 70 | } 71 | res := &proto.MessageGetResp{} 72 | if err = rpcClient.Call(messageServiceGetPrivate, arg, res); err != nil { 73 | log.Error("c.Call(\"%s\",\"%v\") error(%v)", messageServiceGetPrivate, arg, err) 74 | return 75 | } 76 | if len(res.Msgs) == 0 { 77 | return 78 | } 79 | msgs = make([][]byte, 0, len(res.Msgs)) 80 | 81 | for _, msg := range res.Msgs { 82 | msgs = append(msgs, msg.Msg) 83 | } 84 | return 85 | } 86 | -------------------------------------------------------------------------------- /logic/ver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | Ver = "0.1" 5 | ) 6 | -------------------------------------------------------------------------------- /router/cleaner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | const ( 9 | maxCleanNum = 100 10 | ) 11 | 12 | type CleanData struct { 13 | Key int64 14 | expireTime time.Time 15 | next, prev *CleanData 16 | } 17 | 18 | type Cleaner struct { 19 | cLock sync.Mutex 20 | size int 21 | root CleanData 22 | maps map[int64]*CleanData 23 | } 24 | 25 | func NewCleaner(cleaner int) *Cleaner { 26 | c := new(Cleaner) 27 | c.root.next = &c.root 28 | c.root.prev = &c.root 29 | c.size = 0 30 | c.maps = make(map[int64]*CleanData, cleaner) 31 | return c 32 | } 33 | 34 | func (c *Cleaner) PushFront(key int64, expire time.Duration) { 35 | c.cLock.Lock() 36 | if e, ok := c.maps[key]; ok { 37 | // update time 38 | e.expireTime = time.Now().Add(expire) 39 | c.moveToFront(e) 40 | } else { 41 | e = new(CleanData) 42 | e.Key = key 43 | e.expireTime = time.Now().Add(expire) 44 | c.maps[key] = e 45 | at := &c.root 46 | n := at.next 47 | at.next = e 48 | e.prev = at 49 | e.next = n 50 | n.prev = e 51 | c.size++ 52 | } 53 | c.cLock.Unlock() 54 | } 55 | 56 | func (c *CleanData) expire() bool { 57 | return c.expireTime.Before(time.Now()) 58 | } 59 | 60 | func (c *Cleaner) moveToFront(e *CleanData) { 61 | if c.root.next != e { 62 | at := &c.root 63 | // remove element 64 | e.prev.next = e.next 65 | e.next.prev = e.prev 66 | n := at.next 67 | at.next = e 68 | e.prev = at 69 | e.next = n 70 | n.prev = e 71 | } 72 | } 73 | 74 | func (c *Cleaner) Remove(key int64) { 75 | c.cLock.Lock() 76 | c.remove(key) 77 | c.cLock.Unlock() 78 | } 79 | 80 | func (c *Cleaner) remove(key int64) { 81 | if e, ok := c.maps[key]; ok { 82 | delete(c.maps, key) 83 | e.prev.next = e.next 84 | e.next.prev = e.prev 85 | e.next = nil // avoid memory leaks 86 | e.prev = nil // avoid memory leaks 87 | c.size-- 88 | } 89 | } 90 | 91 | func (c *Cleaner) Clean() (keys []int64) { 92 | var ( 93 | i int 94 | e *CleanData 95 | ) 96 | keys = make([]int64, 0, maxCleanNum) 97 | c.cLock.Lock() 98 | for i = 0; i < maxCleanNum; i++ { 99 | if e = c.back(); e != nil { 100 | if e.expire() { 101 | c.remove(e.Key) 102 | keys = append(keys, e.Key) 103 | continue 104 | } 105 | } 106 | break 107 | } 108 | // next time 109 | c.cLock.Unlock() 110 | return 111 | } 112 | 113 | func (c *Cleaner) back() *CleanData { 114 | if c.size == 0 { 115 | return nil 116 | } 117 | return c.root.prev 118 | } 119 | -------------------------------------------------------------------------------- /router/cleaner_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestCleaner(t *testing.T) { 9 | c := NewCleaner(10) 10 | c.PushFront(1, time.Second*1) 11 | time.Sleep(3 * time.Second) 12 | keys := c.Clean() 13 | if len(keys) == 0 { 14 | t.FailNow() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /router/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "runtime" 6 | "time" 7 | 8 | "github.com/Terry-Mao/goconf" 9 | ) 10 | 11 | var ( 12 | gconf *goconf.Config 13 | Conf *Config 14 | confFile string 15 | ) 16 | 17 | func init() { 18 | flag.StringVar(&confFile, "c", "./router.conf", " set router config file path") 19 | } 20 | 21 | type Config struct { 22 | // base section 23 | PidFile string `goconf:"base:pidfile"` 24 | Dir string `goconf:"base:dir"` 25 | Log string `goconf:"base:log"` 26 | MaxProc int `goconf:"base:maxproc"` 27 | PprofAddrs []string `goconf:"base:pprof.addrs:,"` 28 | // rpc 29 | RPCAddrs []string `goconf:"rpc:addrs:,"` 30 | // bucket 31 | Bucket int `goconf:"bucket:bucket"` 32 | Server int `goconf:"bucket:server"` 33 | Cleaner int `goconf:"bucket:cleaner"` 34 | BucketCleanPeriod time.Duration `goconf:"bucket:clean.period:time"` 35 | // session 36 | Session int `goconf:"session:session"` 37 | SessionExpire time.Duration `goconf:"session:expire:time"` 38 | } 39 | 40 | func NewConfig() *Config { 41 | return &Config{ 42 | // base section 43 | PidFile: "/tmp/imgo-router.pid", 44 | Dir: "./", 45 | Log: "./router-log.xml", 46 | MaxProc: runtime.NumCPU(), 47 | PprofAddrs: []string{"localhost:6971"}, 48 | // rpc 49 | RPCAddrs: []string{"localhost:9090"}, 50 | // bucket 51 | Bucket: runtime.NumCPU(), 52 | Server: 5, 53 | Cleaner: 1000, 54 | BucketCleanPeriod: time.Hour * 1, 55 | // session 56 | Session: 1000, 57 | SessionExpire: time.Hour * 1, 58 | } 59 | } 60 | 61 | // InitConfig init the global config. 62 | func InitConfig() (err error) { 63 | Conf = NewConfig() 64 | gconf = goconf.New() 65 | if err = gconf.Parse(confFile); err != nil { 66 | return err 67 | } 68 | if err := gconf.Unmarshal(Conf); err != nil { 69 | return err 70 | } 71 | return nil 72 | } 73 | 74 | func ReloadConfig() (*Config, error) { 75 | conf := NewConfig() 76 | ngconf, err := gconf.Reload() 77 | if err != nil { 78 | return nil, err 79 | } 80 | if err := ngconf.Unmarshal(conf); err != nil { 81 | return nil, err 82 | } 83 | gconf = ngconf 84 | return conf, nil 85 | } 86 | -------------------------------------------------------------------------------- /router/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "imgo/libs/perf" 6 | "runtime" 7 | 8 | log "github.com/thinkboy/log4go" 9 | ) 10 | 11 | const ( 12 | VERSION = "0.1" 13 | ) 14 | 15 | func main() { 16 | flag.Parse() 17 | if err := InitConfig(); err != nil { 18 | panic(err) 19 | } 20 | runtime.GOMAXPROCS(Conf.MaxProc) 21 | log.LoadConfiguration(Conf.Log) 22 | defer log.Close() 23 | log.Info("router[%s] start", VERSION) 24 | // start prof 25 | perf.Init(Conf.PprofAddrs) 26 | // start rpc 27 | buckets := make([]*Bucket, Conf.Bucket) 28 | for i := 0; i < Conf.Bucket; i++ { 29 | buckets[i] = NewBucket(Conf.Session, Conf.Server, Conf.Cleaner) 30 | } 31 | if err := InitRPC(buckets); err != nil { 32 | panic(err) 33 | } 34 | // block until a signal is received. 35 | InitSignal() 36 | } 37 | -------------------------------------------------------------------------------- /router/router-log.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | stdout 4 | console 5 | 6 | DEBUG 7 | 8 | 9 | debug_file 10 | file 11 | DEBUG 12 | /tmp/router_debug.log 13 | [%D %T] [%L] [%S] %M 14 | true 15 | 0M 16 | 0K 17 | true 18 | 19 | 20 | info_file 21 | file 22 | INFO 23 | /tmp/router_info.log 24 | 35 | [%D %T] [%L] [%S] %M 36 | true 37 | 0M 38 | 0K 39 | true 40 | 41 | 42 | warn_file 43 | file 44 | WARNING 45 | /tmp/router_warn.log 46 | [%D %T] [%L] [%S] %M 47 | true 48 | 0M 49 | 0K 50 | true 51 | 52 | 53 | error_file 54 | file 55 | ERROR 56 | /tmp/router_error.log 57 | [%D %T] [%L] [%S] %M 58 | true 59 | 0M 60 | 0K 61 | true 62 | 63 | 64 | -------------------------------------------------------------------------------- /router/router.conf: -------------------------------------------------------------------------------- 1 | # Router configuration file example 2 | 3 | # Note on units: when memory size is needed, it is possible to specify 4 | # it in the usual form of 1k 5GB 4M and so forth: 5 | # 6 | # 1kb => 1024 bytes 7 | # 1mb => 1024*1024 bytes 8 | # 1gb => 1024*1024*1024 bytes 9 | # 10 | # units are case insensitive so 1GB 1Gb 1gB are all the same. 11 | 12 | # Note on units: when time duration is needed, it is possible to specify 13 | # it in the usual form of 1s 5M 4h and so forth: 14 | # 15 | # 1s => 1000 * 1000 * 1000 nanoseconds 16 | # 1m => 60 seconds 17 | # 1h => 60 minutes 18 | # 19 | # units are case insensitive so 1h 1H are all the same. 20 | 21 | [base] 22 | # When running daemonized, Router writes a pid file in 23 | # /tmp/router.pid by default. You can specify a custom pid file 24 | # location here. 25 | pidfile ./router.pid 26 | 27 | # Sets the maximum number of CPUs that can be executing simultaneously. 28 | # This call will go away when the scheduler improves. By default the number of 29 | # logical CPUs is set. 30 | # 31 | # maxproc 4 32 | 33 | # This is used by router service profiling (pprof). 34 | # By default router pprof listens for connections from local interfaces on 7271 35 | # port. It's not safty for listening internet IP addresses. 36 | # 37 | # Examples: 38 | # 39 | # pprof.addrs 192.168.1.100:7271,10.0.0.1:7271 40 | # pprof.addrs 127.0.0.1:7271 41 | # pprof.addrs 0.0.0.0:7271 42 | pprof.addrs localhost:7271 43 | 44 | # The working directory. 45 | # 46 | # The log will be written inside this directory, with the filename specified 47 | # above using the 'logfile' configuration directive. 48 | # 49 | # Note that you must specify a directory here, not a file name. 50 | dir ./ 51 | 52 | # Log4go configuration xml path. 53 | # 54 | # Examples: 55 | # 56 | # log /xxx/xxx/log.xml 57 | log ./router-log.xml 58 | 59 | # rpc listen and service 60 | [rpc] 61 | 62 | # The rpc server network@ip:port bind. 63 | # 64 | # bind localhost:8092 65 | addrs tcp@localhost:7270 66 | 67 | [bucket] 68 | bucket 16 69 | server 5 70 | cleaner 16 71 | 72 | [session] 73 | session 16 74 | expire 1h 75 | -------------------------------------------------------------------------------- /router/session.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type Session struct { 4 | seq int32 5 | servers map[int32]int32 // seq:server 6 | rooms map[int32]map[int32]int32 // roomid:seq:server with specified room id 7 | } 8 | 9 | // NewSession new a session struct. store the seq and serverid. 10 | func NewSession(server int) *Session { 11 | s := new(Session) 12 | s.servers = make(map[int32]int32, server) 13 | s.rooms = make(map[int32]map[int32]int32) 14 | s.seq = 0 15 | return s 16 | } 17 | 18 | func (s *Session) nextSeq() int32 { 19 | s.seq++ 20 | return s.seq 21 | } 22 | 23 | // Put put a session according with sub key. 24 | func (s *Session) Put(server int32) (seq int32) { 25 | seq = s.nextSeq() 26 | s.servers[seq] = server 27 | return 28 | } 29 | 30 | // PutRoom put a session in a room according with subkey. 31 | func (s *Session) PutRoom(server int32, roomId int32) (seq int32) { 32 | var ( 33 | ok bool 34 | room map[int32]int32 35 | ) 36 | seq = s.Put(server) 37 | if room, ok = s.rooms[roomId]; !ok { 38 | room = make(map[int32]int32) 39 | s.rooms[roomId] = room 40 | } 41 | room[seq] = server 42 | return 43 | } 44 | 45 | func (s *Session) Servers() (seqs []int32, servers []int32) { 46 | var ( 47 | i = len(s.servers) 48 | seq, server int32 49 | ) 50 | seqs = make([]int32, i) 51 | servers = make([]int32, i) 52 | for seq, server = range s.servers { 53 | i-- 54 | seqs[i] = seq 55 | servers[i] = server 56 | } 57 | return 58 | } 59 | 60 | // Del delete the session by sub key. 61 | func (s *Session) Del(seq int32) (has, empty bool, server int32) { 62 | if server, has = s.servers[seq]; has { 63 | delete(s.servers, seq) 64 | } 65 | empty = (len(s.servers) == 0) 66 | return 67 | } 68 | 69 | // DelRoom delete the session and room by subkey. 70 | func (s *Session) DelRoom(seq int32, roomId int32) (has, empty bool, server int32) { 71 | var ( 72 | ok bool 73 | room map[int32]int32 74 | ) 75 | has, empty, server = s.Del(seq) 76 | if room, ok = s.rooms[roomId]; ok { 77 | delete(room, seq) 78 | if len(room) == 0 { 79 | delete(s.rooms, roomId) 80 | } 81 | } 82 | return 83 | } 84 | 85 | func (s *Session) Count() int { 86 | return len(s.servers) 87 | } 88 | -------------------------------------------------------------------------------- /router/signal.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "syscall" 7 | 8 | log "github.com/thinkboy/log4go" 9 | ) 10 | 11 | // InitSignal register signals handler. 12 | func InitSignal() { 13 | c := make(chan os.Signal, 1) 14 | signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGSTOP) 15 | for { 16 | s := <-c 17 | log.Info("router[%s] get a signal %s", VERSION, s.String()) 18 | switch s { 19 | case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGINT: 20 | return 21 | case syscall.SIGHUP: 22 | reload() 23 | default: 24 | return 25 | } 26 | } 27 | } 28 | 29 | func reload() { 30 | newConf, err := ReloadConfig() 31 | if err != nil { 32 | log.Error("ReloadConfig() error(%v)", err) 33 | return 34 | } 35 | Conf = newConf 36 | } 37 | -------------------------------------------------------------------------------- /router/test/router_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | proto "github.com/Terry-Mao/imgo/proto/router" 5 | rpc "github.com/Terry-Mao/protorpc" 6 | "testing" 7 | ) 8 | 9 | func TestRouterConnect(t *testing.T) { 10 | c, err := rpc.Dial("tcp", "localhost:7270") 11 | if err != nil { 12 | t.Error(err) 13 | t.FailNow() 14 | } 15 | arg := &proto.ConnArg{} 16 | reply := &proto.ConnReply{} 17 | arg.UserId = 1 18 | arg.Server = 0 19 | if err = c.Call("RouterRPC.Connect", arg, reply); err != nil { 20 | t.Error(err) 21 | t.FailNow() 22 | } 23 | if reply.Seq != 1 { 24 | t.Errorf("reply seq: %d not equal 0", reply.Seq) 25 | t.FailNow() 26 | } 27 | arg.UserId = 1 28 | arg.Server = 0 29 | if err = c.Call("RouterRPC.Connect", arg, reply); err != nil { 30 | t.Error(err) 31 | t.FailNow() 32 | } 33 | if reply.Seq != 2 { 34 | t.Errorf("reply seq: %d not equal 1", reply.Seq) 35 | t.FailNow() 36 | } 37 | } 38 | 39 | func TestRouterDisconnect(t *testing.T) { 40 | c, err := rpc.Dial("tcp", "localhost:7270") 41 | if err != nil { 42 | t.Error(err) 43 | t.FailNow() 44 | } 45 | arg := &proto.ConnArg{} 46 | reply := &proto.ConnReply{} 47 | arg.UserId = 2 48 | arg.Server = 0 49 | if err = c.Call("RouterRPC.Connect", arg, reply); err != nil { 50 | t.Error(err) 51 | t.FailNow() 52 | } 53 | if reply.Seq != 1 { 54 | t.Errorf("reply seq: %d not equal 0", reply.Seq) 55 | t.FailNow() 56 | } 57 | arg1 := &proto.DisconnArg{} 58 | arg1.UserId = 2 59 | arg1.Seq = 1 60 | reply1 := &proto.DisconnReply{} 61 | if err = c.Call("RouterRPC.Disconnect", arg1, reply1); err != nil { 62 | t.Error(err) 63 | t.FailNow() 64 | } 65 | if !reply1.Has { 66 | t.Errorf("reply has: %d not equal true", reply1.Has) 67 | t.FailNow() 68 | } 69 | } 70 | 71 | func TestRouterGet(t *testing.T) { 72 | c, err := rpc.Dial("tcp", "localhost:7270") 73 | if err != nil { 74 | t.Error(err) 75 | t.FailNow() 76 | } 77 | 78 | arg := &proto.GetArg{} 79 | reply := &proto.GetReply{} 80 | arg.UserId = 1 81 | if err = c.Call("RouterRPC.Get", arg, reply); err != nil { 82 | t.Error(err) 83 | t.FailNow() 84 | } 85 | if len(reply.Seqs) != 2 || len(reply.Servers) != 2 { 86 | t.Errorf("reply seqs||servers length not equals 2") 87 | t.FailNow() 88 | } 89 | if reply.Seqs[0] != 1 || reply.Seqs[1] != 2 { 90 | t.Error("reply seqs not match") 91 | t.FailNow() 92 | } 93 | if reply.Servers[0] != 0 || reply.Servers[1] != 0 { 94 | t.Errorf("reply servers not match, %v", reply.Servers) 95 | t.FailNow() 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /store/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "fmt" 22 | "runtime" 23 | "time" 24 | 25 | "github.com/Terry-Mao/goconf" 26 | ) 27 | 28 | var ( 29 | Conf *Config 30 | confFile string 31 | ) 32 | 33 | func init() { 34 | flag.StringVar(&confFile, "c", "./message.conf", " set message config file path") 35 | } 36 | 37 | // Config struct 38 | type Config struct { 39 | HttpServerTimeout time.Duration `goconf:"base:http.servertimeout:time"` 40 | //UseToken bool `goconf:"base:get_msg_use_token"` 41 | HttpBind []string `goconf:"base:http.bind:,"` 42 | RPCBind []string `goconf:"base:rpc.bind:,"` 43 | NodeWeight int `goconf:"base:node.weight"` 44 | User string `goconf:"base:user"` 45 | PidFile string `goconf:"base:pidfile"` 46 | Dir string `goconf:"base:dir"` 47 | Log string `goconf:"base:log"` 48 | MaxProc int `goconf:"base:maxproc"` 49 | PprofBind []string `goconf:"base:pprof.bind:,"` 50 | //StorageType string `goconf:"storage:type"` 51 | RedisIdleTimeout time.Duration `goconf:"redis:timeout:time"` 52 | RedisMaxIdle int `goconf:"redis:idle"` 53 | RedisMaxActive int `goconf:"redis:active"` 54 | RedisMaxStore int `goconf:"redis:store"` 55 | //MySQLClean time.Duration `goconf:"mysql:clean:time"` 56 | RedisSource map[string]string `goconf:"-"` 57 | //MySQLSource map[string]string `goconf:"-"` 58 | // zookeeper 59 | //ZookeeperAddr []string `goconf:"zookeeper:addr:,"` 60 | //ZookeeperTimeout time.Duration `goconf:"zookeeper:timeout:time"` 61 | //ZookeeperPath string `goconf:"zookeeper:path"` 62 | } 63 | 64 | // NewConfig parse config file into Config. 65 | func InitConfig() error { 66 | gconf := goconf.New() 67 | if err := gconf.Parse(confFile); err != nil { 68 | return err 69 | } 70 | Conf = &Config{ 71 | // base 72 | HttpServerTimeout: 10 * time.Second, 73 | //UseToken: true, 74 | HttpBind: []string{"localhost:80"}, 75 | RPCBind: []string{"localhost:8070"}, 76 | NodeWeight: 1, 77 | User: "nobody nobody", 78 | PidFile: "./gopush-cluster-message.pid", 79 | Dir: "./", 80 | Log: "./log/xml", 81 | MaxProc: runtime.NumCPU(), 82 | PprofBind: []string{"localhost:8170"}, 83 | // storage 84 | //StorageType: "redis", 85 | // redis 86 | RedisIdleTimeout: 28800 * time.Second, 87 | RedisMaxIdle: 50, 88 | RedisMaxActive: 1000, 89 | RedisMaxStore: 20, 90 | RedisSource: make(map[string]string), 91 | // mysql 92 | //MySQLSource: make(map[string]string), 93 | //MySQLClean: 1 * time.Hour, 94 | // zookeeper 95 | //ZookeeperAddr: []string{"localhost:2181"}, 96 | //ZookeeperTimeout: 30 * time.Second, 97 | //ZookeeperPath: "/gopush-cluster-message", 98 | } 99 | if err := gconf.Unmarshal(Conf); err != nil { 100 | return err 101 | } 102 | // redis section 103 | redisAddrsSec := gconf.Get("redis.source") 104 | if redisAddrsSec != nil { 105 | for _, key := range redisAddrsSec.Keys() { 106 | addr, err := redisAddrsSec.String(key) 107 | if err != nil { 108 | return fmt.Errorf("config section: \"redis.addrs\" key: \"%s\" error(%v)", key, err) 109 | } 110 | Conf.RedisSource[key] = addr 111 | } 112 | } 113 | // mysql section 114 | //dbSource := gconf.Get("mysql.source") 115 | //if dbSource != nil { 116 | //for _, key := range dbSource.Keys() { 117 | //source, err := dbSource.String(key) 118 | //if err != nil { 119 | //return fmt.Errorf("config section: \"mysql.source\" key: \"%s\" error(%v)", key, err) 120 | //} 121 | //Conf.MySQLSource[key] = source 122 | //} 123 | //} 124 | return nil 125 | } 126 | -------------------------------------------------------------------------------- /store/handle.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "imgo/libs/proto" 5 | "net/http" 6 | "strconv" 7 | "time" 8 | 9 | log "code.google.com/p/log4go" 10 | ) 11 | 12 | // GetOfflineMsg get offline mesage http handler. 13 | func GetOfflineMsg(w http.ResponseWriter, r *http.Request) { 14 | if r.Method != "GET" { 15 | http.Error(w, "Method Not Allowed", 405) 16 | return 17 | } 18 | params := r.URL.Query() 19 | uidStr := params.Get("uid") 20 | midStr := params.Get("mid") 21 | callback := params.Get("cb") 22 | token := params.Get("token") 23 | res := map[string]interface{}{"ret": OK} 24 | defer retWrite(w, r, res, callback, time.Now()) 25 | 26 | if uidStr == "" || midStr == "" || token == "" { 27 | res["ret"] = ParamErr 28 | return 29 | } 30 | 31 | uid, err := strconv.ParseInt(uidStr, 10, 64) 32 | if err != nil { 33 | res["ret"] = ParamErr 34 | return 35 | } 36 | 37 | //check token 38 | ok, err := IsTokenValid(uid, token) 39 | if err != nil { 40 | res["ret"] = InternalErr 41 | return 42 | } 43 | if !ok { 44 | res["ret"] = TokenErr 45 | return 46 | } 47 | 48 | mid, err := strconv.ParseInt(midStr, 10, 64) 49 | if err != nil { 50 | res["ret"] = ParamErr 51 | log.Error("strconv.ParseInt(\"%s\", 10, 64) error(%v)", midStr, err) 52 | return 53 | } 54 | 55 | var msgs []*proto.Message 56 | msgs, err = UseStorage.GetPrivate(uidStr, mid) 57 | if err != nil { 58 | res["ret"] = InternalErr 59 | log.Error("UseStorage.GetPrivate(\"%s\",%v) error(%v)", uidStr, mid, err) 60 | return 61 | } 62 | 63 | if len(msgs) == 0 { 64 | return 65 | } 66 | 67 | res["data"] = map[string]interface{}{"msgs": msgs} 68 | return 69 | } 70 | -------------------------------------------------------------------------------- /store/http.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | "time" 9 | 10 | log "code.google.com/p/log4go" 11 | ) 12 | 13 | // StartHTTP start listen http. 14 | func StartHTTP() { 15 | // external 16 | httpServeMux := http.NewServeMux() 17 | // 1.0 18 | httpServeMux.HandleFunc("/1/msg/get", GetOfflineMsg) 19 | for _, bind := range Conf.HttpBind { 20 | log.Info("start http listen addr:\"%s\"", bind) 21 | go httpListen(httpServeMux, bind) 22 | } 23 | } 24 | 25 | func httpListen(mux *http.ServeMux, bind string) { 26 | server := &http.Server{Handler: mux, ReadTimeout: Conf.HttpServerTimeout, WriteTimeout: Conf.HttpServerTimeout} 27 | server.SetKeepAlivesEnabled(false) 28 | l, err := net.Listen("tcp", bind) 29 | if err != nil { 30 | log.Error("net.Listen(\"tcp\", \"%s\") error(%v)", bind, err) 31 | panic(err) 32 | } 33 | if err := server.Serve(l); err != nil { 34 | log.Error("server.Serve() error(%v)", err) 35 | panic(err) 36 | } 37 | } 38 | 39 | // retWrite marshal the result and write to client(get). 40 | func retWrite(w http.ResponseWriter, r *http.Request, res map[string]interface{}, callback string, start time.Time) { 41 | data, err := json.Marshal(res) 42 | if err != nil { 43 | log.Error("json.Marshal(\"%v\") error(%v)", res, err) 44 | return 45 | } 46 | dataStr := "" 47 | if callback == "" { 48 | // Normal json 49 | dataStr = string(data) 50 | } else { 51 | // Jsonp 52 | dataStr = fmt.Sprintf("%s(%s)", callback, string(data)) 53 | } 54 | if n, err := w.Write([]byte(dataStr)); err != nil { 55 | log.Error("w.Write(\"%s\") error(%v)", dataStr, err) 56 | } else { 57 | log.Debug("w.Write(\"%s\") write %d bytes", dataStr, n) 58 | } 59 | log.Info("req: \"%s\", res:\"%s\", ip:\"%s\", time:\"%fs\"", r.URL.String(), dataStr, r.RemoteAddr, time.Now().Sub(start).Seconds()) 60 | } 61 | -------------------------------------------------------------------------------- /store/log.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | stdout 4 | console 5 | 6 | INFO 7 | 8 | 9 | debug_file 10 | file 11 | DEBUG 12 | /tmp/message_debug.log 13 | [%D %T] [%L] [%S] %M 14 | true 15 | 0M 16 | 0K 17 | true 18 | 19 | 20 | info_file 21 | file 22 | INFO 23 | /tmp/message_info.log 24 | 35 | [%D %T] [%L] [%S] %M 36 | true 37 | 0M 38 | 0K 39 | true 40 | 41 | 42 | warn_file 43 | file 44 | WARNING 45 | /tmp/message_warn.log 46 | [%D %T] [%L] [%S] %M 47 | true 48 | 0M 49 | 0K 50 | true 51 | 52 | 53 | error_file 54 | file 55 | ERROR 56 | /tmp/message_error.log 57 | [%D %T] [%L] [%S] %M 58 | true 59 | 0M 60 | 0K 61 | true 62 | 63 | 64 | -------------------------------------------------------------------------------- /store/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "gopush-cluster/perf" 22 | "gopush-cluster/process" 23 | "gopush-cluster/ver" 24 | "runtime" 25 | 26 | log "code.google.com/p/log4go" 27 | ) 28 | 29 | func main() { 30 | flag.Parse() 31 | log.Info("message ver: \"%s\" start", ver.Version) 32 | if err := InitConfig(); err != nil { 33 | panic(err) 34 | } 35 | // Set max routine 36 | runtime.GOMAXPROCS(Conf.MaxProc) 37 | // init log 38 | log.LoadConfiguration(Conf.Log) 39 | defer log.Close() 40 | // start pprof http 41 | perf.Init(Conf.PprofBind) 42 | // Initialize redis 43 | if err := InitStorage(); err != nil { 44 | panic(err) 45 | } 46 | // init rpc service 47 | if err := InitRPC(); err != nil { 48 | panic(err) 49 | } 50 | // process init 51 | if err := process.Init(Conf.User, Conf.Dir, Conf.PidFile); err != nil { 52 | panic(err) 53 | } 54 | 55 | StartHTTP() 56 | 57 | // init signals, block wait signals 58 | sig := InitSignal() 59 | HandleSignal(sig) 60 | // exit 61 | log.Info("message stop") 62 | } 63 | -------------------------------------------------------------------------------- /store/ret.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | OK = 0 5 | NotFoundServer = 1001 6 | ParamErr = 65534 7 | InternalErr = 65535 8 | TokenErr = 65533 9 | ) 10 | -------------------------------------------------------------------------------- /store/rpc.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "errors" 21 | "imgo/libs/proto" 22 | "net" 23 | "net/rpc" 24 | 25 | log "code.google.com/p/log4go" 26 | ) 27 | 28 | // RPC For receive offline messages 29 | type MessageRPC struct { 30 | } 31 | 32 | // InitRPC start accept rpc call. 33 | func InitRPC() error { 34 | msg := &MessageRPC{} 35 | rpc.Register(msg) 36 | for _, bind := range Conf.RPCBind { 37 | log.Info("start rpc listen addr: \"%s\"", bind) 38 | go rpcListen(bind) 39 | } 40 | return nil 41 | } 42 | 43 | func rpcListen(bind string) { 44 | l, err := net.Listen("tcp", bind) 45 | if err != nil { 46 | log.Error("net.Listen(\"tcp\", \"%s\") error(%v)", bind, err) 47 | panic(err) 48 | } 49 | defer func() { 50 | if err := l.Close(); err != nil { 51 | log.Error("listener.Close() error(%v)", err) 52 | } 53 | }() 54 | rpc.Accept(l) 55 | } 56 | 57 | // SavePrivate rpc interface save user private message. 58 | func (r *MessageRPC) SavePrivate(m *proto.MessageSavePrivateArgs, ret *int) error { 59 | if m == nil || m.Msg == nil || m.MsgId < 0 { 60 | return errors.New("parameter error") 61 | } 62 | if err := UseStorage.SavePrivate(m.Key, m.Msg, m.MsgId, m.Expire); err != nil { 63 | log.Error("UseStorage.SavePrivate(\"%s\", \"%s\", %d, %d) error(%v)", m.Key, string(m.Msg), m.MsgId, m.Expire, err) 64 | return err 65 | } 66 | return nil 67 | } 68 | 69 | // SavePrivates rpc interface save user private messages. 70 | func (r *MessageRPC) SavePrivates(m *proto.MessageSavePrivatesArgs, rw *proto.MessageSavePrivatesResp) error { 71 | if m == nil || m.Msg == nil || m.MsgId < 0 { 72 | return errors.New("parameter error") 73 | } 74 | fkeys, err := UseStorage.SavePrivates(m.Keys, m.Msg, m.MsgId, m.Expire) 75 | if err != nil { 76 | log.Error("UseStorage.SavePrivates(\"%v\", \"%s\", %d, %d) error(%v)", m.Keys, string(m.Msg), m.MsgId, m.Expire, err) 77 | } 78 | rw.FKeys = fkeys 79 | return nil 80 | } 81 | 82 | // GetPrivate rpc interface get user private message. 83 | func (r *MessageRPC) GetPrivate(m *proto.MessageGetPrivateArgs, rw *proto.MessageGetResp) error { 84 | if m == nil || m.Key == "" || m.MsgId < 0 { 85 | return proto.ErrParam 86 | } 87 | msgs, err := UseStorage.GetPrivate(m.Key, m.MsgId) 88 | if err != nil { 89 | log.Error("UseStorage.GetPrivate(\"%s\", %d) error(%v)", m.Key, m.MsgId, err) 90 | return err 91 | } 92 | rw.Msgs = msgs 93 | return nil 94 | } 95 | 96 | // Server Ping interface 97 | func (r *MessageRPC) Ping(arg *proto.NoArg, reply *proto.NoReply) error { 98 | return nil 99 | } 100 | 101 | func (r *MessageRPC) SaveToken(t *proto.Token, ret *int) error { 102 | return UseStorage.SaveToken(t.Uid, t.Token, t.Expire) 103 | } 104 | 105 | func (r *MessageRPC) GetUid(token string, uid *int64) (err error) { 106 | *uid, err = UseStorage.GetUid(token) 107 | return 108 | } 109 | -------------------------------------------------------------------------------- /store/signal.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | log "code.google.com/p/log4go" 21 | "os" 22 | "os/signal" 23 | "syscall" 24 | ) 25 | 26 | // InitSignal register signals handler. 27 | func InitSignal() chan os.Signal { 28 | c := make(chan os.Signal, 1) 29 | signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT, syscall.SIGSTOP) 30 | return c 31 | } 32 | 33 | // HandleSignal fetch signal from chan then do exit or reload. 34 | func HandleSignal(c chan os.Signal) { 35 | // Block until a signal is received. 36 | for { 37 | s := <-c 38 | log.Info("get a signal %s", s.String()) 39 | switch s { 40 | case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGINT: 41 | return 42 | case syscall.SIGHUP: 43 | // TODO reload 44 | //return 45 | default: 46 | return 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /store/storage.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2014 Terry Mao, LiuDing All rights reserved. 2 | // This file is part of gopush-cluster. 3 | 4 | // gopush-cluster is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // gopush-cluster is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with gopush-cluster. If not, see . 16 | 17 | package main 18 | 19 | import ( 20 | "encoding/json" 21 | "errors" 22 | "imgo/libs/proto" 23 | ) 24 | 25 | const ( 26 | RedisStorageType = "redis" 27 | //MySQLStorageType = "mysql" 28 | ketamaBase = 255 29 | saveBatchNum = 1000 30 | ) 31 | 32 | var ( 33 | UseStorage Storage 34 | ErrStorageType = errors.New("unknown storage type") 35 | ) 36 | 37 | // Stored messages interface 38 | type Storage interface { 39 | // GetPrivate get private msgs. 40 | GetPrivate(key string, mid int64) ([]*proto.Message, error) 41 | // SavePrivate Save single private msg. 42 | SavePrivate(key string, msg []byte, mid int64, expire uint) error 43 | // Save private msgs return failed keys. 44 | SavePrivates(keys []string, msg json.RawMessage, mid int64, expire uint) ([]string, error) 45 | // DelPrivate delete private msgs. 46 | DelPrivate(key string) error 47 | 48 | //GetToken(uid int64) (string, error) 49 | GetUid(token string) (int64, error) 50 | //DelToken(key, token string) error 51 | SaveToken(uid int64, token string, expire int64) error 52 | } 53 | 54 | // InitStorage init the storage type(mysql or redis). 55 | func InitStorage() error { 56 | //if Conf.StorageType == RedisStorageType { 57 | UseStorage = NewRedisStorage() 58 | //} else if Conf.StorageType == MySQLStorageType { 59 | //UseStorage = NewMySQLStorage() 60 | //} else { 61 | //log.Error("unknown storage type: \"%s\"", Conf.StorageType) 62 | //return ErrStorageType 63 | //} 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /store/token.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // format the key 6 | func formatUidKey(uid int64) string { 7 | return fmt.Sprintf("uid_%d", uid) 8 | } 9 | 10 | // format the token 11 | func formatTokenKey(token string) string { 12 | return fmt.Sprintf("token_%s", token) 13 | } 14 | 15 | // check if the token is valid 16 | func IsTokenValid(uid int64, token string) (ok bool, err error) { 17 | uidReal, err := UseStorage.GetUid(token) 18 | if err != nil { 19 | return false, err 20 | } 21 | return uidReal == uid, nil 22 | } 23 | --------------------------------------------------------------------------------