Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/service/main/broadcast/api/grpc/v1:all-srcs",
"//app/service/main/broadcast/cmd:all-srcs",
"//app/service/main/broadcast/dao:all-srcs",
"//app/service/main/broadcast/libs:all-srcs",
"//app/service/main/broadcast/model:all-srcs",
"//app/service/main/broadcast/server/grpc:all-srcs",
"//app/service/main/broadcast/server/http:all-srcs",
"//app/service/main/broadcast/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,69 @@
### v1.5.0
> 1.websocket batch message
### v1.4.6
> 1.fix balancer overseas
### v1.4.5
> 1.添加node country info
### v1.4.4
> 1.修复discovery多机房配置
### v1.4.3
> 1.paladin config modules
> 2.添加ipv6支持
> 3.海外节点调度
### v1.3.2
> 1.conn async
### v1.3.1
> 1.使用paladin config sdk
### v1.3.0
> 1.添加http failover
### v1.2.3
> 1.修复nodes为空加入默认domain
### v1.2.2
> 1.fix server key
### v1.2.1
> 1.push key is empty
### v1.2.0
> 1.添加调度器,权重、地区调度
### v1.1.2
> 1.添加推送埋点统计
### v1.1.1
> 1.push按key sharing
### v1.1.0
> 1.修正grpc api目录
### v1.0.6
> 1.添加compress和content-type
> 2.更改心跳返回类型
### v1.0.5
> 1.添加返回ip列表数量
> 2.添加mobi_app和build
### v1.0.4
> 1. fix req bug
### v1.0.3
> 1. 在两个disocvery上注册service
### v1.0.2
> 1. fix bug
### v1.0.1
> 1. fix golint
### v1.0.0
> 1. init

View File

@@ -0,0 +1,14 @@
# Owner
chenzhihui
caoguoliang
guhao
# Author
chenzhihui
caoguoliang
guhao
# Reviewer
chenzhihui
caoguoliang
guhao

View File

@@ -0,0 +1,16 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- caoguoliang
- chenzhihui
- guhao
labels:
- main
- service
- service/main/broadcast
options:
no_parent_owners: true
reviewers:
- caoguoliang
- chenzhihui
- guhao

View File

@@ -0,0 +1,13 @@
# zerg
# 项目简介
1.
# 编译环境
# 依赖包
# 编译执行

View File

@@ -0,0 +1,67 @@
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
proto_library(
name = "v1_proto",
srcs = ["api.proto"],
tags = ["automanaged"],
deps = [
"//app/service/main/broadcast/model:model_proto",
"@gogo_special_proto//github.com/gogo/protobuf/gogoproto",
],
)
go_proto_library(
name = "v1_go_proto",
compilers = ["@io_bazel_rules_go//proto:gogofast_grpc"],
importpath = "go-common/app/service/main/broadcast/api/grpc/v1",
proto = ":v1_proto",
tags = ["manual"],
deps = [
"//app/service/main/broadcast/model:model_go_proto",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"api.go",
"client.go",
],
embed = [":v1_go_proto"],
importpath = "go-common/app/service/main/broadcast/api/grpc/v1",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/broadcast/model:go_default_library",
"//library/net/rpc/warden:go_default_library",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,17 @@
package v1
import (
"fmt"
)
func (m *ConnectReq) String() string {
return fmt.Sprintf("server:%s serverKey:%s token:%s", m.Server, m.ServerKey, m.Token)
}
func (m *OnlineReq) String() string {
return fmt.Sprintf("server:%s", m.Server)
}
func (m *OnlineReply) String() string {
return fmt.Sprintf("rooms:%d", len(m.RoomCount))
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,119 @@
// +bili:type=service
// Code generated by warden.
syntax = "proto3";
package push.service.broadcast;
option go_package = "v1";
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
import "app/service/main/broadcast/model/model.proto";
message CloseReply {
}
message CloseReq {
}
message PingReply {
}
message PingReq {
}
message ConnectReq {
option (gogoproto.goproto_stringer) = false;
string server = 1;
string serverKey = 2;
string cookie = 3;
bytes token = 4;
}
message ConnectReply {
int64 mid = 1;
string key = 2;
string roomID = 3;
string platform = 4;
repeated int32 accepts = 5;
}
message DisconnectReq {
int64 mid = 1;
string key = 2;
string server = 3;
}
message DisconnectReply {
bool has = 1;
}
message HeartbeatReq {
int64 mid = 1;
string key = 2;
string server = 3;
}
message HeartbeatReply {
}
message OnlineReq {
option (gogoproto.goproto_stringer) = false;
string server = 1;
map<string, int32> roomCount = 2;
int32 sharding = 3;
}
message OnlineReply {
option (gogoproto.goproto_stringer) = false;
map<string, int32> roomCount = 1;
}
message ReceiveReq {
int64 mid = 1;
push.service.broadcast.model.Proto proto = 2;
}
message ReceiveReply {
push.service.broadcast.model.Proto proto = 1;
}
message ServerListReq {
string platform = 1;
}
message ServerListReply {
string domain = 1 [(gogoproto.jsontag) = "domain"];
int32 tcpPort = 2 [(gogoproto.jsontag) = "tcp_port"];
int32 wsPort = 3 [(gogoproto.jsontag) = "ws_port"];
int32 wssPort = 4 [(gogoproto.jsontag) = "wss_port"];
int32 heartbeat = 5 [(gogoproto.jsontag) = "heartbeat"];
repeated string nodes = 6 [(gogoproto.jsontag) = "nodes"];
Backoff backoff = 7 [(gogoproto.jsontag) = "backoff"];
int32 heartbeatMax = 8 [(gogoproto.jsontag) = "heartbeat_max"];
}
message Backoff {
int32 MaxDelay = 1 [(gogoproto.jsontag) = "max_delay"];
int32 BaseDelay = 2 [(gogoproto.jsontag) = "base_delay"];
float Factor = 3 [(gogoproto.jsontag) = "factor"];
float Jitter = 4 [(gogoproto.jsontag) = "jitter"];
}
service Zerg {
// Ping Service
rpc Ping(PingReq) returns(PingReply);
// Close Service
rpc Close(CloseReq) returns(CloseReply);
// Connect
rpc Connect(ConnectReq) returns (ConnectReply);
// Disconnect
rpc Disconnect(DisconnectReq) returns (DisconnectReply);
// Heartbeat
rpc Heartbeat(HeartbeatReq) returns (HeartbeatReply);
// RenewOnline
rpc RenewOnline(OnlineReq) returns (OnlineReply);
// Receive
rpc Receive(ReceiveReq) returns (ReceiveReply);
//ServerList
rpc ServerList(ServerListReq) returns (ServerListReply);
}

View File

@@ -0,0 +1,24 @@
package v1
import (
"context"
"go-common/library/net/rpc/warden"
"google.golang.org/grpc"
)
// .
const (
DiscoveryID = "push.service.broadcast"
)
// NewClient .
func NewClient(conf *warden.ClientConfig, opts ...grpc.DialOption) (ZergClient, error) {
client := warden.NewClient(conf, opts...)
cc, err := client.Dial(context.Background(), "discovery://default/"+DiscoveryID)
if err != nil {
return nil, err
}
return NewZergClient(cc), nil
}

View File

@@ -0,0 +1,47 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "cmd",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
importpath = "go-common/app/service/main/broadcast/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/broadcast/server/grpc:go_default_library",
"//app/service/main/broadcast/server/http:go_default_library",
"//app/service/main/broadcast/service:go_default_library",
"//library/conf/env:go_default_library",
"//library/conf/paladin:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
"//library/naming:go_default_library",
"//library/naming/discovery:go_default_library",
"//library/net/ip:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,26 @@
httpToken = "uat"
redisExpire = "30m"
[regions]
"bj" = ["北京","天津","河北","山东","山西","内蒙古","辽宁","吉林","黑龙江","甘肃","宁夏","新疆"]
"sh" = ["上海","江苏","浙江","安徽","江西","湖北","重庆","陕西","青海","河南","台湾"]
"gz" = ["广东","福建","广西","海南","湖南","四川","贵州","云南","西藏","香港","澳门"]
[server]
domain = "broadcast.chat.bilibili.com"
hostDomain = ".biliapi.com"
heartbeat = "30s"
heartbeatMax = 3
tcpPort = 7821
wsPort = 7822
wssPort = 7823
regionWeight = 1.5
[backoff]
maxDelay = 120
baseDelay = 3
factor = 1.6
jitter = 0.2
[log]
dir = "/data/log/broadcast-service"

View File

@@ -0,0 +1,16 @@
[push]
key = "170e302355453683"
secret = "3d0e8db7bed0503949e545a469789279"
group = "BroadcastJob-MainCommunity-P"
topic = "BroadcastJob-T"
action ="pub"
name = "broadcast/service"
proto = "tcp"
addr = "172.18.33.50:6205"
idle = 2
active = 5
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "1h"

View File

@@ -0,0 +1,7 @@
[client]
dial = "1s"
timeout = "1s"
[server]
addr = "0.0.0.0:7839"
timeout = "1s"

View File

@@ -0,0 +1,3 @@
[server]
addr = "0.0.0.0:7831"
timeout = "1s"

View File

@@ -0,0 +1,5 @@
[stats]
taskID = "001482"
proto = "tcp"
addr = "172.18.33.125:15140"
chanSize = 10240

View File

@@ -0,0 +1,11 @@
[push]
name = "broadcast/push"
proto = "tcp"
addr = "172.22.33.126:6379"
idle = 32
active = 1024
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "1h"

View File

@@ -0,0 +1,96 @@
package main
import (
"context"
"flag"
"os"
"os/signal"
"syscall"
"time"
server "go-common/app/service/main/broadcast/server/grpc"
"go-common/app/service/main/broadcast/server/http"
"go-common/app/service/main/broadcast/service"
"go-common/library/conf/env"
"go-common/library/conf/paladin"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
"go-common/library/naming"
"go-common/library/naming/discovery"
"go-common/library/net/ip"
)
const (
ver = "v1.4.4"
)
func main() {
flag.Parse()
if err := paladin.Init(); err != nil {
panic(err)
}
var (
ac struct {
Discovery *discovery.Config
}
)
if err := paladin.Get("application.toml").UnmarshalTOML(&ac); err != nil {
if err != paladin.ErrNotExist {
panic(err)
}
}
log.Init(nil)
defer log.Close()
log.Info("broadcast-service %s start", ver)
// use internal discovery
dis := discovery.New(ac.Discovery)
// new a service
srv := service.New(dis)
ecode.Init(nil)
http.Init(srv)
// grpc server
rpcSrv, rpcPort := server.New(srv)
rpcSrv.Start()
// register discovery
var (
err error
cancel context.CancelFunc
)
if env.IP == "" {
ipAddr := ip.InternalIP()
// broadcast discovery
ins := &naming.Instance{
Zone: env.Zone,
Env: env.DeployEnv,
Hostname: env.Hostname,
AppID: "push.service.broadcast",
Addrs: []string{
"grpc://" + ipAddr + ":" + rpcPort,
},
}
cancel, err = dis.Register(context.Background(), ins)
if err != nil {
panic(err)
}
}
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info("broadcast-service get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
log.Info("broadcast-service %s exit", ver)
if cancel != nil {
cancel()
}
rpcSrv.Shutdown(context.Background())
time.Sleep(time.Second * 2)
srv.Close()
return
case syscall.SIGHUP:
default:
return
}
}
}

View File

@@ -0,0 +1,57 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"databus.go",
"redis.go",
],
importpath = "go-common/app/service/main/broadcast/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/broadcast/model:go_default_library",
"//library/cache/redis:go_default_library",
"//library/conf/paladin:go_default_library",
"//library/log:go_default_library",
"//library/queue/databus:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = [
"dao_test.go",
"databus_test.go",
"redis_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/service/main/broadcast/model:go_default_library",
"//library/conf/paladin:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,50 @@
package dao
import (
"context"
"go-common/library/cache/redis"
"go-common/library/conf/paladin"
"go-common/library/queue/databus"
)
// Dao dao.
type Dao struct {
redis *redis.Pool
pushBus *databus.Databus
}
func checkErr(err error) {
if err != nil {
panic(err)
}
}
// New new a dao and return.
func New() (dao *Dao) {
var (
rds struct {
Push *redis.Config
}
dbus struct {
Push *databus.Config
}
)
checkErr(paladin.Get("redis.toml").UnmarshalTOML(&rds))
checkErr(paladin.Get("databus.toml").UnmarshalTOML(&dbus))
dao = &Dao{
redis: redis.NewPool(rds.Push),
pushBus: databus.New(dbus.Push),
}
return
}
// Close close the resource.
func (d *Dao) Close() {
d.redis.Close()
}
// Ping dao ping.
func (d *Dao) Ping(c context.Context) error {
return d.pingRedis(c)
}

View File

@@ -0,0 +1,46 @@
package dao
import (
"context"
"flag"
"os"
"testing"
"go-common/library/conf/paladin"
"github.com/smartystreets/goconvey/convey"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.community.broadcast-service")
flag.Set("conf_token", "0cb70f9480b77c89626931c31ad2b65b")
flag.Set("tree_id", "39091")
flag.Set("conf_version", "server-1")
flag.Set("deploy_env", "uat")
flag.Set("conf_host", "config.bilibili.co")
flag.Set("conf_path", "/tmp")
flag.Set("region", "sh")
flag.Set("zone", "sh001")
} else {
flag.Set("conf", "../cmd/local")
}
flag.Parse()
if err := paladin.Init(); err != nil {
panic(err)
}
d = New()
m.Run()
d.Close()
os.Exit(0)
}
func TestClose(t *testing.T) {
convey.Convey("keyMidServer", t, func(ctx convey.C) {
ctx.So(d.Ping(context.TODO()), convey.ShouldBeNil)
})
}

View File

@@ -0,0 +1,73 @@
package dao
import (
"context"
"encoding/json"
"go-common/library/log"
)
const (
_pushMsg = "push"
_broadcastMsg = "broadcast"
_broadcastRoomMsg = "broadcast_room"
)
type pushMsg struct {
Type string `json:"type,omitempty"`
Operation int32 `json:"operation,omitempty"`
Server string `json:"server,omitempty"`
Keys []string `json:"keys,omitempty"`
Room string `json:"room,omitempty"`
Speed int32 `json:"speed,omitempty"`
Platform string `json:"platform,omitempty"`
ContentType int32 `json:"content_type,omitempty"`
Message json.RawMessage `json:"message,omitempty"`
}
// PushMsg push a message to databus.
func (d *Dao) PushMsg(c context.Context, op int32, server, msg string, keys []string, contentType int32) (err error) {
pushMsg := &pushMsg{
Type: _pushMsg,
Operation: op,
Server: server,
Keys: keys,
Message: []byte(msg),
ContentType: contentType,
}
if err = d.pushBus.Send(c, keys[0], pushMsg); err != nil {
log.Error("PushMsg.send(server:%v,pushMsg:%v).error(%v)", server, pushMsg, err)
}
return
}
// BroadcastRoomMsg push a message to databus.
func (d *Dao) BroadcastRoomMsg(c context.Context, op int32, room, msg string, contentType int32) (err error) {
pushMsg := &pushMsg{
Type: _broadcastRoomMsg,
Operation: op,
Room: room,
Message: []byte(msg),
ContentType: contentType,
}
if err = d.pushBus.Send(c, room, pushMsg); err != nil {
log.Error("BroadcastRoomMsg.send(room:%v,pushMsg:%v).error(%v)", room, pushMsg, err)
}
return
}
// BroadcastMsg push a message to databus.
func (d *Dao) BroadcastMsg(c context.Context, op, speed int32, msg, platform string, contentType int32) (err error) {
pushMsg := &pushMsg{
Operation: op,
Type: _broadcastMsg,
Speed: speed,
Message: []byte(msg),
Platform: platform,
ContentType: contentType,
}
if err = d.pushBus.Send(c, _broadcastMsg, pushMsg); err != nil {
log.Error("BroadcastMsg.send(_broadcastMsg:%v,pushMsg:%v).error(%v)", _broadcastMsg, pushMsg, err)
}
return
}

View File

@@ -0,0 +1,58 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoPushMsg(t *testing.T) {
var (
c = context.Background()
op = int32(0)
server = ""
msg = ""
keys = []string{"key"}
contentType = int32(0)
)
convey.Convey("PushMsg", t, func(ctx convey.C) {
err := d.PushMsg(c, op, server, msg, keys, contentType)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoBroadcastRoomMsg(t *testing.T) {
var (
c = context.Background()
op = int32(0)
room = ""
msg = ""
contentType = int32(0)
)
convey.Convey("BroadcastRoomMsg", t, func(ctx convey.C) {
err := d.BroadcastRoomMsg(c, op, room, msg, contentType)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoBroadcastMsg(t *testing.T) {
var (
c = context.Background()
op = int32(0)
speed = int32(0)
msg = ""
platform = ""
contentType = int32(0)
)
convey.Convey("BroadcastMsg", t, func(ctx convey.C) {
err := d.BroadcastMsg(c, op, speed, msg, platform, contentType)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,328 @@
package dao
import (
"context"
"encoding/json"
"fmt"
"strconv"
"time"
"go-common/app/service/main/broadcast/model"
"go-common/library/cache/redis"
"go-common/library/log"
)
const (
_prefixMidServer = "mid_%d" // mid -> key:server
_prefixKeyServer = "key_%s" // key -> server
_prefixServerOnline = "ol_%s" // server -> online
_keyServers = "servers"
_keyMigrateRooms = "migrate_rooms"
_keyMigrateServers = "migrate_servers"
)
var (
_redisExpire = int32(time.Minute * 30 / time.Second)
)
func keyMidServer(mid int64) string {
return fmt.Sprintf(_prefixMidServer, mid)
}
func keyKeyServer(key string) string {
return fmt.Sprintf(_prefixKeyServer, key)
}
func keyServerOnline(key string) string {
return fmt.Sprintf(_prefixServerOnline, key)
}
// pingRedis check redis connection.
func (d *Dao) pingRedis(c context.Context) (err error) {
conn := d.redis.Get(c)
_, err = conn.Do("SET", "PING", "PONG")
conn.Close()
return
}
// AddMapping add a mapping.
// Mapping:
// mid -> key_server
// key -> server
func (d *Dao) AddMapping(c context.Context, mid int64, key, server string) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
var n = 2
if mid > 0 {
if err = conn.Send("HSET", keyMidServer(mid), key, server); err != nil {
log.Error("conn.Send(HSET %d,%s,%s) error(%v)", mid, server, key, err)
return
}
if err = conn.Send("EXPIRE", keyMidServer(mid), _redisExpire); err != nil {
log.Error("conn.Send(EXPIRE %d,%s,%s) error(%v)", mid, key, server, err)
return
}
n += 2
}
if err = conn.Send("SET", keyKeyServer(key), server); err != nil {
log.Error("conn.Send(HSET %d,%s,%s) error(%v)", mid, server, key, err)
return
}
if err = conn.Send("EXPIRE", keyKeyServer(key), _redisExpire); err != nil {
log.Error("conn.Send(EXPIRE %d,%s,%s) error(%v)", mid, key, server, err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < n; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// ExpireMapping expire a mapping.
func (d *Dao) ExpireMapping(c context.Context, mid int64, key string) (has bool, err error) {
conn := d.redis.Get(c)
defer conn.Close()
var n = 1
if mid > 0 {
if err = conn.Send("EXPIRE", keyMidServer(mid), _redisExpire); err != nil {
log.Error("conn.Send(EXPIRE %d,%s) error(%v)", mid, key, err)
return
}
n++
}
if err = conn.Send("EXPIRE", keyKeyServer(key), _redisExpire); err != nil {
log.Error("conn.Send(EXPIRE %d,%s) error(%v)", mid, key, err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < n; i++ {
if has, err = redis.Bool(conn.Receive()); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// DelMapping del a mapping.
func (d *Dao) DelMapping(c context.Context, mid int64, key, server string) (has bool, err error) {
conn := d.redis.Get(c)
defer conn.Close()
n := 1
if mid > 0 {
if err = conn.Send("HDEL", keyMidServer(mid), key); err != nil {
log.Error("conn.Send(HDEL %d,%s,%s) error(%v)", mid, key, server, err)
return
}
n++
}
if err = conn.Send("DEL", keyKeyServer(key)); err != nil {
log.Error("conn.Send(HDEL %d,%s,%s) error(%v)", mid, key, server, err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < n; i++ {
if has, err = redis.Bool(conn.Receive()); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// ServersByKeys get a server by key.
func (d *Dao) ServersByKeys(c context.Context, keys []string) (res []string, err error) {
conn := d.redis.Get(c)
defer conn.Close()
var args []interface{}
for _, key := range keys {
args = append(args, keyKeyServer(key))
}
if res, err = redis.Strings(conn.Do("MGET", args...)); err != nil {
log.Error("conn.Do(MGET %v) error(%v)", args, err)
}
return
}
// KeysByMids get a key server by mid.
func (d *Dao) KeysByMids(c context.Context, mids []int64) (ress map[string]string, olMids []int64, err error) {
conn := d.redis.Get(c)
defer conn.Close()
ress = make(map[string]string)
for _, mid := range mids {
if err = conn.Send("HGETALL", keyMidServer(mid)); err != nil {
log.Error("conn.Do(HGETALL %d) error(%v)", mid, err)
return
}
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for idx := 0; idx < len(mids); idx++ {
var (
res map[string]string
)
if res, err = redis.StringMap(conn.Receive()); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
if len(res) > 0 {
olMids = append(olMids, mids[idx])
}
for k, v := range res {
ress[k] = v
}
}
return
}
// AddServerOnline add server online.
func (d *Dao) AddServerOnline(c context.Context, server string, sharding int32, online *model.Online) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
b, _ := json.Marshal(online)
key := keyServerOnline(server)
if err = conn.Send("HSET", key, strconv.FormatInt(int64(sharding), 10), b); err != nil {
log.Error("conn.Send(SET %s,%d) error(%v)", key, sharding, err)
return
}
if err = conn.Send("EXPIRE", key, _redisExpire); err != nil {
log.Error("conn.Send(EXPIRE %s) error(%v)", key, err)
return
}
if err = conn.Flush(); err != nil {
log.Error("conn.Flush() error(%v)", err)
return
}
for i := 0; i < 2; i++ {
if _, err = conn.Receive(); err != nil {
log.Error("conn.Receive() error(%v)", err)
return
}
}
return
}
// ServerOnline get a server online.
func (d *Dao) ServerOnline(c context.Context, server string, shard int) (online *model.Online, err error) {
conn := d.redis.Get(c)
defer conn.Close()
key := keyServerOnline(server)
hashKey := fmt.Sprint(shard)
b, err := redis.Bytes(conn.Do("HGET", key, hashKey))
if err != nil {
if err != redis.ErrNil {
log.Error("conn.Do(HGET %s %s) error(%v)", key, hashKey, err)
} else {
err = nil
}
return
}
online = new(model.Online)
if err = json.Unmarshal(b, online); err != nil {
log.Error("serverOnline json.Unmarshal(%s) error(%v)", b, err)
}
return
}
// DelServerOnline del a server online.
func (d *Dao) DelServerOnline(c context.Context, server string, shard int) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
key := keyServerOnline(server)
hashKey := fmt.Sprint(shard)
if _, err = conn.Do("HDEL", key, hashKey); err != nil {
log.Error("conn.Do(DEL %s) error(%v)", key, err)
}
return
}
// SetServers set servers info.
func (d *Dao) SetServers(c context.Context, srvs []*model.ServerInfo) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
b, _ := json.Marshal(srvs)
if _, err = conn.Do("SET", _keyServers, b); err != nil {
log.Error("conn.Do(SET %s,%s) error(%v)", _keyServers, b, err)
}
return
}
// Servers return servers.
func (d *Dao) Servers(c context.Context) (srvs []*model.ServerInfo, err error) {
conn := d.redis.Get(c)
defer conn.Close()
b, err := redis.Bytes(conn.Do("GET", _keyServers))
if err != nil {
if err != redis.ErrNil {
log.Error("conn.Do(GET %s) error(%v)", _keyServers, err)
} else {
err = nil
}
return
}
if err = json.Unmarshal(b, &srvs); err != nil {
log.Error("MigrateServers json.Unmarshal(%s) error(%v)", b, err)
}
return
}
// MigrateServers migrate servers.
func (d *Dao) MigrateServers(c context.Context) (conns, ips int64, err error) {
var servers struct {
Conns int64 `json:"conn_count"`
IPs int64 `json:"ip_count"`
}
conn := d.redis.Get(c)
defer conn.Close()
b, err := redis.Bytes(conn.Do("GET", _keyMigrateServers))
if err != nil {
if err != redis.ErrNil {
log.Error("conn.Do(GET %s) error(%v)", _keyMigrateServers, err)
} else {
err = nil
}
return
}
if err = json.Unmarshal(b, &servers); err != nil {
log.Error("MigrateServers json.Unmarshal(%s) error(%v)", b, err)
return
}
conns = servers.Conns
ips = servers.IPs
return
}
// MigrateRooms migrate rooms.
func (d *Dao) MigrateRooms(c context.Context, shard int) (rooms map[string]int32, err error) {
conn := d.redis.Get(c)
defer conn.Close()
b, err := redis.Bytes(conn.Do("HGET", _keyMigrateRooms, fmt.Sprint(shard)))
if err != nil {
if err != redis.ErrNil {
log.Error("conn.Do(HGET %s,%d) error(%v)", _keyMigrateRooms, shard, err)
} else {
err = nil
}
return
}
rooms = make(map[string]int32)
if err = json.Unmarshal(b, &rooms); err != nil {
log.Error("migrateRooms json.Unmarshal() error(%v)", err)
}
return
}

View File

@@ -0,0 +1,151 @@
package dao
import (
"context"
"testing"
"go-common/app/service/main/broadcast/model"
"github.com/smartystreets/goconvey/convey"
)
func TestDaokeyMidServer(t *testing.T) {
var (
mid = int64(1)
)
convey.Convey("keyMidServer", t, func(ctx convey.C) {
p1 := keyMidServer(mid)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaokeyKeyServer(t *testing.T) {
var (
key = "key"
)
convey.Convey("keyKeyServer", t, func(ctx convey.C) {
p1 := keyKeyServer(key)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaokeyServerOnline(t *testing.T) {
var (
key = "key"
)
convey.Convey("keyServerOnline", t, func(ctx convey.C) {
p1 := keyServerOnline(key)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldNotBeNil)
})
})
}
func TestDaopingRedis(t *testing.T) {
var (
c = context.Background()
)
convey.Convey("pingRedis", t, func(ctx convey.C) {
err := d.pingRedis(c)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestDaoAddMapping(t *testing.T) {
var (
c = context.Background()
mid = int64(1)
key = "key"
server = "server"
)
convey.Convey("AddMapping", t, func(ctx convey.C) {
err := d.AddMapping(c, mid, key, server)
ctx.So(err, convey.ShouldBeNil)
has, err := d.ExpireMapping(c, mid, key)
ctx.So(err, convey.ShouldBeNil)
ctx.So(has, convey.ShouldBeTrue)
has, err = d.DelMapping(c, mid, key, server)
ctx.So(err, convey.ShouldBeNil)
ctx.So(has, convey.ShouldBeTrue)
// false
has, err = d.ExpireMapping(c, mid, key)
ctx.So(err, convey.ShouldBeNil)
ctx.So(has, convey.ShouldBeFalse)
has, err = d.DelMapping(c, mid, key, server)
ctx.So(err, convey.ShouldBeNil)
ctx.So(has, convey.ShouldBeFalse)
})
}
func TestDaoServersByKeys(t *testing.T) {
var (
c = context.Background()
keys = []string{"key"}
)
convey.Convey("ServersByKeys", t, func(ctx convey.C) {
res, err := d.ServersByKeys(c, keys)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
}
func TestDaoKeysByMids(t *testing.T) {
var (
c = context.Background()
mids = []int64{1, 2, 3}
)
convey.Convey("KeysByMids", t, func(ctx convey.C) {
ress, _, err := d.KeysByMids(c, mids)
ctx.Convey("Then err should be nil.ress should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ress, convey.ShouldNotBeNil)
})
})
}
func TestDaoAddServerOnline(t *testing.T) {
var (
c = context.Background()
server = "key"
shard = 1
online = &model.Online{RoomCount: map[string]int32{
"test1": 100,
"test2": 200,
"test3": 300,
}, Updated: 1}
)
convey.Convey("AddServerOnline", t, func(ctx convey.C) {
err := d.AddServerOnline(c, server, int32(shard), online)
ctx.So(err, convey.ShouldBeNil)
res, err := d.ServerOnline(c, server, shard)
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldResemble, online)
err = d.DelServerOnline(c, server, shard)
ctx.So(err, convey.ShouldBeNil)
})
}
func TestDaoSetServers(t *testing.T) {
var (
c = context.Background()
servers = []*model.ServerInfo{}
)
convey.Convey("SetServers", t, func(ctx convey.C) {
ctx.So(d.SetServers(c, servers), convey.ShouldBeNil)
res, err := d.Servers(c)
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldResemble, servers)
_, _, err = d.MigrateServers(c)
ctx.So(err, convey.ShouldBeNil)
_, err = d.MigrateRooms(c, 0)
ctx.So(err, convey.ShouldBeNil)
})
}

View File

@@ -0,0 +1,22 @@
package(default_visibility = ["//visibility:public"])
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/service/main/broadcast/libs/bufio:all-srcs",
"//app/service/main/broadcast/libs/bytes:all-srcs",
"//app/service/main/broadcast/libs/encoding/binary:all-srcs",
"//app/service/main/broadcast/libs/time:all-srcs",
"//app/service/main/broadcast/libs/websocket:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,35 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["bufio.go"],
importpath = "go-common/app/service/main/broadcast/libs/bufio",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_xtest",
srcs = ["bufio_test.go"],
tags = ["automanaged"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,521 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package bufio implements buffered I/O. It wraps an io.Reader or io.Writer
// object, creating another object (Reader or Writer) that also implements
// the interface but provides buffering and some help for textual I/O.
package bufio
import (
"bytes"
"errors"
"io"
)
const (
defaultBufSize = 4096
)
var (
// ErrInvalidUnreadByte invalid use of UnreadByete
ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte")
// ErrInvalidUnreadRune invalid use of UnreadRune
ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune")
// ErrBufferFull buffer full
ErrBufferFull = errors.New("bufio: buffer full")
// ErrNegativeCount negative count
ErrNegativeCount = errors.New("bufio: negative count")
)
// Buffered input.
// Reader implements buffering for an io.Reader object.
type Reader struct {
buf []byte
rd io.Reader // reader provided by the client
r, w int // buf read and write positions
err error
}
const minReadBufferSize = 16
const maxConsecutiveEmptyReads = 100
// NewReaderSize returns a new Reader whose buffer has at least the specified
// size. If the argument io.Reader is already a Reader with large enough
// size, it returns the underlying Reader.
func NewReaderSize(rd io.Reader, size int) *Reader {
// Is it already a Reader?
b, ok := rd.(*Reader)
if ok && len(b.buf) >= size {
return b
}
if size < minReadBufferSize {
size = minReadBufferSize
}
r := new(Reader)
r.reset(make([]byte, size), rd)
return r
}
// NewReader returns a new Reader whose buffer has the default size.
func NewReader(rd io.Reader) *Reader {
return NewReaderSize(rd, defaultBufSize)
}
// Reset discards any buffered data, resets all state, and switches
// the buffered reader to read from r.
func (b *Reader) Reset(r io.Reader) {
b.reset(b.buf, r)
}
// ResetBuffer discards any buffered data, resets all state, and switches
// the buffered reader to read from r.
func (b *Reader) ResetBuffer(r io.Reader, buf []byte) {
b.reset(buf, r)
}
func (b *Reader) reset(buf []byte, r io.Reader) {
*b = Reader{
buf: buf,
rd: r,
}
}
var errNegativeRead = errors.New("bufio: reader returned negative count from Read")
// fill reads a new chunk into the buffer.
func (b *Reader) fill() {
// Slide existing data to beginning.
if b.r > 0 {
copy(b.buf, b.buf[b.r:b.w])
b.w -= b.r
b.r = 0
}
if b.w >= len(b.buf) {
panic("bufio: tried to fill full buffer")
}
// Read new data: try a limited number of times.
for i := maxConsecutiveEmptyReads; i > 0; i-- {
n, err := b.rd.Read(b.buf[b.w:])
if n < 0 {
panic(errNegativeRead)
}
b.w += n
if err != nil {
b.err = err
return
}
if n > 0 {
return
}
}
b.err = io.ErrNoProgress
}
func (b *Reader) readErr() error {
err := b.err
b.err = nil
return err
}
// Peek returns the next n bytes without advancing the reader. The bytes stop
// being valid at the next read call. If Peek returns fewer than n bytes, it
// also returns an error explaining why the read is short. The error is
// ErrBufferFull if n is larger than b's buffer size.
func (b *Reader) Peek(n int) ([]byte, error) {
if n < 0 {
return nil, ErrNegativeCount
}
if n > len(b.buf) {
return nil, ErrBufferFull
}
// 0 <= n <= len(b.buf)
for b.w-b.r < n && b.err == nil {
b.fill() // b.w-b.r < len(b.buf) => buffer is not full
}
var err error
if avail := b.w - b.r; avail < n {
// not enough data in buffer
n = avail
err = b.readErr()
if err == nil {
err = ErrBufferFull
}
}
return b.buf[b.r : b.r+n], err
}
// Pop returns the next n bytes with advancing the reader. The bytes stop
// being valid at the next read call. If Pop returns fewer than n bytes, it
// also returns an error explaining why the read is short. The error is
// ErrBufferFull if n is larger than b's buffer size.
func (b *Reader) Pop(n int) ([]byte, error) {
d, err := b.Peek(n)
if err == nil {
b.r += n
return d, err
}
return nil, err
}
// Discard skips the next n bytes, returning the number of bytes discarded.
//
// If Discard skips fewer than n bytes, it also returns an error.
// If 0 <= n <= b.Buffered(), Discard is guaranteed to succeed without
// reading from the underlying io.Reader.
func (b *Reader) Discard(n int) (discarded int, err error) {
if n < 0 {
return 0, ErrNegativeCount
}
if n == 0 {
return
}
remain := n
for {
skip := b.Buffered()
if skip == 0 {
b.fill()
skip = b.Buffered()
}
if skip > remain {
skip = remain
}
b.r += skip
remain -= skip
if remain == 0 {
return n, nil
}
if b.err != nil {
return n - remain, b.readErr()
}
}
}
// Read reads data into p.
// It returns the number of bytes read into p.
// It calls Read at most once on the underlying Reader,
// hence n may be less than len(p).
// At EOF, the count will be zero and err will be io.EOF.
func (b *Reader) Read(p []byte) (n int, err error) {
n = len(p)
if n == 0 {
return 0, b.readErr()
}
if b.r == b.w {
if b.err != nil {
return 0, b.readErr()
}
if len(p) >= len(b.buf) {
// Large read, empty buffer.
// Read directly into p to avoid copy.
n, b.err = b.rd.Read(p)
if n < 0 {
panic(errNegativeRead)
}
return n, b.readErr()
}
b.fill() // buffer is empty
if b.r == b.w {
return 0, b.readErr()
}
}
// copy as much as we can
n = copy(p, b.buf[b.r:b.w])
b.r += n
return n, nil
}
// ReadByte reads and returns a single byte.
// If no byte is available, returns an error.
func (b *Reader) ReadByte() (c byte, err error) {
//b.lastRuneSize = -1
for b.r == b.w {
if b.err != nil {
return 0, b.readErr()
}
b.fill() // buffer is empty
}
c = b.buf[b.r]
b.r++
//b.lastByte = int(c)
return c, nil
}
// ReadSlice reads until the first occurrence of delim in the input,
// returning a slice pointing at the bytes in the buffer.
// The bytes stop being valid at the next read.
// If ReadSlice encounters an error before finding a delimiter,
// it returns all the data in the buffer and the error itself (often io.EOF).
// ReadSlice fails with error ErrBufferFull if the buffer fills without a delim.
// Because the data returned from ReadSlice will be overwritten
// by the next I/O operation, most clients should use
// ReadBytes or ReadString instead.
// ReadSlice returns err != nil if and only if line does not end in delim.
func (b *Reader) ReadSlice(delim byte) (line []byte, err error) {
for {
// Search buffer.
if i := bytes.IndexByte(b.buf[b.r:b.w], delim); i >= 0 {
line = b.buf[b.r : b.r+i+1]
b.r += i + 1
break
}
// Pending error?
if b.err != nil {
line = b.buf[b.r:b.w]
b.r = b.w
err = b.readErr()
break
}
// Buffer full?
if b.Buffered() >= len(b.buf) {
b.r = b.w
line = b.buf
err = ErrBufferFull
break
}
b.fill() // buffer is not full
}
return
}
// ReadLine is a low-level line-reading primitive. Most callers should use
// ReadBytes('\n') or ReadString('\n') instead or use a Scanner.
//
// ReadLine tries to return a single line, not including the end-of-line bytes.
// If the line was too long for the buffer then isPrefix is set and the
// beginning of the line is returned. The rest of the line will be returned
// from future calls. isPrefix will be false when returning the last fragment
// of the line. The returned buffer is only valid until the next call to
// ReadLine. ReadLine either returns a non-nil line or it returns an error,
// never both.
//
// The text returned from ReadLine does not include the line end ("\r\n" or "\n").
// No indication or error is given if the input ends without a final line end.
// Calling UnreadByte after ReadLine will always unread the last byte read
// (possibly a character belonging to the line end) even if that byte is not
// part of the line returned by ReadLine.
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error) {
line, err = b.ReadSlice('\n')
if err == ErrBufferFull {
// Handle the case where "\r\n" straddles the buffer.
if len(line) > 0 && line[len(line)-1] == '\r' {
// Put the '\r' back on buf and drop it from line.
// Let the next call to ReadLine check for "\r\n".
if b.r == 0 {
// should be unreachable
panic("bufio: tried to rewind past start of buffer")
}
b.r--
line = line[:len(line)-1]
}
return line, true, nil
}
if len(line) == 0 {
if err != nil {
line = nil
}
return
}
err = nil
if line[len(line)-1] == '\n' {
drop := 1
if len(line) > 1 && line[len(line)-2] == '\r' {
drop = 2
}
line = line[:len(line)-drop]
}
return
}
// Buffered returns the number of bytes that can be read from the current buffer.
func (b *Reader) Buffered() int { return b.w - b.r }
// buffered output
// Writer implements buffering for an io.Writer object.
// If an error occurs writing to a Writer, no more data will be
// accepted and all subsequent writes will return the error.
// After all data has been written, the client should call the
// Flush method to guarantee all data has been forwarded to
// the underlying io.Writer.
type Writer struct {
err error
buf []byte
n int
wr io.Writer
}
// NewWriterSize returns a new Writer whose buffer has at least the specified
// size. If the argument io.Writer is already a Writer with large enough
// size, it returns the underlying Writer.
func NewWriterSize(w io.Writer, size int) *Writer {
// Is it already a Writer?
b, ok := w.(*Writer)
if ok && len(b.buf) >= size {
return b
}
if size <= 0 {
size = defaultBufSize
}
return &Writer{
buf: make([]byte, size),
wr: w,
}
}
// NewWriter returns a new Writer whose buffer has the default size.
func NewWriter(w io.Writer) *Writer {
return NewWriterSize(w, defaultBufSize)
}
// Reset discards any unflushed buffered data, clears any error, and
// resets b to write its output to w.
func (b *Writer) Reset(w io.Writer) {
b.err = nil
b.n = 0
b.wr = w
}
// ResetBuffer discards any unflushed buffered data, clears any error, and
// resets b to write its output to w.
func (b *Writer) ResetBuffer(w io.Writer, buf []byte) {
b.buf = buf
b.err = nil
b.n = 0
b.wr = w
}
// Flush writes any buffered data to the underlying io.Writer.
func (b *Writer) Flush() error {
err := b.flush()
return err
}
func (b *Writer) flush() error {
if b.err != nil {
return b.err
}
if b.n == 0 {
return nil
}
n, err := b.wr.Write(b.buf[0:b.n])
if n < b.n && err == nil {
err = io.ErrShortWrite
}
if err != nil {
if n > 0 && n < b.n {
copy(b.buf[0:b.n-n], b.buf[n:b.n])
}
b.n -= n
b.err = err
return err
}
b.n = 0
return nil
}
// Available returns how many bytes are unused in the buffer.
func (b *Writer) Available() int { return len(b.buf) - b.n }
// Buffered returns the number of bytes that have been written into the current buffer.
func (b *Writer) Buffered() int { return b.n }
// Write writes the contents of p into the buffer.
// It returns the number of bytes written.
// If nn < len(p), it also returns an error explaining
// why the write is short.
func (b *Writer) Write(p []byte) (nn int, err error) {
for len(p) > b.Available() && b.err == nil {
var n int
if b.Buffered() == 0 {
// Large write, empty buffer.
// Write directly from p to avoid copy.
n, b.err = b.wr.Write(p)
} else {
n = copy(b.buf[b.n:], p)
b.n += n
b.flush()
}
nn += n
p = p[n:]
}
if b.err != nil {
return nn, b.err
}
n := copy(b.buf[b.n:], p)
b.n += n
nn += n
return nn, nil
}
// WriteRaw writes the contents of p into the raw io.Writer without buffer.
// It returns the number of bytes written.
// If nn < len(p), it also returns an error explaining
// why the write is short.
func (b *Writer) WriteRaw(p []byte) (nn int, err error) {
if b.err != nil {
return 0, b.err
}
if b.Buffered() == 0 {
// if no buffer data, write raw writer
nn, err = b.wr.Write(p)
b.err = err
} else {
nn, err = b.Write(p)
}
return
}
// Peek returns the next n bytes with advancing the writer. The bytes stop
// being used at the next write call. If Peek returns fewer than n bytes, it
// also returns an error explaining why the read is short. The error is
// ErrBufferFull if n is larger than b's buffer size.
func (b *Writer) Peek(n int) ([]byte, error) {
if n < 0 {
return nil, ErrNegativeCount
}
if n > len(b.buf) {
return nil, ErrBufferFull
}
for b.Available() < n && b.err == nil {
b.flush()
}
if b.err != nil {
return nil, b.err
}
d := b.buf[b.n : b.n+n]
b.n += n
return d, nil
}
// WriteString writes a string.
// It returns the number of bytes written.
// If the count is less than len(s), it also returns an error explaining
// why the write is short.
func (b *Writer) WriteString(s string) (int, error) {
nn := 0
for len(s) > b.Available() && b.err == nil {
n := copy(b.buf[b.n:], s)
b.n += n
nn += n
s = s[n:]
b.flush()
}
if b.err != nil {
return nn, b.err
}
n := copy(b.buf[b.n:], s)
b.n += n
nn += n
return nn, nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["buffer_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = [
"buffer.go",
"writer.go",
],
importpath = "go-common/app/service/main/broadcast/libs/bytes",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,86 @@
package bytes
import (
"sync"
)
// Buffer buffer.
type Buffer struct {
buf []byte
next *Buffer // next free buffer
}
// Bytes bytes.
func (b *Buffer) Bytes() []byte {
return b.buf
}
// Pool is a buffer pool.
type Pool struct {
lock sync.Mutex
free *Buffer
max int
num int
size int
}
// NewPool new a memory buffer pool struct.
func NewPool(num, size int) (p *Pool) {
p = new(Pool)
p.init(num, size)
return
}
// Init init the memory buffer.
func (p *Pool) Init(num, size int) {
p.init(num, size)
}
// init init the memory buffer.
func (p *Pool) init(num, size int) {
p.num = num
p.size = size
p.max = num * size
p.grow()
}
// grow grow the memory buffer size, and update free pointer.
func (p *Pool) grow() {
var (
i int
b *Buffer
bs []Buffer
buf []byte
)
buf = make([]byte, p.max)
bs = make([]Buffer, p.num)
p.free = &bs[0]
b = p.free
for i = 1; i < p.num; i++ {
b.buf = buf[(i-1)*p.size : i*p.size]
b.next = &bs[i]
b = b.next
}
b.buf = buf[(i-1)*p.size : i*p.size]
b.next = nil
}
// Get get a free memory buffer.
func (p *Pool) Get() (b *Buffer) {
p.lock.Lock()
if b = p.free; b == nil {
p.grow()
b = p.free
}
p.free = b.next
p.lock.Unlock()
return
}
// Put put back a memory buffer to free.
func (p *Pool) Put(b *Buffer) {
p.lock.Lock()
b.next = p.free
p.free = b
p.lock.Unlock()
}

View File

@@ -0,0 +1,21 @@
package bytes
import (
"testing"
)
func TestBuffer(t *testing.T) {
p := NewPool(2, 10)
b := p.Get()
if b.Bytes() == nil || len(b.Bytes()) == 0 {
t.FailNow()
}
b = p.Get()
if b.Bytes() == nil || len(b.Bytes()) == 0 {
t.FailNow()
}
b = p.Get()
if b.Bytes() == nil || len(b.Bytes()) == 0 {
t.FailNow()
}
}

View File

@@ -0,0 +1,57 @@
package bytes
// Writer writer.
type Writer struct {
n int
buf []byte
}
// NewWriterSize new a writer with size.
func NewWriterSize(n int) *Writer {
return &Writer{buf: make([]byte, n)}
}
// Len buff len.
func (w *Writer) Len() int {
return w.n
}
// Size buff cap.
func (w *Writer) Size() int {
return len(w.buf)
}
// Reset reset the buff.
func (w *Writer) Reset() {
w.n = 0
}
// Buffer return buff.
func (w *Writer) Buffer() []byte {
return w.buf[:w.n]
}
// Peek peek a buf.
func (w *Writer) Peek(n int) []byte {
var buf []byte
w.grow(n)
buf = w.buf[w.n : w.n+n]
w.n += n
return buf
}
// Write write a buff.
func (w *Writer) Write(p []byte) {
w.grow(len(p))
w.n += copy(w.buf[w.n:], p)
}
func (w *Writer) grow(n int) {
var buf []byte
if w.n+n < len(w.buf) {
return
}
buf = make([]byte, 2*len(w.buf)+n)
copy(buf, w.buf[:w.n])
w.buf = buf
}

View File

@@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["endian.go"],
importpath = "go-common/app/service/main/broadcast/libs/encoding/binary",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,30 @@
package binary
// BigEndian big endian.
var BigEndian bigEndian
type bigEndian struct{}
func (bigEndian) Int8(b []byte) int8 { return int8(b[0]) }
func (bigEndian) PutInt8(b []byte, v int8) {
b[0] = byte(v)
}
func (bigEndian) Int16(b []byte) int16 { return int16(b[1]) | int16(b[0])<<8 }
func (bigEndian) PutInt16(b []byte, v int16) {
b[0] = byte(v >> 8)
b[1] = byte(v)
}
func (bigEndian) Int32(b []byte) int32 {
return int32(b[3]) | int32(b[2])<<8 | int32(b[1])<<16 | int32(b[0])<<24
}
func (bigEndian) PutInt32(b []byte, v int32) {
b[0] = byte(v >> 24)
b[1] = byte(v >> 16)
b[2] = byte(v >> 8)
b[3] = byte(v)
}

View File

@@ -0,0 +1,42 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["timer_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = ["//library/log:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = [
"debug.go",
"timer.go",
],
importpath = "go-common/app/service/main/broadcast/libs/time",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//library/log:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,6 @@
package time
const (
// Debug debug switch
Debug = false
)

View File

@@ -0,0 +1,268 @@
package time
import (
"sync"
itime "time"
"go-common/library/log"
)
const (
timerFormat = "2006-01-02 15:04:05"
infiniteDuration = itime.Duration(1<<63 - 1)
)
var (
timerLazyDelay = 300 * itime.Millisecond
)
// TimerData timer data.
type TimerData struct {
Key string
expire itime.Time
fn func()
index int
next *TimerData
}
// Delay delay duration.
func (td *TimerData) Delay() itime.Duration {
return td.expire.Sub(itime.Now())
}
// ExpireString expire string.
func (td *TimerData) ExpireString() string {
return td.expire.Format(timerFormat)
}
// Timer timer.
type Timer struct {
lock sync.Mutex
free *TimerData
timers []*TimerData
signal *itime.Timer
num int
}
// NewTimer new a timer.
// A heap must be initialized before any of the heap operations
// can be used. Init is idempotent with respect to the heap invariants
// and may be called whenever the heap invariants may have been invalidated.
// Its complexity is O(n) where n = h.Len().
//
func NewTimer(num int) (t *Timer) {
t = new(Timer)
t.init(num)
return t
}
// Init init the timer.
func (t *Timer) Init(num int) {
t.init(num)
}
func (t *Timer) init(num int) {
t.signal = itime.NewTimer(infiniteDuration)
t.timers = make([]*TimerData, 0, num)
t.num = num
t.grow()
go t.start()
}
func (t *Timer) grow() {
var (
i int
td *TimerData
tds = make([]TimerData, t.num)
)
t.free = &(tds[0])
td = t.free
for i = 1; i < t.num; i++ {
td.next = &(tds[i])
td = td.next
}
td.next = nil
}
// get get a free timer data.
func (t *Timer) get() (td *TimerData) {
if td = t.free; td == nil {
t.grow()
td = t.free
}
t.free = td.next
return
}
// put put back a timer data.
func (t *Timer) put(td *TimerData) {
td.fn = nil
td.next = t.free
t.free = td
}
// Add add the element x onto the heap. The complexity is
// O(log(n)) where n = h.Len().
func (t *Timer) Add(expire itime.Duration, fn func()) (td *TimerData) {
t.lock.Lock()
td = t.get()
td.expire = itime.Now().Add(expire)
td.fn = fn
t.add(td)
t.lock.Unlock()
return
}
// Del removes the element at index i from the heap.
// The complexity is O(log(n)) where n = h.Len().
func (t *Timer) Del(td *TimerData) {
t.lock.Lock()
t.del(td)
t.put(td)
t.lock.Unlock()
}
// Push pushes the element x onto the heap. The complexity is
// O(log(n)) where n = h.Len().
func (t *Timer) add(td *TimerData) {
var d itime.Duration
td.index = len(t.timers)
// add to the minheap last node
t.timers = append(t.timers, td)
t.up(td.index)
if td.index == 0 {
// if first node, signal start goroutine
d = td.Delay()
t.signal.Reset(d)
if Debug {
log.Info("timer: add reset delay %d ms", int64(d)/int64(itime.Millisecond))
}
}
if Debug {
log.Info("timer: push item key: %s, expire: %s, index: %d", td.Key, td.ExpireString(), td.index)
}
}
func (t *Timer) del(td *TimerData) {
var (
i = td.index
last = len(t.timers) - 1
)
if i < 0 || i > last || t.timers[i] != td {
// already remove, usually by expire
if Debug {
log.Info("timer del i: %d, last: %d, %p", i, last, td)
}
return
}
if i != last {
t.swap(i, last)
t.down(i, last)
t.up(i)
}
// remove item is the last node
t.timers[last].index = -1 // for safety
t.timers = t.timers[:last]
if Debug {
log.Info("timer: remove item key: %s, expire: %s, index: %d", td.Key, td.ExpireString(), td.index)
}
}
// Set update timer data.
func (t *Timer) Set(td *TimerData, expire itime.Duration) {
t.lock.Lock()
t.del(td)
td.expire = itime.Now().Add(expire)
t.add(td)
t.lock.Unlock()
}
// start start the timer.
func (t *Timer) start() {
for {
t.expire()
<-t.signal.C
}
}
// expire removes the minimum element (according to Less) from the heap.
// The complexity is O(log(n)) where n = max.
// It is equivalent to Del(0).
func (t *Timer) expire() {
var (
fn func()
td *TimerData
d itime.Duration
)
t.lock.Lock()
for {
if len(t.timers) == 0 {
d = infiniteDuration
if Debug {
log.Info("timer: no other instance")
}
break
}
td = t.timers[0]
if d = td.Delay(); d > 0 {
break
}
fn = td.fn
// let caller put back
t.del(td)
t.lock.Unlock()
if fn == nil {
log.Warn("expire timer no fn")
} else {
if Debug {
log.Info("timer key: %s, expire: %s, index: %d expired, call fn", td.Key, td.ExpireString(), td.index)
}
fn()
}
t.lock.Lock()
}
t.signal.Reset(d)
if Debug {
log.Info("timer: expier reset delay %d ms", int64(d)/int64(itime.Millisecond))
}
t.lock.Unlock()
}
func (t *Timer) up(j int) {
for {
i := (j - 1) / 2 // parent
if i <= j || !t.less(j, i) {
break
}
t.swap(i, j)
j = i
}
}
func (t *Timer) down(i, n int) {
for {
j1 := 2*i + 1
if j1 >= n || j1 < 0 { // j1 < 0 after int overflow
break
}
j := j1 // left child
if j2 := j1 + 1; j2 < n && !t.less(j1, j2) {
j = j2 // = 2*i + 2 // right child
}
if !t.less(j, i) {
break
}
t.swap(i, j)
i = j
}
}
func (t *Timer) less(i, j int) bool {
return t.timers[i].expire.Before(t.timers[j].expire)
}
func (t *Timer) swap(i, j int) {
t.timers[i], t.timers[j] = t.timers[j], t.timers[i]
t.timers[i].index = i
t.timers[j].index = j
}

View File

@@ -0,0 +1,43 @@
package time
import (
"testing"
"time"
"go-common/library/log"
)
func TestTimer(t *testing.T) {
timer := NewTimer(100)
tds := make([]*TimerData, 100)
for i := 0; i < 100; i++ {
tds[i] = timer.Add(time.Duration(i)*time.Second+5*time.Minute, nil)
}
printTimer(timer)
for i := 0; i < 100; i++ {
log.Info("td: %s, %s, %d", tds[i].Key, tds[i].ExpireString(), tds[i].index)
timer.Del(tds[i])
}
printTimer(timer)
for i := 0; i < 100; i++ {
tds[i] = timer.Add(time.Duration(i)*time.Second+5*time.Minute, nil)
}
printTimer(timer)
for i := 0; i < 100; i++ {
timer.Del(tds[i])
}
printTimer(timer)
timer.Add(time.Second, nil)
time.Sleep(time.Second * 2)
if len(timer.timers) != 0 {
t.FailNow()
}
}
func printTimer(timer *Timer) {
log.Info("----------timers: %d ----------", len(timer.timers))
for i := 0; i < len(timer.timers); i++ {
log.Info("timer: %s, %s, index: %d", timer.timers[i].Key, timer.timers[i].ExpireString(), timer.timers[i].index)
}
log.Info("--------------------")
}

View File

@@ -0,0 +1,33 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"conn.go",
"request.go",
"server.go",
],
importpath = "go-common/app/service/main/broadcast/libs/websocket",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//app/service/main/broadcast/libs/bufio:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,256 @@
package websocket
import (
"encoding/binary"
"errors"
"fmt"
"io"
"go-common/app/service/main/broadcast/libs/bufio"
)
const (
// Frame header byte 0 bits from Section 5.2 of RFC 6455
finBit = 1 << 7
rsv1Bit = 1 << 6
rsv2Bit = 1 << 5
rsv3Bit = 1 << 4
opBit = 0x0f
// Frame header byte 1 bits from Section 5.2 of RFC 6455
maskBit = 1 << 7
lenBit = 0x7f
continuationFrame = 0
continuationFrameMaxRead = 100
)
// The message types are defined in RFC 6455, section 11.8.
const (
// TextMessage denotes a text data message. The text message payload is
// interpreted as UTF-8 encoded text data.
TextMessage = 1
// BinaryMessage denotes a binary data message.
BinaryMessage = 2
// CloseMessage denotes a close control message. The optional message
// payload contains a numeric code and text. Use the FormatCloseMessage
// function to format a close message payload.
CloseMessage = 8
// PingMessage denotes a ping control message. The optional message payload
// is UTF-8 encoded text.
PingMessage = 9
// PongMessage denotes a ping control message. The optional message payload
// is UTF-8 encoded text.
PongMessage = 10
)
var (
// ErrMessageClose close control message
ErrMessageClose = errors.New("close control message")
// ErrMessageMaxRead continuation frrame max read
ErrMessageMaxRead = errors.New("continuation frame max read")
)
// Conn represents a WebSocket connection.
type Conn struct {
rwc io.ReadWriteCloser
r *bufio.Reader
w *bufio.Writer
}
// new connection
func newConn(rwc io.ReadWriteCloser, r *bufio.Reader, w *bufio.Writer) *Conn {
return &Conn{rwc: rwc, r: r, w: w}
}
// WriteMessage write a message by type.
func (c *Conn) WriteMessage(msgType int, msg []byte) (err error) {
if err = c.WriteHeader(msgType, len(msg)); err != nil {
return
}
err = c.WriteBody(msg)
return
}
// WriteHeader write header frame.
func (c *Conn) WriteHeader(msgType int, length int) (err error) {
var h []byte
if h, err = c.w.Peek(2); err != nil {
return
}
// 1.First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)
h[0] = 0
h[0] |= finBit | byte(msgType)
// 2.Second byte. Mask/Payload len(7bits)
h[1] = 0
switch {
case length <= 125:
// 7 bits
h[1] |= byte(length)
case length < 65536:
// 16 bits
h[1] |= 126
if h, err = c.w.Peek(2); err != nil {
return
}
binary.BigEndian.PutUint16(h, uint16(length))
default:
// 64 bits
h[1] |= 127
if h, err = c.w.Peek(8); err != nil {
return
}
binary.BigEndian.PutUint64(h, uint64(length))
}
return
}
// WriteBody write a message body.
func (c *Conn) WriteBody(b []byte) (err error) {
if len(b) > 0 {
_, err = c.w.Write(b)
}
return
}
// Peek write peek.
func (c *Conn) Peek(n int) ([]byte, error) {
return c.w.Peek(n)
}
// Flush flush writer buffer
func (c *Conn) Flush() error {
return c.w.Flush()
}
// ReadMessage read a message.
func (c *Conn) ReadMessage() (op int, payload []byte, err error) {
var (
fin bool
finOp, n int
partPayload []byte
)
for {
// read frame
if fin, op, partPayload, err = c.readFrame(); err != nil {
return
}
switch op {
case BinaryMessage, TextMessage, continuationFrame:
if fin && len(payload) == 0 {
return op, partPayload, nil
}
// continuation frame
payload = append(payload, partPayload...)
if op != continuationFrame {
finOp = op
}
// final frame
if fin {
op = finOp
return
}
case PingMessage:
// handler ping
if err = c.WriteMessage(PongMessage, partPayload); err != nil {
return
}
case PongMessage:
// handler pong
case CloseMessage:
// handler close
err = ErrMessageClose
return
default:
err = fmt.Errorf("unknown control message, fin=%t, op=%d", fin, op)
return
}
if n > continuationFrameMaxRead {
err = ErrMessageMaxRead
return
}
n++
}
}
func (c *Conn) readFrame() (fin bool, op int, payload []byte, err error) {
var (
b byte
p []byte
mask bool
maskKey []byte
payloadLen int64
)
// 1.First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)
b, err = c.r.ReadByte()
if err != nil {
return
}
// final frame
fin = (b & finBit) != 0
// rsv MUST be 0
if rsv := b & (rsv1Bit | rsv2Bit | rsv3Bit); rsv != 0 {
return false, 0, nil, fmt.Errorf("unexpected reserved bits rsv1=%d, rsv2=%d, rsv3=%d", b&rsv1Bit, b&rsv2Bit, b&rsv3Bit)
}
// op code
op = int(b & opBit)
// 2.Second byte. Mask/Payload len(7bits)
b, err = c.r.ReadByte()
if err != nil {
return
}
// is mask payload
mask = (b & maskBit) != 0
// payload length
switch b & lenBit {
case 126:
// 16 bits
if p, err = c.r.Pop(2); err != nil {
return
}
payloadLen = int64(binary.BigEndian.Uint16(p))
case 127:
// 64 bits
if p, err = c.r.Pop(8); err != nil {
return
}
payloadLen = int64(binary.BigEndian.Uint64(p))
default:
// 7 bits
payloadLen = int64(b & lenBit)
}
// read mask key
if mask {
maskKey, err = c.r.Pop(4)
if err != nil {
return
}
}
// read payload
if payloadLen > 0 {
if payload, err = c.r.Pop(int(payloadLen)); err != nil {
return
}
if mask {
maskBytes(maskKey, 0, payload)
}
}
return
}
// Close close the connection.
func (c *Conn) Close() error {
return c.rwc.Close()
}
func maskBytes(key []byte, pos int, b []byte) int {
for i := range b {
b[i] ^= key[pos&3]
pos++
}
return pos & 3
}

View File

@@ -0,0 +1,115 @@
package websocket
import (
"bytes"
"fmt"
"net/http"
"strings"
"go-common/app/service/main/broadcast/libs/bufio"
)
// Request request.
type Request struct {
Method string
RequestURI string
Proto string
Host string
Header http.Header
reader *bufio.Reader
}
// ReadRequest reads and parses an incoming request from b.
func ReadRequest(r *bufio.Reader) (req *Request, err error) {
var (
b []byte
ok bool
)
req = &Request{reader: r}
if b, err = req.readLine(); err != nil {
return
}
if req.Method, req.RequestURI, req.Proto, ok = parseRequestLine(string(b)); !ok {
return nil, fmt.Errorf("malformed HTTP request %s", b)
}
if req.Header, err = req.readMIMEHeader(); err != nil {
return
}
req.Host = req.Header.Get("Host")
return req, nil
}
func (r *Request) readLine() ([]byte, error) {
var line []byte
for {
l, more, err := r.reader.ReadLine()
if err != nil {
return nil, err
}
// Avoid the copy if the first call produced a full line.
if line == nil && !more {
return l, nil
}
line = append(line, l...)
if !more {
break
}
}
return line, nil
}
func (r *Request) readMIMEHeader() (header http.Header, err error) {
var (
line []byte
i int
k, v string
)
header = make(http.Header, 16)
for {
if line, err = r.readLine(); err != nil {
return
}
line = trim(line)
if len(line) == 0 {
return
}
if i = bytes.IndexByte(line, ':'); i <= 0 {
err = fmt.Errorf("malformed MIME header line: " + string(line))
return
}
k = string(line[:i])
// Skip initial spaces in value.
i++ // skip colon
for i < len(line) && (line[i] == ' ' || line[i] == '\t') {
i++
}
v = string(line[i:])
header.Add(k, v)
}
}
// parseRequestLine parses "GET /foo HTTP/1.1" into its three parts.
func parseRequestLine(line string) (method, requestURI, proto string, ok bool) {
s1 := strings.Index(line, " ")
s2 := strings.Index(line[s1+1:], " ")
if s1 < 0 || s2 < 0 {
return
}
s2 += s1 + 1
return line[:s1], line[s1+1 : s2], line[s2+1:], true
}
// trim returns s with leading and trailing spaces and tabs removed.
// It does not assume Unicode or UTF-8.
func trim(s []byte) []byte {
i := 0
for i < len(s) && (s[i] == ' ' || s[i] == '\t') {
i++
}
n := len(s)
for n > i && (s[n-1] == ' ' || s[n-1] == '\t') {
n--
}
return s[i:n]
}

View File

@@ -0,0 +1,56 @@
package websocket
import (
"crypto/sha1"
"encoding/base64"
"errors"
"io"
"strings"
"go-common/app/service/main/broadcast/libs/bufio"
)
var (
keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
// ErrBadRequestMethod bad request method
ErrBadRequestMethod = errors.New("bad method")
// ErrNotWebSocket not websocket protocal
ErrNotWebSocket = errors.New("not websocket protocol")
// ErrBadWebSocketVersion bad websocket version
ErrBadWebSocketVersion = errors.New("missing or bad WebSocket Version")
// ErrChallengeResponse mismatch challenge response
ErrChallengeResponse = errors.New("mismatch challenge/response")
)
// Upgrade Switching Protocols
func Upgrade(rwc io.ReadWriteCloser, rr *bufio.Reader, wr *bufio.Writer, req *Request) (conn *Conn, err error) {
if req.Method != "GET" {
return nil, ErrBadRequestMethod
}
if req.Header.Get("Sec-Websocket-Version") != "13" {
return nil, ErrBadWebSocketVersion
}
if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" {
return nil, ErrNotWebSocket
}
if !strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") {
return nil, ErrNotWebSocket
}
challengeKey := req.Header.Get("Sec-Websocket-Key")
if challengeKey == "" {
return nil, ErrChallengeResponse
}
wr.WriteString("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n")
wr.WriteString("Sec-WebSocket-Accept: " + computeAcceptKey(challengeKey) + "\r\n\r\n")
if err = wr.Flush(); err != nil {
return
}
return newConn(rwc, rr, wr), nil
}
func computeAcceptKey(challengeKey string) string {
h := sha1.New()
h.Write([]byte(challengeKey))
h.Write(keyGUID)
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

View File

@@ -0,0 +1,63 @@
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
proto_library(
name = "model_proto",
srcs = ["model.proto"],
tags = ["automanaged"],
)
go_proto_library(
name = "model_go_proto",
compilers = ["@io_bazel_rules_go//proto:gogofast_proto"],
importpath = "go-common/app/service/main/broadcast/model",
proto = ":model_proto",
tags = ["manual"],
)
go_library(
name = "go_default_library",
srcs = [
"auth.go",
"model.go",
"model_v1.go",
"online.go",
"operation.go",
"room.go",
"server.go",
],
embed = [":model_go_proto"],
importpath = "go-common/app/service/main/broadcast/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/broadcast/libs/bufio:go_default_library",
"//app/service/main/broadcast/libs/bytes:go_default_library",
"//app/service/main/broadcast/libs/encoding/binary:go_default_library",
"//app/service/main/broadcast/libs/websocket:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,15 @@
package model
// AuthToken auth token.
type AuthToken struct {
DeviceID string `json:"device_id"` // 客户端唯一key
RoomID string `json:"room_id"` // 业务房间号
AccessKey string `json:"access_key"` // access key用来获取mid
Platform string `json:"platform"` // 平台, android/ios/h5/web
MobiApp string `json:"mobi_app"` // mobi_app
Build int32 `json:"build"` // build
Accepts []int32 `json:"accepts"` // accept operations
// 兼容goim-chat
Aid int64 `json:"aid"`
Cid int64 `json:"roomid"`
}

View File

@@ -0,0 +1,256 @@
package model
import (
"errors"
"go-common/app/service/main/broadcast/libs/bufio"
"go-common/app/service/main/broadcast/libs/bytes"
"go-common/app/service/main/broadcast/libs/encoding/binary"
"go-common/app/service/main/broadcast/libs/websocket"
)
const (
// MaxBodySize max proto body size
MaxBodySize = int32(1 << 12)
)
const (
// size
_packSize = 4
_headerSize = 2
_verSize = 2
_operationSize = 4
_seqIDSize = 4
_compressSize = 1
_contentTypeSize = 1
_rawHeaderSize = _packSize + _headerSize + _verSize + _operationSize + _seqIDSize + _compressSize + _contentTypeSize
_maxPackSize = MaxBodySize + int32(_rawHeaderSize)
// offset
_packOffset = 0
_headerOffset = _packOffset + _packSize
_verOffset = _headerOffset + _headerSize
_operationOffset = _verOffset + _verSize
_seqIDOffset = _operationOffset + _operationSize
_compressOffset = _seqIDOffset + _seqIDSize
_contentTypeOffset = _compressOffset + _compressSize
)
var (
emptyJSONBody = []byte("{}")
// ErrProtoPackLen proto packet len error
ErrProtoPackLen = errors.New("default server codec pack length error")
// ErrProtoHeaderLen proto header len error
ErrProtoHeaderLen = errors.New("default server codec header length error")
)
var (
// ProtoReady proto ready
ProtoReady = &Proto{Operation: OpProtoReady}
// ProtoFinish proto finish
ProtoFinish = &Proto{Operation: OpProtoFinish}
)
// WriteTo write a proto to bytes writer.
func (p *Proto) WriteTo(b *bytes.Writer) {
var (
packLen = _rawHeaderSize + int32(len(p.Body))
buf = b.Peek(_rawHeaderSize)
)
binary.BigEndian.PutInt32(buf[_packOffset:], packLen)
binary.BigEndian.PutInt16(buf[_headerOffset:], int16(_rawHeaderSize))
binary.BigEndian.PutInt16(buf[_verOffset:], int16(p.Ver))
binary.BigEndian.PutInt32(buf[_operationOffset:], p.Operation)
binary.BigEndian.PutInt32(buf[_seqIDOffset:], p.SeqId)
binary.BigEndian.PutInt8(buf[_compressOffset:], int8(p.Compress))
binary.BigEndian.PutInt8(buf[_contentTypeOffset:], int8(p.ContentType))
if p.Body != nil {
b.Write(p.Body)
}
}
// ReadTCP read a proto from TCP reader.
func (p *Proto) ReadTCP(rr *bufio.Reader) (err error) {
var (
bodyLen int
headerLen int16
packLen int32
buf []byte
)
if buf, err = rr.Pop(_rawHeaderSize); err != nil {
return
}
packLen = binary.BigEndian.Int32(buf[_packOffset:_headerOffset])
headerLen = binary.BigEndian.Int16(buf[_headerOffset:_verOffset])
p.Ver = int32(binary.BigEndian.Int16(buf[_verOffset:_operationOffset]))
p.Operation = binary.BigEndian.Int32(buf[_operationOffset:_seqIDOffset])
p.SeqId = binary.BigEndian.Int32(buf[_seqIDOffset:_compressOffset])
p.Compress = int32(binary.BigEndian.Int8(buf[_compressOffset:_contentTypeOffset]))
p.ContentType = int32(binary.BigEndian.Int8(buf[_contentTypeOffset:]))
if packLen > _maxPackSize {
return ErrProtoPackLen
}
if headerLen != _rawHeaderSize {
return ErrProtoHeaderLen
}
if bodyLen = int(packLen - int32(headerLen)); bodyLen > 0 {
p.Body, err = rr.Pop(bodyLen)
} else {
p.Body = nil
}
return
}
// WriteTCP write a proto to TCP writer.
func (p *Proto) WriteTCP(wr *bufio.Writer) (err error) {
var (
buf []byte
packLen int32
)
if p.Operation == OpRaw {
// write without buffer, job concact proto into raw buffer
_, err = wr.WriteRaw(p.Body)
return
}
packLen = _rawHeaderSize + int32(len(p.Body))
if buf, err = wr.Peek(_rawHeaderSize); err != nil {
return
}
binary.BigEndian.PutInt32(buf[_packOffset:], packLen)
binary.BigEndian.PutInt16(buf[_headerOffset:], int16(_rawHeaderSize))
binary.BigEndian.PutInt16(buf[_verOffset:], int16(p.Ver))
binary.BigEndian.PutInt32(buf[_operationOffset:], p.Operation)
binary.BigEndian.PutInt32(buf[_seqIDOffset:], p.SeqId)
binary.BigEndian.PutInt8(buf[_compressOffset:], int8(p.Compress))
binary.BigEndian.PutInt8(buf[_contentTypeOffset:], int8(p.ContentType))
if p.Body != nil {
_, err = wr.Write(p.Body)
}
return
}
// ReadWebsocket read a proto from websocket connection.
func (p *Proto) ReadWebsocket(ws *websocket.Conn) (err error) {
var (
bodyLen int
headerLen int16
packLen int32
buf []byte
)
if _, buf, err = ws.ReadMessage(); err != nil {
return
}
if len(buf) < _rawHeaderSize {
return ErrProtoPackLen
}
packLen = binary.BigEndian.Int32(buf[_packOffset:_headerOffset])
headerLen = binary.BigEndian.Int16(buf[_headerOffset:_verOffset])
p.Ver = int32(binary.BigEndian.Int16(buf[_verOffset:_operationOffset]))
p.Operation = binary.BigEndian.Int32(buf[_operationOffset:_seqIDOffset])
p.SeqId = binary.BigEndian.Int32(buf[_seqIDOffset:_compressOffset])
p.Compress = int32(binary.BigEndian.Int8(buf[_compressOffset:_contentTypeOffset]))
p.ContentType = int32(binary.BigEndian.Int8(buf[_contentTypeOffset:]))
if packLen > _maxPackSize {
return ErrProtoPackLen
}
if headerLen != _rawHeaderSize {
return ErrProtoHeaderLen
}
if bodyLen = int(packLen - int32(headerLen)); bodyLen > 0 {
p.Body = buf[headerLen:packLen]
} else {
p.Body = nil
}
return
}
// WriteWebsocket write a proto to websocket connection.
func (p *Proto) WriteWebsocket(ws *websocket.Conn) (err error) {
var (
buf []byte
packLen int
)
// NOTE: 通过 OpRaw = 9 为ws批量消息处理
// if p.Operation == OpRaw {
// err = ws.WriteMessage(websocket.BinaryMessage, p.Body)
// return
// }
packLen = _rawHeaderSize + len(p.Body)
if err = ws.WriteHeader(websocket.BinaryMessage, packLen); err != nil {
return
}
if buf, err = ws.Peek(_rawHeaderSize); err != nil {
return
}
binary.BigEndian.PutInt32(buf[_packOffset:], int32(packLen))
binary.BigEndian.PutInt16(buf[_headerOffset:], int16(_rawHeaderSize))
binary.BigEndian.PutInt16(buf[_verOffset:], int16(p.Ver))
binary.BigEndian.PutInt32(buf[_operationOffset:], p.Operation)
binary.BigEndian.PutInt32(buf[_seqIDOffset:], p.SeqId)
binary.BigEndian.PutInt8(buf[_compressOffset:], int8(p.Compress))
binary.BigEndian.PutInt8(buf[_contentTypeOffset:], int8(p.ContentType))
if p.Body != nil {
err = ws.WriteBody(p.Body)
}
return
}
// WriteWebsocketHeart write a heartbeat proto to websocket connnection.
func (p *Proto) WriteWebsocketHeart(wr *websocket.Conn) (err error) {
var (
buf []byte
packLen int
)
if len(p.Body) == 0 {
p.Body = emptyJSONBody
}
packLen = _rawHeaderSize + len(p.Body)
// websocket header
if err = wr.WriteHeader(websocket.BinaryMessage, packLen); err != nil {
return
}
if buf, err = wr.Peek(_rawHeaderSize); err != nil {
return
}
// proto header
binary.BigEndian.PutInt32(buf[_packOffset:], int32(packLen))
binary.BigEndian.PutInt16(buf[_headerOffset:], int16(_rawHeaderSize))
binary.BigEndian.PutInt16(buf[_verOffset:], int16(p.Ver))
binary.BigEndian.PutInt32(buf[_operationOffset:], p.Operation)
binary.BigEndian.PutInt32(buf[_seqIDOffset:], p.SeqId)
binary.BigEndian.PutInt8(buf[_compressOffset:], int8(p.Compress))
binary.BigEndian.PutInt8(buf[_contentTypeOffset:], int8(p.ContentType))
// proto body
if p.Body != nil {
err = wr.WriteBody(p.Body)
}
return
}
// WriteTCPHeart write a heartbeat proto to TCP writer.
func (p *Proto) WriteTCPHeart(wr *bufio.Writer) (err error) {
var (
buf []byte
packLen int32
)
if len(p.Body) == 0 {
p.Body = emptyJSONBody
}
packLen = _rawHeaderSize + int32(len(p.Body))
if buf, err = wr.Peek(_rawHeaderSize); err != nil {
return
}
// header
binary.BigEndian.PutInt32(buf[_packOffset:], int32(packLen))
binary.BigEndian.PutInt16(buf[_headerOffset:], int16(_rawHeaderSize))
binary.BigEndian.PutInt16(buf[_verOffset:], int16(p.Ver))
binary.BigEndian.PutInt32(buf[_operationOffset:], p.Operation)
binary.BigEndian.PutInt32(buf[_seqIDOffset:], p.SeqId)
binary.BigEndian.PutInt8(buf[_compressOffset:], int8(p.Compress))
binary.BigEndian.PutInt8(buf[_contentTypeOffset:], int8(p.ContentType))
// body
if p.Body != nil {
_, err = wr.Write(p.Body)
}
return
}

View File

@@ -0,0 +1,486 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: app/service/main/broadcast/model/model.proto
/*
Package model is a generated protocol buffer package.
It is generated from these files:
app/service/main/broadcast/model/model.proto
It has these top-level messages:
Proto
*/
package model
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
import io "io"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
type Proto struct {
Ver int32 `protobuf:"varint,1,opt,name=ver,proto3" json:"ver,omitempty"`
Operation int32 `protobuf:"varint,2,opt,name=operation,proto3" json:"operation,omitempty"`
SeqId int32 `protobuf:"varint,3,opt,name=seqId,proto3" json:"seqId,omitempty"`
Compress int32 `protobuf:"varint,4,opt,name=compress,proto3" json:"compress,omitempty"`
ContentType int32 `protobuf:"varint,5,opt,name=contentType,proto3" json:"contentType,omitempty"`
Body []byte `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"`
}
func (m *Proto) Reset() { *m = Proto{} }
func (m *Proto) String() string { return proto.CompactTextString(m) }
func (*Proto) ProtoMessage() {}
func (*Proto) Descriptor() ([]byte, []int) { return fileDescriptorModel, []int{0} }
func (m *Proto) GetVer() int32 {
if m != nil {
return m.Ver
}
return 0
}
func (m *Proto) GetOperation() int32 {
if m != nil {
return m.Operation
}
return 0
}
func (m *Proto) GetSeqId() int32 {
if m != nil {
return m.SeqId
}
return 0
}
func (m *Proto) GetCompress() int32 {
if m != nil {
return m.Compress
}
return 0
}
func (m *Proto) GetContentType() int32 {
if m != nil {
return m.ContentType
}
return 0
}
func (m *Proto) GetBody() []byte {
if m != nil {
return m.Body
}
return nil
}
func init() {
proto.RegisterType((*Proto)(nil), "push.service.broadcast.model.Proto")
}
func (m *Proto) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Proto) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if m.Ver != 0 {
dAtA[i] = 0x8
i++
i = encodeVarintModel(dAtA, i, uint64(m.Ver))
}
if m.Operation != 0 {
dAtA[i] = 0x10
i++
i = encodeVarintModel(dAtA, i, uint64(m.Operation))
}
if m.SeqId != 0 {
dAtA[i] = 0x18
i++
i = encodeVarintModel(dAtA, i, uint64(m.SeqId))
}
if m.Compress != 0 {
dAtA[i] = 0x20
i++
i = encodeVarintModel(dAtA, i, uint64(m.Compress))
}
if m.ContentType != 0 {
dAtA[i] = 0x28
i++
i = encodeVarintModel(dAtA, i, uint64(m.ContentType))
}
if len(m.Body) > 0 {
dAtA[i] = 0x32
i++
i = encodeVarintModel(dAtA, i, uint64(len(m.Body)))
i += copy(dAtA[i:], m.Body)
}
return i, nil
}
func encodeVarintModel(dAtA []byte, offset int, v uint64) int {
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return offset + 1
}
func (m *Proto) Size() (n int) {
var l int
_ = l
if m.Ver != 0 {
n += 1 + sovModel(uint64(m.Ver))
}
if m.Operation != 0 {
n += 1 + sovModel(uint64(m.Operation))
}
if m.SeqId != 0 {
n += 1 + sovModel(uint64(m.SeqId))
}
if m.Compress != 0 {
n += 1 + sovModel(uint64(m.Compress))
}
if m.ContentType != 0 {
n += 1 + sovModel(uint64(m.ContentType))
}
l = len(m.Body)
if l > 0 {
n += 1 + l + sovModel(uint64(l))
}
return n
}
func sovModel(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
}
func sozModel(x uint64) (n int) {
return sovModel(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *Proto) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowModel
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Proto: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Proto: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Ver", wireType)
}
m.Ver = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowModel
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Ver |= (int32(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 2:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Operation", wireType)
}
m.Operation = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowModel
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Operation |= (int32(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 3:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field SeqId", wireType)
}
m.SeqId = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowModel
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.SeqId |= (int32(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 4:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Compress", wireType)
}
m.Compress = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowModel
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Compress |= (int32(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 5:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field ContentType", wireType)
}
m.ContentType = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowModel
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.ContentType |= (int32(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 6:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Body", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowModel
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthModel
}
postIndex := iNdEx + byteLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Body = append(m.Body[:0], dAtA[iNdEx:postIndex]...)
if m.Body == nil {
m.Body = []byte{}
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipModel(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthModel
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipModel(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowModel
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowModel
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowModel
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
iNdEx += length
if length < 0 {
return 0, ErrInvalidLengthModel
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowModel
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipModel(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
}
return iNdEx, nil
case 4:
return iNdEx, nil
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
}
panic("unreachable")
}
var (
ErrInvalidLengthModel = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowModel = fmt.Errorf("proto: integer overflow")
)
func init() { proto.RegisterFile("app/service/main/broadcast/model/model.proto", fileDescriptorModel) }
var fileDescriptorModel = []byte{
// 216 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x49, 0x2c, 0x28, 0xd0,
0x2f, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0xd5, 0xcf, 0x4d, 0xcc, 0xcc, 0xd3, 0x4f, 0x2a, 0xca,
0x4f, 0x4c, 0x49, 0x4e, 0x2c, 0x2e, 0xd1, 0xcf, 0xcd, 0x4f, 0x49, 0xcd, 0x81, 0x90, 0x7a, 0x05,
0x45, 0xf9, 0x25, 0xf9, 0x42, 0x32, 0x05, 0xa5, 0xc5, 0x19, 0x7a, 0x50, 0xe5, 0x7a, 0x70, 0x95,
0x7a, 0x60, 0x35, 0x4a, 0xf3, 0x19, 0xb9, 0x58, 0x03, 0xc0, 0xea, 0x04, 0xb8, 0x98, 0xcb, 0x52,
0x8b, 0x24, 0x18, 0x15, 0x18, 0x35, 0x58, 0x83, 0x40, 0x4c, 0x21, 0x19, 0x2e, 0xce, 0xfc, 0x82,
0xd4, 0xa2, 0xc4, 0x92, 0xcc, 0xfc, 0x3c, 0x09, 0x26, 0xb0, 0x38, 0x42, 0x40, 0x48, 0x84, 0x8b,
0xb5, 0x38, 0xb5, 0xd0, 0x33, 0x45, 0x82, 0x19, 0x2c, 0x03, 0xe1, 0x08, 0x49, 0x71, 0x71, 0x24,
0xe7, 0xe7, 0x16, 0x14, 0xa5, 0x16, 0x17, 0x4b, 0xb0, 0x80, 0x25, 0xe0, 0x7c, 0x21, 0x05, 0x2e,
0xee, 0xe4, 0xfc, 0xbc, 0x92, 0xd4, 0xbc, 0x92, 0x90, 0xca, 0x82, 0x54, 0x09, 0x56, 0xb0, 0x34,
0xb2, 0x90, 0x90, 0x10, 0x17, 0x4b, 0x52, 0x7e, 0x4a, 0xa5, 0x04, 0x9b, 0x02, 0xa3, 0x06, 0x4f,
0x10, 0x98, 0xed, 0x24, 0x7c, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, 0x1e, 0xc9,
0x31, 0x46, 0xb1, 0x82, 0x9d, 0x9d, 0xc4, 0x06, 0xf6, 0x9b, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff,
0x43, 0x45, 0x1f, 0x62, 0x0b, 0x01, 0x00, 0x00,
}

View File

@@ -0,0 +1,16 @@
// +bili:type=service
// Code generated by warden.
syntax = "proto3";
package push.service.broadcast.model;
option go_package = "model";
message Proto {
int32 ver = 1;
int32 operation = 2;
int32 seqId = 3;
int32 compress = 4;
int32 contentType = 5;
bytes body = 6;
}

View File

@@ -0,0 +1,207 @@
package model
import (
"go-common/app/service/main/broadcast/libs/bufio"
"go-common/app/service/main/broadcast/libs/bytes"
"go-common/app/service/main/broadcast/libs/encoding/binary"
"go-common/app/service/main/broadcast/libs/websocket"
)
const (
maxBodySizeV1 = int32(1 << 10)
// size
packSizeV1 = 4
headerSizeV1 = 2
verSizeV1 = 2
operationSizeV1 = 4
seqIDSizeV1 = 4
heartbeatSizeV1 = 4
rawHeaderSizeV1 = packSizeV1 + headerSizeV1 + verSizeV1 + operationSizeV1 + seqIDSizeV1
maxPackSizeV1 = maxBodySizeV1 + int32(rawHeaderSizeV1)
// offset
packOffsetV1 = 0
headerOffsetV1 = packOffsetV1 + packSizeV1
verOffsetV1 = headerOffsetV1 + headerSizeV1
operationOffsetV1 = verOffsetV1 + verSizeV1
seqIDOffsetV1 = operationOffsetV1 + operationSizeV1
heartbeatOffsetV1 = seqIDOffsetV1 + seqIDSizeV1
)
// WriteToV1 .
func (p *Proto) WriteToV1(b *bytes.Writer) {
var (
packLen = rawHeaderSizeV1 + int32(len(p.Body))
buf = b.Peek(rawHeaderSizeV1)
)
binary.BigEndian.PutInt32(buf[packOffsetV1:], packLen)
binary.BigEndian.PutInt16(buf[headerOffsetV1:], int16(rawHeaderSizeV1))
binary.BigEndian.PutInt16(buf[verOffsetV1:], int16(p.Ver))
binary.BigEndian.PutInt32(buf[operationOffsetV1:], p.Operation)
binary.BigEndian.PutInt32(buf[seqIDOffsetV1:], p.SeqId)
if p.Body != nil {
b.Write(p.Body)
}
}
// ReadTCPV1 .
func (p *Proto) ReadTCPV1(rr *bufio.Reader) (err error) {
var (
bodyLen int
headerLen int16
packLen int32
buf []byte
)
if buf, err = rr.Pop(rawHeaderSizeV1); err != nil {
return
}
packLen = binary.BigEndian.Int32(buf[packOffsetV1:headerOffsetV1])
headerLen = binary.BigEndian.Int16(buf[headerOffsetV1:verOffsetV1])
p.Ver = int32(binary.BigEndian.Int16(buf[verOffsetV1:operationOffsetV1]))
p.Operation = binary.BigEndian.Int32(buf[operationOffsetV1:seqIDOffsetV1])
p.SeqId = binary.BigEndian.Int32(buf[seqIDOffsetV1:])
if packLen > maxPackSizeV1 {
return ErrProtoPackLen
}
if headerLen != rawHeaderSizeV1 {
return ErrProtoHeaderLen
}
if bodyLen = int(packLen - int32(headerLen)); bodyLen > 0 {
p.Body, err = rr.Pop(bodyLen)
} else {
p.Body = nil
}
return
}
// WriteTCPV1 .
func (p *Proto) WriteTCPV1(wr *bufio.Writer) (err error) {
var (
buf []byte
packLen int32
)
if p.Operation == OpRaw {
// write without buffer, job concact proto into raw buffer
_, err = wr.WriteRaw(p.Body)
return
}
packLen = rawHeaderSizeV1 + int32(len(p.Body))
if buf, err = wr.Peek(rawHeaderSizeV1); err != nil {
return
}
binary.BigEndian.PutInt32(buf[packOffsetV1:], packLen)
binary.BigEndian.PutInt16(buf[headerOffsetV1:], int16(rawHeaderSizeV1))
binary.BigEndian.PutInt16(buf[verOffsetV1:], int16(p.Ver))
binary.BigEndian.PutInt32(buf[operationOffsetV1:], p.Operation)
binary.BigEndian.PutInt32(buf[seqIDOffsetV1:], p.SeqId)
if p.Body != nil {
_, err = wr.Write(p.Body)
}
return
}
// WriteTCPHeartV1 .
func (p *Proto) WriteTCPHeartV1(wr *bufio.Writer, online int32) (err error) {
var (
buf []byte
packLen int
)
packLen = rawHeaderSizeV1 + heartbeatSizeV1
if buf, err = wr.Peek(packLen); err != nil {
return
}
// header
binary.BigEndian.PutInt32(buf[packOffsetV1:], int32(packLen))
binary.BigEndian.PutInt16(buf[headerOffsetV1:], int16(rawHeaderSizeV1))
binary.BigEndian.PutInt16(buf[verOffsetV1:], int16(p.Ver))
binary.BigEndian.PutInt32(buf[operationOffsetV1:], p.Operation)
binary.BigEndian.PutInt32(buf[seqIDOffsetV1:], p.SeqId)
// body
binary.BigEndian.PutInt32(buf[heartbeatOffsetV1:], online)
return
}
// ReadWebsocketV1 .
func (p *Proto) ReadWebsocketV1(ws *websocket.Conn) (err error) {
var (
bodyLen int
headerLen int16
packLen int32
buf []byte
)
if _, buf, err = ws.ReadMessage(); err != nil {
return
}
if len(buf) < rawHeaderSizeV1 {
return ErrProtoPackLen
}
packLen = binary.BigEndian.Int32(buf[packOffsetV1:headerOffsetV1])
headerLen = binary.BigEndian.Int16(buf[headerOffsetV1:verOffsetV1])
p.Ver = int32(binary.BigEndian.Int16(buf[verOffsetV1:operationOffsetV1]))
p.Operation = binary.BigEndian.Int32(buf[operationOffsetV1:seqIDOffsetV1])
p.SeqId = binary.BigEndian.Int32(buf[seqIDOffsetV1:])
if packLen > maxPackSizeV1 {
return ErrProtoPackLen
}
if headerLen != rawHeaderSizeV1 {
return ErrProtoHeaderLen
}
if bodyLen = int(packLen - int32(headerLen)); bodyLen > 0 {
p.Body = buf[headerLen:packLen]
} else {
p.Body = nil
}
return
}
// WriteWebsocketV1 .
func (p *Proto) WriteWebsocketV1(ws *websocket.Conn) (err error) {
var (
buf []byte
packLen int
)
if p.Operation == OpRaw {
err = ws.WriteMessage(websocket.BinaryMessage, p.Body)
return
}
packLen = rawHeaderSizeV1 + len(p.Body)
if err = ws.WriteHeader(websocket.BinaryMessage, packLen); err != nil {
return
}
if buf, err = ws.Peek(rawHeaderSizeV1); err != nil {
return
}
binary.BigEndian.PutInt32(buf[packOffsetV1:], int32(packLen))
binary.BigEndian.PutInt16(buf[headerOffsetV1:], int16(rawHeaderSizeV1))
binary.BigEndian.PutInt16(buf[verOffsetV1:], int16(p.Ver))
binary.BigEndian.PutInt32(buf[operationOffsetV1:], p.Operation)
binary.BigEndian.PutInt32(buf[seqIDOffsetV1:], p.SeqId)
if p.Body != nil {
err = ws.WriteBody(p.Body)
}
return
}
// WriteWebsocketHeartV1 .
func (p *Proto) WriteWebsocketHeartV1(wr *websocket.Conn, online int32) (err error) {
var (
buf []byte
packLen int
)
packLen = rawHeaderSizeV1 + heartbeatSizeV1
// websocket header
if err = wr.WriteHeader(websocket.BinaryMessage, packLen); err != nil {
return
}
if buf, err = wr.Peek(packLen); err != nil {
return
}
// proto header
binary.BigEndian.PutInt32(buf[packOffsetV1:], int32(packLen))
binary.BigEndian.PutInt16(buf[headerOffsetV1:], int16(rawHeaderSizeV1))
binary.BigEndian.PutInt16(buf[verOffsetV1:], int16(p.Ver))
binary.BigEndian.PutInt32(buf[operationOffsetV1:], p.Operation)
binary.BigEndian.PutInt32(buf[seqIDOffsetV1:], p.SeqId)
// proto body
binary.BigEndian.PutInt32(buf[heartbeatOffsetV1:], online)
return
}

View File

@@ -0,0 +1,14 @@
package model
// Online ip and room online.
type Online struct {
Server string `json:"server"`
RoomCount map[string]int32 `json:"room_count"`
Updated int64 `json:"updated"`
}
// Top top sorted.
type Top struct {
RoomID string `json:"room_id"`
Count int32 `json:"count"`
}

View File

@@ -0,0 +1,54 @@
package model
const (
// OpHandshake handshake
OpHandshake = int32(0)
// OpHandshakeReply handshake reply
OpHandshakeReply = int32(1)
// OpHeartbeat heartbeat
OpHeartbeat = int32(2)
// OpHeartbeatReply heartbeat reply
OpHeartbeatReply = int32(3)
// OpSendMsg send message.
OpSendMsg = int32(4)
// OpSendMsgReply send message reply
OpSendMsgReply = int32(5)
// OpDisconnectReply disconnect reply
OpDisconnectReply = int32(6)
// OpAuth auth connnect
OpAuth = int32(7)
// OpAuthReply auth connect reply
OpAuthReply = int32(8)
// OpRaw raw message
OpRaw = int32(9)
// OpProtoReady proto ready
OpProtoReady = int32(10)
// OpProtoFinish proto finish
OpProtoFinish = int32(11)
// OpChangeRoom change room
OpChangeRoom = int32(12)
// OpChangeRoomReply change room reply
OpChangeRoomReply = int32(13)
// OpRegister register operation
OpRegister = int32(14)
// OpRegisterReply register operation
OpRegisterReply = int32(15)
// OpUnregister unregister operation
OpUnregister = int32(16)
// OpUnregisterReply unregister operation reply
OpUnregisterReply = int32(17)
// MinBusinessOp min business operation
MinBusinessOp = 1000
// MaxBusinessOp max business operation
MaxBusinessOp = 10000
)

View File

@@ -0,0 +1,25 @@
package model
import (
"fmt"
"net/url"
)
const (
// NoRoom default no room key
NoRoom = "noroom"
)
// EncodeRoomKey encode a room key.
func EncodeRoomKey(business string, room string) string {
return fmt.Sprintf("%s://%s", business, room)
}
// DecodeRoomKey decode room key.
func DecodeRoomKey(key string) (string, string, error) {
u, err := url.Parse(key)
if err != nil {
return "", "", err
}
return u.Scheme, u.Host, nil
}

View File

@@ -0,0 +1,17 @@
package model
// ServerInfo server info.
type ServerInfo struct {
Region string `json:"region"`
Server string `json:"server"`
IPCount int32 `json:"ip_count"`
ConnCount int32 `json:"conn_count"`
RoomIPCount int32 `json:"room_ips"`
Weight int32 `json:"weight"`
Updated int64 `json:"updated"`
IPAddrs []string `json:"ip_addrs"`
IPAddrsV6 []string `json:"ip_addrs_v6"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
Overseas bool `json:"overseas"`
}

View File

@@ -0,0 +1,36 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["server.go"],
importpath = "go-common/app/service/main/broadcast/server/grpc",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/broadcast/api/grpc/v1:go_default_library",
"//app/service/main/broadcast/service:go_default_library",
"//library/conf/paladin:go_default_library",
"//library/net/rpc/warden:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//encoding/gzip:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,94 @@
// Package server generate by warden_gen
package server
import (
"context"
"net"
pb "go-common/app/service/main/broadcast/api/grpc/v1"
"go-common/app/service/main/broadcast/service"
"go-common/library/conf/paladin"
"go-common/library/net/rpc/warden"
"google.golang.org/grpc"
// use gzip decoder
_ "google.golang.org/grpc/encoding/gzip"
)
// New Zerg warden rpc server
func New(svr *service.Service) (*warden.Server, string) {
var rc struct {
Server *warden.ServerConfig
}
if err := paladin.Get("grpc.toml").UnmarshalTOML(&rc); err != nil {
panic(err)
}
_, port, _ := net.SplitHostPort(rc.Server.Addr)
ws := warden.NewServer(rc.Server, grpc.MaxRecvMsgSize(32*1024*1024), grpc.MaxSendMsgSize(32*1024*1024))
pb.RegisterZergServer(ws.Server(), &server{svr})
return ws, port
}
type server struct {
srv *service.Service
}
var _ pb.ZergServer = &server{}
// Ping Service
func (s *server) Ping(ctx context.Context, req *pb.PingReq) (*pb.PingReply, error) {
return &pb.PingReply{}, nil
}
// Close Service
func (s *server) Close(ctx context.Context, req *pb.CloseReq) (*pb.CloseReply, error) {
return &pb.CloseReply{}, nil
}
// Connect connect a conn.
func (s *server) Connect(ctx context.Context, req *pb.ConnectReq) (*pb.ConnectReply, error) {
mid, key, room, platform, accepts, err := s.srv.Connect(ctx, req.Server, req.ServerKey, req.Cookie, req.Token)
if err != nil {
return &pb.ConnectReply{}, err
}
return &pb.ConnectReply{Mid: mid, Key: key, RoomID: room, Accepts: accepts, Platform: platform}, nil
}
// Disconnect disconnect a conn.
func (s *server) Disconnect(ctx context.Context, req *pb.DisconnectReq) (*pb.DisconnectReply, error) {
has, err := s.srv.Disconnect(ctx, req.Mid, req.Key, req.Server)
if err != nil {
return &pb.DisconnectReply{}, err
}
return &pb.DisconnectReply{Has: has}, nil
}
// Heartbeat beartbeat a conn.
func (s *server) Heartbeat(ctx context.Context, req *pb.HeartbeatReq) (*pb.HeartbeatReply, error) {
if err := s.srv.Heartbeat(ctx, req.Mid, req.Key, req.Server); err != nil {
return &pb.HeartbeatReply{}, err
}
return &pb.HeartbeatReply{}, nil
}
// RenewOnline renew server online.
func (s *server) RenewOnline(ctx context.Context, req *pb.OnlineReq) (*pb.OnlineReply, error) {
roomCount, err := s.srv.RenewOnline(ctx, req.Server, req.Sharding, req.RoomCount)
if err != nil {
return &pb.OnlineReply{}, err
}
return &pb.OnlineReply{RoomCount: roomCount}, nil
}
// Receive receive a message.
func (s *server) Receive(ctx context.Context, req *pb.ReceiveReq) (*pb.ReceiveReply, error) {
if err := s.srv.Receive(ctx, req.Mid, req.Proto); err != nil {
return &pb.ReceiveReply{}, err
}
return &pb.ReceiveReply{}, nil
}
// ServerList return server list.
func (s *server) ServerList(ctx context.Context, req *pb.ServerListReq) (*pb.ServerListReply, error) {
return s.srv.ServerList(ctx, req.Platform), nil
}

View File

@@ -0,0 +1,44 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"conn.go",
"http.go",
"online.go",
"push.go",
"server.go",
],
importpath = "go-common/app/service/main/broadcast/server/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/broadcast/api/grpc/v1:go_default_library",
"//app/service/main/broadcast/service:go_default_library",
"//library/conf/paladin:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/verify:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,106 @@
package http
import (
"io/ioutil"
pb "go-common/app/service/main/broadcast/api/grpc/v1"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
"github.com/gogo/protobuf/proto"
)
func connect(ctx *bm.Context) {
query := ctx.Request.URL.Query()
if token, err := c.Get("httpToken").String(); err != nil || token != query.Get("token") {
ctx.Protobuf(nil, ecode.Unauthorized)
return
}
b, err := ioutil.ReadAll(ctx.Request.Body)
if err != nil {
ctx.Protobuf(nil, err)
return
}
var req pb.ConnectReq
if err = proto.Unmarshal(b, &req); err != nil {
ctx.Protobuf(nil, err)
return
}
mid, key, room, platform, accepts, err := srv.Connect(ctx, req.Server, req.ServerKey, req.Cookie, req.Token)
if err != nil {
ctx.Protobuf(nil, err)
return
}
ctx.Protobuf(&pb.ConnectReply{Mid: mid, Key: key, RoomID: room, Accepts: accepts, Platform: platform}, nil)
}
func disconnect(ctx *bm.Context) {
query := ctx.Request.URL.Query()
if token, err := c.Get("httpToken").String(); err != nil || token != query.Get("token") {
ctx.Protobuf(nil, ecode.Unauthorized)
return
}
b, err := ioutil.ReadAll(ctx.Request.Body)
if err != nil {
ctx.Protobuf(nil, err)
return
}
var req pb.DisconnectReq
if err = proto.Unmarshal(b, &req); err != nil {
ctx.Protobuf(nil, err)
return
}
has, err := srv.Disconnect(ctx, req.Mid, req.Key, req.Server)
if err != nil {
ctx.Protobuf(nil, err)
return
}
ctx.Protobuf(&pb.DisconnectReply{Has: has}, nil)
}
func heartbeat(ctx *bm.Context) {
query := ctx.Request.URL.Query()
if token, err := c.Get("httpToken").String(); err != nil || token != query.Get("token") {
ctx.Protobuf(nil, ecode.Unauthorized)
return
}
b, err := ioutil.ReadAll(ctx.Request.Body)
if err != nil {
ctx.Protobuf(nil, err)
return
}
var req pb.HeartbeatReq
if err = proto.Unmarshal(b, &req); err != nil {
ctx.Protobuf(nil, err)
return
}
if err := srv.Heartbeat(ctx, req.Mid, req.Key, req.Server); err != nil {
ctx.Protobuf(nil, err)
return
}
ctx.Protobuf(&pb.HeartbeatReply{}, nil)
}
func renewOnline(ctx *bm.Context) {
query := ctx.Request.URL.Query()
if token, err := c.Get("httpToken").String(); err != nil || token != query.Get("token") {
ctx.Protobuf(nil, ecode.Unauthorized)
return
}
b, err := ioutil.ReadAll(ctx.Request.Body)
if err != nil {
ctx.Protobuf(nil, err)
return
}
var req pb.OnlineReq
if err = proto.Unmarshal(b, &req); err != nil {
ctx.Protobuf(nil, err)
return
}
roomCount, err := srv.RenewOnline(ctx, req.Server, req.Sharding, req.RoomCount)
if err != nil {
ctx.Protobuf(nil, err)
return
}
ctx.Protobuf(&pb.OnlineReply{RoomCount: roomCount}, nil)
}

View File

@@ -0,0 +1,80 @@
package http
import (
"net/http"
"go-common/app/service/main/broadcast/service"
"go-common/library/conf/paladin"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/verify"
)
var (
c paladin.Map
srv *service.Service
verifySvr *verify.Verify
)
// Init init.
func Init(s *service.Service) {
var hc struct {
Server *bm.ServerConfig
}
if err := paladin.Get("http.toml").UnmarshalTOML(&hc); err != nil {
if err != paladin.ErrNotExist {
panic(err)
}
}
if err := paladin.Watch("application.toml", &c); err != nil {
panic(err)
}
srv = s
verifySvr = verify.New(nil)
engine := bm.DefaultServer(hc.Server)
outerRouter(engine)
interRouter(engine)
if err := engine.Start(); err != nil {
log.Error("xhttp.Serve2 error(%v)", err)
panic(err)
}
}
func outerRouter(e *bm.Engine) {
g := e.Group("/x/broadcast")
{
g.POST("/conn/connect", connect)
g.POST("/conn/disconnect", disconnect)
g.POST("/conn/heartbeat", heartbeat)
g.POST("/online/renew", renewOnline)
}
}
func interRouter(e *bm.Engine) {
e.Ping(ping)
e.Register(register)
g := e.Group("/x/internal/broadcast")
{
g.POST("/push/keys", verifySvr.Verify, pushKeys)
g.POST("/push/mids", verifySvr.Verify, pushMids)
g.POST("/push/room", verifySvr.Verify, pushRoom)
g.POST("/push/all", verifySvr.Verify, pushAll)
g.GET("/online/top", onlineTop)
g.GET("/online/room", onlineRoom)
g.GET("/online/total", onlineTotal)
g.GET("/server/list", serverList)
g.GET("/server/infos", serverInfos)
g.GET("/server/weight", serverWeight)
}
}
func ping(c *bm.Context) {
if err := srv.Ping(c); err != nil {
log.Error("broadcast-service ping error(%v)", err)
c.AbortWithStatus(http.StatusServiceUnavailable)
}
}
func register(c *bm.Context) {
c.JSON(map[string]interface{}{}, nil)
}

View File

@@ -0,0 +1,31 @@
package http
import bm "go-common/library/net/http/blademaster"
func onlineTop(c *bm.Context) {
v := new(struct {
Business string `form:"business" validate:"required"`
Num int `form:"num" validate:"required"`
})
if err := c.Bind(v); err != nil {
return
}
c.JSON(srv.OnlineTop(c, v.Business, v.Num))
}
func onlineRoom(c *bm.Context) {
v := new(struct {
Business string `form:"business" validate:"required"`
Rooms []string `form:"rooms" validate:"required"`
})
if err := c.Bind(v); err != nil {
return
}
c.JSON(srv.OnlineRoom(c, v.Business, v.Rooms))
}
func onlineTotal(c *bm.Context) {
res := make(map[string]int64)
res["ip_count"], res["conn_count"] = srv.OnlineTotal(c)
c.JSON(res, nil)
}

View File

@@ -0,0 +1,76 @@
package http
import (
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
)
const maxMsgSize = 1024 * 1024 * 8
func pushKeys(c *bm.Context) {
v := new(struct {
Op int32 `form:"operation" validate:"required"`
Keys []string `form:"keys,split" validate:"required"`
Msg string `form:"message" validate:"required"`
ContentType int32 `form:"content_type"`
})
if err := c.Bind(v); err != nil {
return
}
if len(v.Msg) > maxMsgSize {
c.JSON(ecode.FileTooLarge, nil)
}
c.JSON(nil, srv.PushKeys(c, v.Op, v.Keys, v.Msg, v.ContentType))
}
func pushMids(c *bm.Context) {
v := new(struct {
Op int32 `form:"operation" validate:"required"`
Mids []int64 `form:"mids,split" validate:"required"`
Msg string `form:"message" validate:"required"`
ContentType int32 `form:"content_type"`
})
if err := c.Bind(v); err != nil {
return
}
if len(v.Msg) > maxMsgSize {
c.JSON(nil, ecode.FileTooLarge)
return
}
c.JSON(nil, srv.PushMids(c, v.Op, v.Mids, v.Msg, v.ContentType))
}
func pushRoom(c *bm.Context) {
v := new(struct {
Op int32 `form:"operation" validate:"required"`
Room string `form:"room" validate:"required"`
Msg string `form:"message" validate:"required"`
ContentType int32 `form:"content_type"`
})
if err := c.Bind(v); err != nil {
return
}
if len(v.Msg) > maxMsgSize {
c.JSON(nil, ecode.FileTooLarge)
return
}
c.JSON(nil, srv.PushRoom(c, v.Op, v.Room, v.Msg, v.ContentType))
}
func pushAll(c *bm.Context) {
v := new(struct {
Op int32 `form:"operation" validate:"required"`
Speed int32 `form:"speed" validate:"min=0"`
Msg string `form:"message" validate:"required"`
Platform string `form:"platform"`
ContentType int32 `form:"content_type"`
})
if err := c.Bind(v); err != nil {
return
}
if len(v.Msg) > maxMsgSize {
c.JSON(nil, ecode.FileTooLarge)
return
}
c.JSON(nil, srv.PushAll(c, v.Op, v.Speed, v.Msg, v.Platform, v.ContentType))
}

View File

@@ -0,0 +1,38 @@
package http
import (
bm "go-common/library/net/http/blademaster"
)
func serverInfos(c *bm.Context) {
c.JSON(srv.ServerInfos(c))
}
func serverList(c *bm.Context) {
v := new(struct {
Platform string `form:"platform" validate:"required"`
})
if err := c.Bind(v); err != nil {
return
}
c.JSON(srv.ServerList(c, v.Platform), nil)
}
func serverWeight(c *bm.Context) {
v := new(struct {
IP string `form:"ip"`
})
if err := c.Bind(v); err != nil {
return
}
nodes, region, province, err := srv.ServerWeight(c, v.IP)
if err != nil {
c.JSON(nil, err)
return
}
data := make(map[string]interface{})
data["nodes"] = nodes
data["region"] = region
data["province"] = province
c.JSON(data, err)
}

View File

@@ -0,0 +1,76 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"balancer.go",
"conn.go",
"online.go",
"push.go",
"server.go",
"service.go",
],
importpath = "go-common/app/service/main/broadcast/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/main/broadcast/api/grpc/v1:go_default_library",
"//app/service/main/broadcast/dao:go_default_library",
"//app/service/main/broadcast/model:go_default_library",
"//app/service/main/identify/api/grpc:go_default_library",
"//app/service/main/location/api:go_default_library",
"//library/cache:go_default_library",
"//library/conf/paladin:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/log/infoc:go_default_library",
"//library/naming:go_default_library",
"//library/naming/discovery:go_default_library",
"//library/net/metadata:go_default_library",
"//library/net/netutil:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/rpc/warden/balancer/wrr:go_default_library",
"//library/time:go_default_library",
"//library/xstr:go_default_library",
"//vendor/github.com/zhenjl/cityhash:go_default_library",
"@org_golang_google_grpc//:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = [
"balancer_test.go",
"conn_test.go",
"service_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/service/main/broadcast/model:go_default_library",
"//library/conf/paladin:go_default_library",
"//library/naming/discovery:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,318 @@
package service
import (
"fmt"
"math"
"sort"
"sync"
"go-common/app/service/main/broadcast/model"
"go-common/library/log"
)
const (
_minWeight = 1
_maxWeight = 1 << 20
_maxNodes = 5
)
// haversin(θ) function
func hsin(theta float64) float64 {
return math.Pow(math.Sin(theta/2), 2)
}
// Distance function returns the distance (in meters) between two points of
// a given longitude and latitude relatively accurately (using a spherical
// approximation of the Earth) through the Haversin Distance Formula for
// great arc distance on a sphere with accuracy for small distances
//
// point coordinates are supplied in degrees and converted into rad. in the func
//
// distance returned is METERS!!!!!!
// http://en.wikipedia.org/wiki/Haversine_formula
func distance(lat1, lon1, lat2, lon2 float64) float64 {
// convert to radians
// must cast radius as float to multiply later
var la1, lo1, la2, lo2, r float64
la1 = lat1 * math.Pi / 180
lo1 = lon1 * math.Pi / 180
la2 = lat2 * math.Pi / 180
lo2 = lon2 * math.Pi / 180
r = 6378100 // Earth radius in METERS
// calculate
h := hsin(la2-la1) + math.Cos(la1)*math.Cos(la2)*hsin(lo2-lo1)
return 2 * r * math.Asin(math.Sqrt(h))
}
type weightedNode struct {
region string
hostname string
addrs []string
addrsV6 []string
fixedWeight int64
currentWeight int64
currentConns int64
updated int64
lat float64
lng float64
overseas bool
}
func (w *weightedNode) String() string {
return fmt.Sprintf("region:%s fixedWeight:%d, currentWeight:%d, currentConns:%d", w.region, w.fixedWeight, w.currentWeight, w.currentConns)
}
func (w *weightedNode) chosen() {
w.currentConns++
}
func (w *weightedNode) reset() {
w.currentWeight = 0
}
func (w *weightedNode) calcuateWeightByGeo(totalWeight, totalConns, currentConns int64, totalDist, currentDist float64) {
fixedWeight := float64(w.fixedWeight)
if totalConns > 0 && w.lat != 0 && w.lng != 0 {
var (
connRatio, geoRatio float64
weightRatio = fixedWeight / float64(totalWeight)
)
if totalConns > 0 {
connRatio = float64(currentConns) / float64(totalConns) * 0.4
}
if totalDist > 0 {
geoRatio = float64(currentDist) / float64(totalDist) * 0.6
}
diff := weightRatio - connRatio - geoRatio
multiple := diff * float64(float64(totalConns)+totalDist)
floor := math.Floor(multiple)
if floor-multiple >= -0.5 {
w.currentWeight = int64(fixedWeight + floor)
} else {
w.currentWeight = int64(fixedWeight + math.Ceil(multiple))
}
if diff < 0 {
// we always return the max from minWeight and calculated Current weight
if _minWeight > w.currentWeight {
w.currentWeight = _minWeight
}
} else {
// we always return the min from maxWeight and calculated Current weight
if _maxWeight < w.currentWeight {
w.currentWeight = _maxWeight
}
}
} else {
w.reset()
}
}
func (w *weightedNode) calcuateWeightByConns(totalWeight, totalConns, currentConns int64, gainWeight float64) {
fixedWeight := float64(w.fixedWeight) * gainWeight
totalWeight += int64(fixedWeight) - w.fixedWeight
if totalConns > 0 {
var (
connRatio float64
weightRatio = fixedWeight / float64(totalWeight)
)
if totalConns > 0 {
connRatio = float64(currentConns) / float64(totalConns)
}
diff := weightRatio - connRatio
multiple := diff * float64(totalConns)
floor := math.Floor(multiple)
if floor-multiple >= -0.5 {
w.currentWeight = int64(fixedWeight + floor)
} else {
w.currentWeight = int64(fixedWeight + math.Ceil(multiple))
}
if diff < 0 {
// we always return the max from minWeight and calculated Current weight
if _minWeight > w.currentWeight {
w.currentWeight = _minWeight
}
} else {
// we always return the min from maxWeight and calculated Current weight
if _maxWeight < w.currentWeight {
w.currentWeight = _maxWeight
}
}
} else {
w.reset()
}
}
// LoadBalancer load balancer.
type LoadBalancer struct {
totalConns int64
totalWeight int64
nodes map[string]*weightedNode
nodesMutex sync.Mutex
}
// NewLoadBalancer new a load balancer.
func NewLoadBalancer() *LoadBalancer {
lb := &LoadBalancer{
nodes: make(map[string]*weightedNode),
}
return lb
}
// Size return node size.
func (lb *LoadBalancer) Size() int {
return len(lb.nodes)
}
func (lb *LoadBalancer) weightedByGeo(lat, lng float64) (nodes []*weightedNode) {
var totalDist float64
for _, n := range lb.nodes {
if n.lat != 0 && n.lng != 0 {
totalDist += distance(n.lat, n.lng, lat, lng)
}
}
for _, n := range lb.nodes {
if n.lat != 0 && n.lng != 0 {
n.calcuateWeightByGeo(lb.totalWeight, lb.totalConns, n.currentConns, totalDist, distance(n.lat, n.lng, lat, lng))
nodes = append(nodes, n)
}
}
sort.Slice(nodes, func(i, j int) bool {
return nodes[i].currentWeight > nodes[j].currentWeight
})
if len(nodes) > 0 {
nodes[0].chosen()
lb.totalConns++
}
return
}
func (lb *LoadBalancer) weightedByConns(region string, regionWeight float64) (nodes []*weightedNode) {
for _, n := range lb.nodes {
var gainWeight = 1.0
if n.region == region {
gainWeight *= regionWeight
}
n.calcuateWeightByConns(lb.totalWeight, lb.totalConns, n.currentConns, gainWeight)
nodes = append(nodes, n)
}
sort.Slice(nodes, func(i, j int) bool {
return nodes[i].currentWeight > nodes[j].currentWeight
})
if len(nodes) > 0 {
nodes[0].chosen()
lb.totalConns++
}
return
}
// NodeDetails return nodes.
func (lb *LoadBalancer) NodeDetails(region string, regionWeight float64) (res []map[string]interface{}) {
lb.nodesMutex.Lock()
nodes := lb.weightedByConns(region, regionWeight)
res = make([]map[string]interface{}, 0, len(nodes))
for _, n := range nodes {
r := map[string]interface{}{
"region": n.region,
"hostname": n.hostname,
"fixedWeight": n.fixedWeight,
"currentWeight": n.currentWeight,
"currentConns": n.currentConns,
"addrs": n.addrs,
"updated": n.updated,
}
res = append(res, r)
}
lb.nodesMutex.Unlock()
return
}
// NodeAddrsByRegion return node addrs.
func (lb *LoadBalancer) NodeAddrsByRegion(region, domain string, regionWeight float64, overseas, ipv6 bool) (domains, addrs []string) {
lb.nodesMutex.Lock()
nodes := lb.weightedByConns(region, regionWeight)
lb.nodesMutex.Unlock()
for _, n := range nodes {
if n.overseas && !overseas {
continue
}
if !ipv6 && len(n.addrs) > 0 {
// ipv4
domains = append(domains, n.hostname+domain)
addrs = append(addrs, n.addrs...)
} else if ipv6 && len(n.addrsV6) > 0 {
// ipv6
domains = append(domains, n.hostname+domain)
addrs = append(addrs, n.addrsV6...)
}
if len(addrs) == _maxNodes {
break
}
}
return
}
// NodeAddrsByGeo return node addrs by geo.
func (lb *LoadBalancer) NodeAddrsByGeo(domain string, lat, lng float64, overseas, ipv6 bool) (domains, addrs []string) {
lb.nodesMutex.Lock()
nodes := lb.weightedByGeo(lat, lng)
lb.nodesMutex.Unlock()
for _, n := range nodes {
if n.overseas && !overseas {
continue
}
if !ipv6 && len(n.addrs) > 0 {
// ipv4
domains = append(domains, n.hostname+domain)
addrs = append(addrs, n.addrs...)
} else if ipv6 && len(n.addrsV6) > 0 {
// ipv6
domains = append(domains, n.hostname+domain)
addrs = append(addrs, n.addrsV6...)
}
if len(addrs) == _maxNodes {
break
}
}
return
}
// Update update server nodes.
func (lb *LoadBalancer) Update(srvs []*model.ServerInfo) {
var (
totalConns int64
totalWeight int64
nodes = make(map[string]*weightedNode, len(srvs))
)
if len(srvs) == 0 || float32(len(srvs))/float32(len(lb.nodes)) < 0.5 {
log.Error("load balancer update src:%d target:%d less than half", len(lb.nodes), len(srvs))
return
}
lb.nodesMutex.Lock()
for _, s := range srvs {
if old, ok := lb.nodes[s.Server]; ok && old.updated == s.Updated {
nodes[s.Server] = old
totalConns += old.currentConns
totalWeight += old.fixedWeight
} else {
node := &weightedNode{
region: s.Region,
hostname: s.Server,
fixedWeight: int64(s.Weight),
currentConns: int64(s.ConnCount),
addrs: s.IPAddrs,
addrsV6: s.IPAddrsV6,
updated: s.Updated,
lat: s.Latitude,
lng: s.Longitude,
overseas: s.Overseas,
}
nodes[s.Server] = node
totalConns += int64(s.ConnCount)
totalWeight += int64(s.Weight)
}
}
lb.nodes = nodes
lb.totalConns = totalConns
lb.totalWeight = totalWeight
lb.nodesMutex.Unlock()
}

View File

@@ -0,0 +1,159 @@
package service
import (
"sort"
"testing"
"go-common/app/service/main/broadcast/model"
)
func TestCurrentWeightByConns(t *testing.T) {
nodes := []*weightedNode{
{fixedWeight: 10, currentWeight: 0, currentConns: 100000},
{fixedWeight: 10, currentWeight: 0, currentConns: 100000},
{fixedWeight: 10, currentWeight: 0, currentConns: 100000},
}
totalWeight := nodes[0].fixedWeight + nodes[1].fixedWeight + nodes[2].fixedWeight
totalConns := nodes[0].currentConns + nodes[1].currentConns + nodes[2].currentConns
for i := 0; i < 1000000; i++ {
for _, n := range nodes {
n.calcuateWeightByConns(totalWeight, totalConns, n.currentConns, 1.0)
}
sort.Slice(nodes, func(i, j int) bool {
return nodes[i].currentWeight > nodes[j].currentWeight
})
nodes[0].chosen()
totalConns++
}
ft := float64(nodes[0].fixedWeight + nodes[1].fixedWeight + nodes[2].fixedWeight)
ct := float64(nodes[0].currentConns + nodes[1].currentConns + nodes[2].currentConns)
for _, n := range nodes {
k, j := float64(n.fixedWeight)/ft*10, float64(n.currentConns)/ct*10
if j/k < 1/3 {
t.Errorf("unmatch %d:%d", int(k), int(j))
}
t.Logf("node:%+v ratio %d:%d", n, int(k), int(j))
}
}
func TestFixedWeightByConns(t *testing.T) {
nodes := []*weightedNode{
{fixedWeight: 1, currentWeight: 0, currentConns: 0},
{fixedWeight: 2, currentWeight: 0, currentConns: 0},
{fixedWeight: 3, currentWeight: 0, currentConns: 0},
}
totalWeight := nodes[0].fixedWeight + nodes[1].fixedWeight + nodes[2].fixedWeight
totalConns := nodes[0].currentConns + nodes[1].currentConns + nodes[2].currentConns
for i := 0; i < 1000000; i++ {
for _, n := range nodes {
n.calcuateWeightByConns(totalWeight, totalConns, n.currentConns, 1.0)
}
sort.Slice(nodes, func(i, j int) bool {
return nodes[i].currentWeight > nodes[j].currentWeight
})
nodes[0].chosen()
totalConns++
}
ft := float64(nodes[0].fixedWeight + nodes[1].fixedWeight + nodes[2].fixedWeight)
ct := float64(nodes[0].currentConns + nodes[1].currentConns + nodes[2].currentConns)
for _, n := range nodes {
k, j := float64(n.fixedWeight)/ft*10, float64(n.currentConns)/ct*10
if j/k < 1/3 {
t.Errorf("unmatch %d:%d", int(k), int(j))
}
t.Logf("node:%+v ratio %d:%d", n, int(k), int(j))
}
}
func TestCurrentWeightByGeo(t *testing.T) {
nodes := []*weightedNode{
{region: "bj", fixedWeight: 10, currentWeight: 0, currentConns: 0, lat: 39.904989, lng: 116.405285},
{region: "sh", fixedWeight: 10, currentWeight: 0, currentConns: 0, lat: 31.231706, lng: 121.472644},
{region: "gz", fixedWeight: 10, currentWeight: 0, currentConns: 0, lat: 23.125178, lng: 113.280637},
}
totalWeight := nodes[0].fixedWeight + nodes[1].fixedWeight + nodes[2].fixedWeight
totalConns := nodes[0].currentConns + nodes[1].currentConns + nodes[2].currentConns
lat, lng := float64(31), float64(121)
for i := 0; i < 1000000; i++ {
var totalDist float64
for _, n := range nodes {
if n.lat != 0 && n.lng != 0 {
totalDist += distance(n.lat, n.lng, lat, lng)
}
}
for _, n := range nodes {
n.calcuateWeightByGeo(totalWeight, totalConns, n.currentConns, totalDist, distance(n.lat, n.lng, lat, lng))
}
sort.Slice(nodes, func(i, j int) bool {
return nodes[i].currentWeight > nodes[j].currentWeight
})
nodes[0].chosen()
totalConns++
}
ft := float64(nodes[0].fixedWeight + nodes[1].fixedWeight + nodes[2].fixedWeight)
ct := float64(nodes[0].currentConns + nodes[1].currentConns + nodes[2].currentConns)
for _, n := range nodes {
k, j := float64(n.fixedWeight)/ft*10, float64(n.currentConns)/ct*10
if j/k < 1/3 {
t.Errorf("unmatch %d:%d", int(k), int(j))
}
t.Logf("node:%+v ratio %d:%d", n, int(k), int(j))
}
}
func TestLoadBalancer(t *testing.T) {
var (
ss = []*model.ServerInfo{
{
Region: "bj",
Server: "01",
Weight: 10,
ConnCount: 300000,
IPCount: 100000,
IPAddrs: []string{"ip_bj"},
IPAddrsV6: []string{"ip_bj_v6"},
Latitude: 39.904989,
Longitude: 116.405285,
},
{
Region: "sh",
Server: "02",
Weight: 10,
ConnCount: 300000,
IPCount: 10,
IPAddrs: []string{"ip_sh"},
IPAddrsV6: []string{"ip_sh_v6"},
Latitude: 31.231706,
Longitude: 121.472644,
},
{
Region: "gz",
Server: "03",
Weight: 10,
ConnCount: 300000,
IPCount: 100000,
IPAddrs: []string{"ip_gz"},
IPAddrsV6: []string{"ip_gz_v6"},
Latitude: 23.125178,
Longitude: 113.280637,
Overseas: true,
},
}
)
lb := NewLoadBalancer()
lb.Update(ss)
for i := 0; i < 10; i++ {
t.Log(lb.NodeAddrsByGeo(".text", 39, 116, false, false))
t.Log(lb.NodeAddrsByGeo(".text", 31, 121, false, false))
t.Log(lb.NodeAddrsByGeo(".text", 23, 121, true, false))
}
t.Log(lb.nodes)
for i := 0; i < 10; i++ {
t.Log(lb.NodeAddrsByRegion("sh", ".test", 1.5, false, false))
}
t.Log(lb.nodes)
for i := 0; i < 10; i++ {
t.Log(lb.NodeAddrsByRegion("sh", ".test", 1.5, true, true))
}
t.Log(lb.nodes)
}

View File

@@ -0,0 +1,130 @@
package service
import (
"context"
"encoding/json"
"fmt"
"net/url"
"time"
"go-common/app/service/main/broadcast/model"
identify "go-common/app/service/main/identify/api/grpc"
"go-common/library/ecode"
"go-common/library/log"
"github.com/zhenjl/cityhash"
)
const (
roomsShard = 32
)
// Connect connect a conn.
func (s *Service) Connect(c context.Context, server, serverKey, cookie string, token []byte) (mid int64, key, roomID string, paltform string, accepts []int32, err error) {
var auth model.AuthToken
if err = json.Unmarshal(token, &auth); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", token, err)
return
}
accepts = auth.Accepts
paltform = auth.Platform
key = serverKey
// 兼容goim-chat
if auth.Aid != 0 && auth.Cid != 0 {
roomID = fmt.Sprintf("video://%d_%d", auth.Aid, auth.Cid)
return
}
if auth.DeviceID != "" {
key = auth.DeviceID
}
// new auth
if auth.RoomID != "" {
var u *url.URL
if u, err = url.Parse(auth.RoomID); err != nil || u.Scheme == "" {
err = ecode.RequestErr
log.Error("url.Parse(%s) error(%v)", auth.RoomID, err)
return
}
roomID = auth.RoomID
}
if auth.AccessKey != "" {
info, err1 := s.identifyCli.GetTokenInfo(c, &identify.GetTokenInfoReq{Token: auth.AccessKey})
if err1 == nil {
mid = info.Mid
} else {
log.Error("s.identifyCli.GetTokenInfo(%s) failed!err:=%v", auth.AccessKey, err)
}
} else if cookie != "" {
info, err1 := s.identifyCli.GetCookieInfo(c, &identify.GetCookieInfoReq{Cookie: cookie})
if err1 == nil {
mid = info.Mid
} else {
log.Error("s.identifyCli.GetCookieInfo(%s) failed!err:=%v", cookie, err)
}
}
if mid > 0 {
s.cache.Save(func() {
if err1 := s.dao.AddMapping(context.Background(), mid, key, server); err1 != nil {
log.Error("s.dao.AddMapping(%d,%s,%s) error(%v)", mid, key, server, err1)
}
})
}
log.Info("conn connected key:%s server:%s mid:%d token:%s", key, server, mid, token)
return
}
// Disconnect disconnect a conn.
func (s *Service) Disconnect(c context.Context, mid int64, key, server string) (has bool, err error) {
if has, err = s.dao.DelMapping(c, mid, key, server); err != nil {
log.Error("s.dao.DelMapping(%d,%s,%s) error(%v)", mid, key, server, err)
return
}
log.Info("conn disconnected key:%s server:%s mid:%d", key, server, mid)
return
}
// Heartbeat heartbeat a conn.
func (s *Service) Heartbeat(c context.Context, mid int64, key, server string) (err error) {
has, err := s.dao.ExpireMapping(c, mid, key)
if err != nil {
log.Error("s.dao.ExpireMapping(%d,%s,%s) error(%v)", mid, key, server, err)
return
}
if !has {
s.cache.Save(func() {
if err1 := s.dao.AddMapping(context.Background(), mid, key, server); err1 != nil {
log.Error("s.dao.AddMapping(%d,%s,%s) error(%v)", mid, key, server, err1)
return
}
})
}
log.Info("conn heartbeat key:%s server:%s mid:%d", key, server, mid)
return
}
// RenewOnline renew a server online.
func (s *Service) RenewOnline(c context.Context, server string, shard int32, roomCount map[string]int32) (mergedRoomCount map[string]int32, err error) {
online := &model.Online{
Server: server,
RoomCount: roomCount,
Updated: time.Now().Unix(),
}
mergedRoomCount = make(map[string]int32)
for roomID, count := range s.roomCount {
hash := cityhash.CityHash32([]byte(roomID), uint32(len(roomID))) % roomsShard
if hash == uint32(shard) {
mergedRoomCount[roomID] = count
}
}
if err := s.dao.AddServerOnline(c, server, shard, online); err != nil {
log.Error("s.dao.AddServerOnline(%s %d %d) merged:%d error(%v)", server, shard, len(roomCount), len(mergedRoomCount), err)
}
return
}
// Receive receive a message.
func (s *Service) Receive(c context.Context, mid int64, proto *model.Proto) (err error) {
// TODO upstream message
log.Info("conn receive a message mid:%d proto:%+v", mid, proto)
return
}

View File

@@ -0,0 +1,33 @@
package service
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestConnect(t *testing.T) {
var (
server = "test_server"
serverKey = "test_server_key"
token = []byte(`{"device_id":"test_server_key","business":"dm", "room_id":"test://test_room", "accepts":[1,2,3]}`)
c = context.Background()
)
Convey("connect", t, WithService(func(s *Service) {
// connect
mid, key, roomID, _, accepts, err := s.Connect(c, server, serverKey, "", token)
So(err, ShouldBeNil)
So(key, ShouldEqual, serverKey)
So(roomID, ShouldEqual, "test://test_room")
So(len(accepts), ShouldEqual, 3)
t.Log(mid, key, roomID, accepts, err)
// heartbeat
err = s.Heartbeat(c, mid, key, server)
So(err, ShouldBeNil)
// disconnect
has, err := s.Disconnect(c, mid, key, server)
So(err, ShouldBeNil)
So(has, ShouldEqual, true)
}))
}

View File

@@ -0,0 +1,54 @@
package service
import (
"context"
"sort"
"strings"
"go-common/app/service/main/broadcast/model"
)
var (
_emptyTops = make([]*model.Top, 0)
)
// OnlineTop get the top online.
func (s *Service) OnlineTop(c context.Context, business string, n int) (tops []*model.Top, err error) {
for roomKey, cnt := range s.roomCount {
if strings.HasPrefix(roomKey, business) {
_, roomID, err := model.DecodeRoomKey(roomKey)
if err != nil {
continue
}
top := &model.Top{
RoomID: roomID,
Count: cnt,
}
tops = append(tops, top)
}
}
sort.Slice(tops, func(i, j int) bool {
return tops[i].Count > tops[j].Count
})
if len(tops) > n {
tops = tops[:n]
}
if len(tops) == 0 {
tops = _emptyTops
}
return
}
// OnlineRoom get rooms online.
func (s *Service) OnlineRoom(c context.Context, business string, rooms []string) (res map[string]int32, err error) {
res = make(map[string]int32, len(rooms))
for _, roomID := range rooms {
res[roomID] = s.roomCount[model.EncodeRoomKey(business, roomID)]
}
return
}
// OnlineTotal get all online.
func (s *Service) OnlineTotal(c context.Context) (int64, int64) {
return s.totalIPs, s.totalConns
}

View File

@@ -0,0 +1,86 @@
package service
import (
"context"
"time"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/xstr"
)
const (
_maxPushBatch = 200
)
// PushKeys push a message by keys.
func (s *Service) PushKeys(c context.Context, op int32, keys []string, msg string, contentType int32) (err error) {
if len(keys) > _maxPushBatch {
err = ecode.LimitExceed
return
}
servers, err := s.dao.ServersByKeys(c, keys)
if err != nil {
return
}
pushKeys := make(map[string][]string)
for i, key := range keys {
server := servers[i]
if server != "" && key != "" {
pushKeys[server] = append(pushKeys[server], key)
}
}
for server := range pushKeys {
if err = s.dao.PushMsg(c, op, server, msg, pushKeys[server], contentType); err != nil {
return
}
}
return
}
// PushMids push a message by mid.
func (s *Service) PushMids(c context.Context, op int32, mids []int64, msg string, contentType int32) (err error) {
if len(mids) > _maxPushBatch {
err = ecode.LimitExceed
return
}
now := time.Now().Unix()
s.stats.Info("push_mids", op, xstr.JoinInts(mids), "", "", len(mids), now)
keyServers, olMids, err := s.dao.KeysByMids(c, mids)
if err != nil {
return
}
keys := make(map[string][]string)
for key, server := range keyServers {
if key != "" && server != "" {
keys[server] = append(keys[server], key)
} else {
log.Warn("push key:%s server:%s is empty", key, server)
}
}
for server, keys := range keys {
if err = s.dao.PushMsg(c, op, server, msg, keys, contentType); err != nil {
return
}
}
s.stats.Info("push_mids_ol", op, xstr.JoinInts(olMids), "", "", len(olMids), now)
return
}
// PushRoom push a message by room.
func (s *Service) PushRoom(c context.Context, op int32, room, msg string, contentType int32) (err error) {
if err = s.dao.BroadcastRoomMsg(c, op, room, msg, contentType); err != nil {
return
}
s.stats.Info("push_room", op, "", room, "", s.roomCount[room], time.Now().Unix())
return
}
// PushAll push a message to all.
func (s *Service) PushAll(c context.Context, op, speed int32, msg, platform string, contentType int32) (err error) {
if err = s.dao.BroadcastMsg(c, op, speed, msg, platform, contentType); err != nil {
return
}
s.stats.Info("push_all", op, "", "", platform, 0, time.Now().Unix())
return
}

View File

@@ -0,0 +1,128 @@
package service
import (
"context"
"time"
pb "go-common/app/service/main/broadcast/api/grpc/v1"
"go-common/app/service/main/broadcast/model"
location "go-common/app/service/main/location/api"
"go-common/library/log"
"go-common/library/net/metadata"
xtime "go-common/library/time"
)
// ServerBackoff server backoff config.
type ServerBackoff struct {
MaxDelay int32
BaseDelay int32
Factor float32
Jitter float32
}
// ServerConfig server config.
type ServerConfig struct {
Domain string
HostDomain string
TCPPort int
WSPort int
WSSPort int
HeartbeatMax int
Heartbeat xtime.Duration
RegionWeight float64
}
func isIPV6(s string) bool {
for i := 0; i < len(s); i++ {
switch s[i] {
case '.':
return false
case ':':
return true
}
}
return false
}
// ServerInfos get servers info.
func (s *Service) ServerInfos(c context.Context) (res []*model.ServerInfo, err error) {
return s.servers, nil
}
// ServerList get server list.
func (s *Service) ServerList(c context.Context, platform string) *pb.ServerListReply {
reply := &pb.ServerListReply{
Domain: s.srvConf.Domain,
TcpPort: int32(s.srvConf.TCPPort),
WsPort: int32(s.srvConf.WSPort),
WssPort: int32(s.srvConf.WSSPort),
Heartbeat: int32(time.Duration(s.srvConf.Heartbeat) / time.Second),
HeartbeatMax: int32(s.srvConf.HeartbeatMax),
Backoff: &pb.Backoff{
MaxDelay: s.srvBackoff.MaxDelay,
BaseDelay: s.srvBackoff.BaseDelay,
Factor: s.srvBackoff.Factor,
Jitter: s.srvBackoff.Jitter,
},
}
domains, addrs := s.nodeAddrs(c)
if platform == "web" {
reply.Nodes = domains
// FIXME cross domain
reply.Nodes = []string{s.srvConf.Domain}
} else {
reply.Nodes = addrs
}
if len(reply.Nodes) == 0 {
reply.Nodes = []string{s.srvConf.Domain}
}
return reply
}
// ServerWeight server node details.
func (s *Service) ServerWeight(c context.Context, clientIP string) (interface{}, string, string, error) {
var (
region string
province string
)
if clientIP != "" {
req := &location.InfoReq{Addr: clientIP}
resp, err := s.locationCli.Info(c, req)
if err != nil {
return nil, "", "", err
}
province = resp.Province
region = s.regions[province]
}
return s.loadBalancer.NodeDetails(region, s.srvConf.RegionWeight), region, province, nil
}
func (s *Service) nodeAddrs(c context.Context) (domains, addrs []string) {
var (
country string
region string
province string
lat, lng float64
overseas bool
clientIP = metadata.String(c, metadata.RemoteIP)
)
req := &location.InfoReq{Addr: clientIP}
resp, err := s.locationCli.Info(c, req)
if err == nil {
region = s.regions[resp.Province]
country = resp.Country
province = resp.Province
lat, lng = resp.Latitude, resp.Longitude
// 海外节点判断
if (country != "" && country != "中国" && country != "局域网") || province == "香港" || province == "澳门" || province == "台湾" {
overseas = true
}
}
if region != "" || lat == 0 || lng == 0 {
domains, addrs = s.loadBalancer.NodeAddrsByRegion(region, s.srvConf.HostDomain, s.srvConf.RegionWeight, overseas, isIPV6(clientIP))
} else {
domains, addrs = s.loadBalancer.NodeAddrsByGeo(s.srvConf.HostDomain, lat, lng, overseas, isIPV6(clientIP))
}
log.Info("nodeAddrs clientIP:%s overseas:%t region:%s country:%s province:%s lat:%6f lng:%6f domains:%v addrs:%v", clientIP, overseas, region, country, province, lat, lng, domains, addrs)
return
}

View File

@@ -0,0 +1,313 @@
package service
import (
"context"
"strconv"
"strings"
"time"
"go-common/app/service/main/broadcast/dao"
"go-common/app/service/main/broadcast/model"
identify "go-common/app/service/main/identify/api/grpc"
location "go-common/app/service/main/location/api"
"go-common/library/cache"
"go-common/library/conf/paladin"
"go-common/library/log"
"go-common/library/log/infoc"
"go-common/library/naming"
"go-common/library/naming/discovery"
"go-common/library/net/netutil"
"go-common/library/net/rpc/warden"
"go-common/library/net/rpc/warden/balancer/wrr"
"google.golang.org/grpc"
)
const (
_onlineTick = time.Second * 10
_onlineDeadline = time.Minute * 5
)
var (
_olBackoff = &netutil.BackoffConfig{
MaxDelay: 5 * time.Second,
BaseDelay: 1.0 * time.Second,
Factor: 1.6,
Jitter: 0.2,
}
)
// Service struct
type Service struct {
c *paladin.Map
dao *dao.Dao
cache *cache.Cache
dis *discovery.Discovery
// online
totalIPs int64
totalConns int64
roomCount map[string]int32
// client
identifyCli identify.IdentifyClient
locationCli location.LocationClient
servers []*model.ServerInfo
loadBalancer *LoadBalancer
regions map[string]string // province -> region
// infoc
stats *infoc.Infoc
srvBackoff *ServerBackoff
srvConf *ServerConfig
}
func checkErr(err error) {
if err != nil {
panic(err)
}
}
// New new a service and return.
func New(dis *discovery.Discovery) (s *Service) {
var (
appConf = new(paladin.TOML)
srvConf = new(ServerConfig)
srvBackoff = new(ServerBackoff)
regions map[string][]string
infocConf struct {
Stats *infoc.Config
}
grpcConf struct {
Client *warden.ClientConfig
}
)
checkErr(paladin.Watch("application.toml", appConf))
checkErr(appConf.Get("server").UnmarshalTOML(srvConf))
checkErr(appConf.Get("regions").UnmarshalTOML(&regions))
checkErr(appConf.Get("backoff").UnmarshalTOML(srvBackoff))
checkErr(paladin.Get("infoc.toml").UnmarshalTOML(&infocConf))
if err := paladin.Get("grpc.toml").UnmarshalTOML(&grpcConf); err != nil {
if err != paladin.ErrNotExist {
panic(err)
}
}
idtv1, err := identify.NewClient(grpcConf.Client, grpc.WithBalancerName(wrr.Name))
if err != nil {
panic(err)
}
locConn, err := warden.NewClient(grpcConf.Client).Dial(context.Background(), "discovery://default/location.service")
if err != nil {
panic(err)
}
s = &Service{
c: appConf,
srvConf: srvConf,
srvBackoff: srvBackoff,
dis: dis,
dao: dao.New(),
cache: cache.New(64, 10240),
stats: infoc.New(infocConf.Stats),
identifyCli: idtv1,
locationCli: location.NewLocationClient(locConn),
loadBalancer: NewLoadBalancer(),
regions: make(map[string]string),
}
s.initRegions(regions)
s.initServer()
s.loadOnline()
go s.onlineproc()
return s
}
func (s *Service) initRegions(regions map[string][]string) {
for region, ps := range regions {
for _, province := range ps {
s.regions[province] = region
}
}
}
func (s *Service) initServer() {
res := s.dis.Build("push.interface.broadcast")
event := res.Watch()
select {
case _, ok := <-event:
if ok {
s.newServers(res)
} else {
panic("discovery watch failed")
}
case <-time.After(10 * time.Second):
log.Error("discovery start timeout")
}
go func() {
for {
if _, ok := <-event; !ok {
return
}
s.newServers(res)
}
}()
}
func (s *Service) newServers(res naming.Resolver) {
if zoneIns, ok := res.Fetch(context.Background()); ok {
var (
totalConns int64
totalIPs int64
srvs []*model.ServerInfo
)
for _, zins := range zoneIns {
for _, in := range zins {
if in.Metadata == nil {
log.Error("instance metadata is empty(%+v)", in)
continue
}
if ok, _ := strconv.ParseBool(in.Metadata["offline"]); ok {
continue
}
conns, err := strconv.ParseInt(in.Metadata["conns"], 10, 32)
if err != nil {
log.Error("strconv.ParseInt(conns:%d) error(%v)", conns, err)
continue
}
ips, err := strconv.ParseInt(in.Metadata["ips"], 10, 32)
if err != nil {
log.Error("strconv.ParseInt(ips:%d) error(%v)", ips, err)
continue
}
roomIPs, err := strconv.ParseInt(in.Metadata["room_ips"], 10, 32)
if err != nil {
log.Error("strconv.ParseInt(room_ips:%d) error(%v)", ips, err)
continue
}
weight, err := strconv.ParseInt(in.Metadata["weight"], 10, 32)
if err != nil {
log.Error("strconv.ParseInt(weight:%d) error(%v)", conns, err)
continue
}
var (
addrs []string
addrsV6 []string
lat, lng float64
)
if in.Metadata["ip_addrs"] != "" {
addrs = strings.Split(in.Metadata["ip_addrs"], ",")
}
if in.Metadata["ip_addrs_v6"] != "" {
addrsV6 = strings.Split(in.Metadata["ip_addrs_v6"], ",")
}
for _, addr := range addrs {
resp, err := s.locationCli.Info(context.Background(), &location.InfoReq{Addr: addr})
if err == nil {
lat, lng = resp.Latitude, resp.Longitude
break
}
}
overseas, _ := strconv.ParseBool(in.Metadata["overseas"])
srvs = append(srvs, &model.ServerInfo{
Region: in.Region,
Server: in.Hostname,
ConnCount: int32(conns),
IPCount: int32(ips),
RoomIPCount: int32(roomIPs),
Weight: int32(weight),
IPAddrs: addrs,
IPAddrsV6: addrsV6,
Updated: in.LastTs,
Latitude: lat,
Longitude: lng,
Overseas: overseas,
})
totalConns += conns
totalIPs += ips
}
}
s.totalConns = totalConns
s.totalIPs = totalIPs
s.servers = srvs
s.loadBalancer.Update(srvs)
if ok, _ := s.c.Get("saveServers").Bool(); ok {
s.dao.SetServers(context.Background(), srvs)
log.Info("set servers:%d", len(srvs))
}
}
}
// Ping Service.
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}
// Close Service.
func (s *Service) Close() {
s.dao.Close()
}
func (s *Service) onlineproc() {
var retry int
for {
if err := s.loadOnline(); err != nil {
retry++
time.Sleep(_olBackoff.Backoff(retry))
continue
}
retry = 0
time.Sleep(_onlineTick)
}
}
func (s *Service) loadOnline() (err error) {
const pullRetries = 3
var roomCount = make(map[string]int32)
srvs, err := s.dao.Servers(context.Background())
if err != nil {
log.Error("s.dao.Servers() error(%v)", err)
return
}
for _, server := range srvs {
for i := 0; i < roomsShard; i++ {
var online *model.Online
for r := 0; r < pullRetries; r++ {
if online, err = s.dao.ServerOnline(context.Background(), server.Server, i); err != nil {
log.Error("s.dao.ServerOnline(%s, %d) retries:%d error(%v)", server.Server, i, r, err)
time.Sleep(_olBackoff.Backoff(r))
continue
}
break
}
if err != nil {
return
}
if online == nil {
continue
}
if time.Since(time.Unix(online.Updated, 0)) > _onlineDeadline {
s.dao.DelServerOnline(context.Background(), server.Server, i)
continue
}
for roomID, count := range online.RoomCount {
roomCount[roomID] += count
}
}
}
// FIXME migrate rooms count
for i := 0; i < roomsShard; i++ {
var rooms map[string]int32
for r := 0; r < pullRetries; r++ {
if rooms, err = s.dao.MigrateRooms(context.Background(), i); err != nil {
log.Error("s.dao.MigrateRooms(%d) retries:%d error(%v)", i, r, err)
time.Sleep(_olBackoff.Backoff(r))
continue
}
break
}
if err != nil {
return
}
for rid, cnt := range rooms {
roomCount[rid] += cnt
}
}
s.roomCount = roomCount
log.Info("loadOnline rooms:%d", len(roomCount))
return
}

View File

@@ -0,0 +1,39 @@
package service
import (
"flag"
"path/filepath"
"time"
"go-common/library/conf/paladin"
"go-common/library/naming/discovery"
. "github.com/smartystreets/goconvey/convey"
)
var (
s *Service
)
func init() {
dir, _ := filepath.Abs("../cmd/local")
flag.Set("conf", dir)
if err := paladin.Init(); err != nil {
panic(err)
}
s = New(discovery.New(nil))
time.Sleep(time.Second)
}
func CleanCache() {
//c := context.TODO()
//pool := redis.NewPool(conf.Conf.Redis.Config)
//pool.Get(c).Do("FLUSHDB")
}
func WithService(f func(s *Service)) func() {
return func() {
Reset(func() { CleanCache() })
f(s)
}
}