├── .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 | 
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 | 
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 | 
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 |
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 |
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 |
18 | { 19 | "ret": 1 //错误码 20 | } 21 |22 | 23 |
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