├── .gitignore
├── comet
├── ver.go
├── test_test.go
├── bucket_test.go
├── stat.go
├── round_test.go
├── client
│ ├── main.go
│ ├── proto.go
│ ├── config.go
│ ├── websocket.go
│ ├── websocket_tls.go
│ ├── log.xml
│ ├── client-example.conf
│ └── tcp.go
├── signal.go
├── errors.go
├── whitelist.go
├── channel.go
├── server.go
├── ring.go
├── operation.go
├── room.go
├── round.go
├── logic.go
├── main.go
├── ring_test.go
├── comet-log.xml
├── flash_policy.go
├── rpc.go
└── bucket.go
├── doc
├── arch.png
├── push.gif
├── connect.gif
├── goim.graffle
├── protocol.png
├── benchmark.jpg
├── handshake.png
├── benchmark-comet.jpg
├── benchmark-flow.jpg
├── benchmark-heap.jpg
├── token.md
├── benchmark_cn.md
├── benchmark_en.md
├── push.md
├── proto.md
└── en
│ ├── push.md
│ └── proto.md
├── logic
├── job
│ ├── ver.go
│ ├── errors.go
│ ├── signal.go
│ ├── round.go
│ ├── job.conf
│ ├── main.go
│ ├── comet_info.go
│ ├── kafka.go
│ ├── push.go
│ ├── config.go
│ ├── logic.conf
│ ├── job-log.xml
│ └── room.go
├── ver.go
├── .idea
│ ├── copyright
│ │ └── profiles_settings.xml
│ ├── modules.xml
│ ├── logic.iml
│ ├── compiler.xml
│ ├── libraries
│ │ └── GOPATH__logic_.xml
│ └── misc.xml
├── ret.go
├── errors.go
├── auth.go
├── encoder.go
├── signal.go
├── main.go
├── counter.go
├── store.go
├── logic.conf
├── kafka.go
├── rpc.go
├── config.go
└── logic-log.xml
├── libs
├── define
│ ├── room.go
│ ├── user.go
│ ├── kafka.go
│ └── operation.go
├── time
│ ├── debug.go
│ └── timer_test.go
├── proto
│ ├── ret.go
│ ├── logic.go
│ ├── job.go
│ ├── comet.go
│ ├── router.go
│ └── message.go
├── crypto
│ ├── padding
│ │ ├── pkcs7.go
│ │ ├── padding.go
│ │ ├── pkcs5_test.go
│ │ └── pkcs5.go
│ ├── aes
│ │ ├── aes_test.go
│ │ └── aes.go
│ ├── rsa
│ │ ├── rsa.go
│ │ └── rsa_test.go
│ └── cipher
│ │ └── ecb.go
├── io
│ └── ioutil
│ │ └── ioutil.go
├── hash
│ ├── ketama
│ │ ├── ketama_test.go
│ │ └── ketama.go
│ └── murmurhash3
│ │ └── mmhash3_test.go
├── bytes
│ ├── buffer_test.go
│ ├── writer.go
│ └── buffer.go
├── net
│ ├── network.go
│ └── xrpc
│ │ ├── clients.go
│ │ └── client.go
├── encoding
│ └── binary
│ │ └── endian.go
└── perf
│ └── perf.go
├── id
├── .timeid.go.swp
├── timeid_test.go
└── timeid.go
├── store
├── ret.go
├── token.go
├── signal.go
├── handle.go
├── main.go
├── http.go
├── storage.go
├── log.xml
├── rpc.go
└── config.go
├── examples
├── javascript
│ ├── main.go
│ ├── demo.html
│ └── client.js
├── cert.pem
└── private.pem
├── router
├── cleaner_test.go
├── main.go
├── signal.go
├── router.conf
├── session.go
├── config.go
├── test
│ └── router_test.go
├── cleaner.go
└── router-log.xml
├── LICENSE
├── benchmark
├── push_room
│ └── main.go
├── multi_push
│ └── main.go
├── push_rooms
│ └── main.go
└── push
│ └── main.go
├── README.md
└── README_en.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # no
2 |
--------------------------------------------------------------------------------
/comet/ver.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | const (
4 | Ver = "0.1"
5 | )
6 |
--------------------------------------------------------------------------------
/doc/arch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imroc/imgo/HEAD/doc/arch.png
--------------------------------------------------------------------------------
/doc/push.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imroc/imgo/HEAD/doc/push.gif
--------------------------------------------------------------------------------
/logic/job/ver.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | const (
4 | Ver = "0.1"
5 | )
6 |
--------------------------------------------------------------------------------
/logic/ver.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | const (
4 | Ver = "0.1"
5 | )
6 |
--------------------------------------------------------------------------------
/doc/connect.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imroc/imgo/HEAD/doc/connect.gif
--------------------------------------------------------------------------------
/doc/goim.graffle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imroc/imgo/HEAD/doc/goim.graffle
--------------------------------------------------------------------------------
/doc/protocol.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imroc/imgo/HEAD/doc/protocol.png
--------------------------------------------------------------------------------
/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/time/debug.go:
--------------------------------------------------------------------------------
1 | package time
2 |
3 | const (
4 | Debug = false
5 | )
6 |
--------------------------------------------------------------------------------
/doc/benchmark.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imroc/imgo/HEAD/doc/benchmark.jpg
--------------------------------------------------------------------------------
/doc/handshake.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imroc/imgo/HEAD/doc/handshake.png
--------------------------------------------------------------------------------
/id/.timeid.go.swp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imroc/imgo/HEAD/id/.timeid.go.swp
--------------------------------------------------------------------------------
/comet/test_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | func init() {
4 | Conf = new(Config)
5 | }
6 |
--------------------------------------------------------------------------------
/doc/benchmark-comet.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imroc/imgo/HEAD/doc/benchmark-comet.jpg
--------------------------------------------------------------------------------
/doc/benchmark-flow.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imroc/imgo/HEAD/doc/benchmark-flow.jpg
--------------------------------------------------------------------------------
/doc/benchmark-heap.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imroc/imgo/HEAD/doc/benchmark-heap.jpg
--------------------------------------------------------------------------------
/comet/bucket_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestBucket(t *testing.T) {
8 | }
9 |
--------------------------------------------------------------------------------
/libs/proto/ret.go:
--------------------------------------------------------------------------------
1 | package proto
2 |
3 | import "errors"
4 |
5 | var (
6 | ErrParam = errors.New("parameter error")
7 | )
8 |
--------------------------------------------------------------------------------
/logic/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/comet/stat.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // TODO
4 | // bucket
5 | // channel
6 | // ring
7 | // timer
8 | // push
9 | // bufio
10 | // operation
11 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/logic/.idea/logic.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/doc/benchmark_cn.md:
--------------------------------------------------------------------------------
1 | ## 压测图表
2 | 
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 | 
28 |
29 | ## 流量
30 | 
31 |
32 | ## heap信息(包含GC)
33 | 
34 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/examples/javascript/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | client demo
7 |
8 |
9 | client demo
10 |
11 |
20 | 获取离线消息
21 |
22 |
23 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/doc/benchmark_en.md:
--------------------------------------------------------------------------------
1 | ## Benchmark Chart
2 | 
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 | 
28 |
29 | ## Network traffic
30 | 
31 |
32 | ## Heap (include GC)
33 | 
34 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/.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/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 |
--------------------------------------------------------------------------------
/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 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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/logic/.idea/misc.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 |
43 |
44 | Go 1.7beta2
45 |
46 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------