feat: 基本完成TCP/WS端,tcp包协议,数据包协议
@ -0,0 +1,13 @@
|
||||
Copyright [2022-2025] [NorthLan]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -0,0 +1 @@
|
||||
protoc --go_opt=paths=source_relative --go_out=. --proto_path=. *.proto
|
@ -0,0 +1 @@
|
||||
./protoc --go_out=. *.proto
|
@ -0,0 +1,203 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.0
|
||||
// protoc v3.19.4
|
||||
// source: test.proto
|
||||
|
||||
package testdata
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type Ping struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Content string `protobuf:"bytes,1,opt,name=Content,proto3" json:"Content,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Ping) Reset() {
|
||||
*x = Ping{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_test_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Ping) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Ping) ProtoMessage() {}
|
||||
|
||||
func (x *Ping) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_test_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Ping.ProtoReflect.Descriptor instead.
|
||||
func (*Ping) Descriptor() ([]byte, []int) {
|
||||
return file_test_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Ping) GetContent() string {
|
||||
if x != nil {
|
||||
return x.Content
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Pong struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Content string `protobuf:"bytes,2,opt,name=Content,proto3" json:"Content,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Pong) Reset() {
|
||||
*x = Pong{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_test_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Pong) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Pong) ProtoMessage() {}
|
||||
|
||||
func (x *Pong) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_test_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Pong.ProtoReflect.Descriptor instead.
|
||||
func (*Pong) Descriptor() ([]byte, []int) {
|
||||
return file_test_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *Pong) GetContent() string {
|
||||
if x != nil {
|
||||
return x.Content
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_test_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_test_proto_rawDesc = []byte{
|
||||
0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x74, 0x65,
|
||||
0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x22, 0x20, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x18,
|
||||
0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x20, 0x0a, 0x04, 0x50, 0x6f, 0x6e, 0x67,
|
||||
0x12, 0x18, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x42, 0x0b, 0x5a, 0x09, 0x2f, 0x74,
|
||||
0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_test_proto_rawDescOnce sync.Once
|
||||
file_test_proto_rawDescData = file_test_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_test_proto_rawDescGZIP() []byte {
|
||||
file_test_proto_rawDescOnce.Do(func() {
|
||||
file_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData)
|
||||
})
|
||||
return file_test_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_test_proto_goTypes = []interface{}{
|
||||
(*Ping)(nil), // 0: testdata.Ping
|
||||
(*Pong)(nil), // 1: testdata.Pong
|
||||
}
|
||||
var file_test_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_test_proto_init() }
|
||||
func file_test_proto_init() {
|
||||
if File_test_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Ping); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Pong); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_test_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_test_proto_goTypes,
|
||||
DependencyIndexes: file_test_proto_depIdxs,
|
||||
MessageInfos: file_test_proto_msgTypes,
|
||||
}.Build()
|
||||
File_test_proto = out.File
|
||||
file_test_proto_rawDesc = nil
|
||||
file_test_proto_goTypes = nil
|
||||
file_test_proto_depIdxs = nil
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package testdata;
|
||||
|
||||
option go_package = "/testdata";
|
||||
|
||||
message Ping {
|
||||
string Content = 1;
|
||||
}
|
||||
|
||||
message Pong {
|
||||
string Content = 2;
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"ngs/cluster/clusterpb"
|
||||
"ngs/internal/message"
|
||||
"ngs/mock"
|
||||
"ngs/session"
|
||||
)
|
||||
|
||||
type acceptor struct {
|
||||
sid int64
|
||||
gateClient clusterpb.MemberClient
|
||||
session *session.Session
|
||||
lastMid uint64
|
||||
rpcHandler rpcHandler
|
||||
gateAddr string
|
||||
}
|
||||
|
||||
// Push implements the session.NetworkEntity interface
|
||||
func (a *acceptor) Push(route string, v interface{}) error {
|
||||
// TODO: buffer
|
||||
data, err := message.Serialize(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request := &clusterpb.PushMessage{
|
||||
SessionId: a.sid,
|
||||
Route: route,
|
||||
Data: data,
|
||||
}
|
||||
_, err = a.gateClient.HandlePush(context.Background(), request)
|
||||
return err
|
||||
}
|
||||
|
||||
// RPC implements the session.NetworkEntity interface
|
||||
func (a *acceptor) RPC(route string, v interface{}) error {
|
||||
// TODO: buffer
|
||||
data, err := message.Serialize(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := &message.Message{
|
||||
Type: message.Notify,
|
||||
Route: route,
|
||||
Data: data,
|
||||
}
|
||||
a.rpcHandler(a.session, msg, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// LastMid implements the session.NetworkEntity interface
|
||||
func (a *acceptor) LastMid() uint64 {
|
||||
return a.lastMid
|
||||
}
|
||||
|
||||
// Response implements the session.NetworkEntity interface
|
||||
func (a *acceptor) Response(v interface{}) error {
|
||||
return a.ResponseMid(a.lastMid, v)
|
||||
}
|
||||
|
||||
// ResponseMid implements the session.NetworkEntity interface
|
||||
func (a *acceptor) ResponseMid(mid uint64, v interface{}) error {
|
||||
// TODO: buffer
|
||||
data, err := message.Serialize(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request := &clusterpb.ResponseMessage{
|
||||
SessionId: a.sid,
|
||||
Id: mid,
|
||||
Data: data,
|
||||
}
|
||||
_, err = a.gateClient.HandleResponse(context.Background(), request)
|
||||
return err
|
||||
}
|
||||
|
||||
// Close implements the session.NetworkEntity interface
|
||||
func (a *acceptor) Close() error {
|
||||
// TODO: buffer
|
||||
request := &clusterpb.CloseSessionRequest{
|
||||
SessionId: a.sid,
|
||||
}
|
||||
_, err := a.gateClient.CloseSession(context.Background(), request)
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoteAddr implements the session.NetworkEntity interface
|
||||
func (*acceptor) RemoteAddr() net.Addr {
|
||||
return mock.NetAddr{}
|
||||
}
|
@ -0,0 +1,299 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"ngs/internal/codec"
|
||||
"ngs/internal/env"
|
||||
"ngs/internal/log"
|
||||
"ngs/internal/message"
|
||||
"ngs/internal/packet"
|
||||
"ngs/pipeline"
|
||||
"ngs/scheduler"
|
||||
"ngs/session"
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
agentWriteBacklog = 16
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrBrokenPipe represents the low-level connection has broken.
|
||||
ErrBrokenPipe = errors.New("broken low-level pipe")
|
||||
// ErrBufferExceed indicates that the current session buffer is full and
|
||||
// can not receive more data.
|
||||
ErrBufferExceed = errors.New("session send buffer exceed")
|
||||
)
|
||||
|
||||
type (
|
||||
// Agent corresponding a user, used for store raw conn information
|
||||
agent struct {
|
||||
// regular agent member
|
||||
session *session.Session // session
|
||||
conn net.Conn // low-level conn fd
|
||||
lastMid uint64 // last message id
|
||||
state int32 // current agent state
|
||||
chDie chan struct{} // wait for close
|
||||
chSend chan pendingMessage // push message queue
|
||||
lastAt int64 // last heartbeat unix time stamp
|
||||
decoder *codec.Decoder // binary decoder
|
||||
pipeline pipeline.Pipeline
|
||||
|
||||
rpcHandler rpcHandler
|
||||
srv reflect.Value // cached session reflect.Value
|
||||
}
|
||||
|
||||
pendingMessage struct {
|
||||
typ message.Type // message type
|
||||
route string // message route(push)
|
||||
mid uint64 // response message id(response)
|
||||
payload interface{} // payload
|
||||
}
|
||||
)
|
||||
|
||||
// Create new agent instance
|
||||
func newAgent(conn net.Conn, pipeline pipeline.Pipeline, rpcHandler rpcHandler) *agent {
|
||||
a := &agent{
|
||||
conn: conn,
|
||||
state: statusStart,
|
||||
chDie: make(chan struct{}),
|
||||
lastAt: time.Now().Unix(),
|
||||
chSend: make(chan pendingMessage, agentWriteBacklog),
|
||||
decoder: codec.NewDecoder(),
|
||||
pipeline: pipeline,
|
||||
rpcHandler: rpcHandler,
|
||||
}
|
||||
|
||||
// binding session
|
||||
s := session.New(a)
|
||||
a.session = s
|
||||
a.srv = reflect.ValueOf(s)
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *agent) send(m pendingMessage) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = ErrBrokenPipe
|
||||
}
|
||||
}()
|
||||
a.chSend <- m
|
||||
return
|
||||
}
|
||||
|
||||
// LastMid implements the session.NetworkEntity interface
|
||||
func (a *agent) LastMid() uint64 {
|
||||
return a.lastMid
|
||||
}
|
||||
|
||||
// Push implementation for session.NetworkEntity interface
|
||||
func (a *agent) Push(route string, v interface{}) error {
|
||||
if a.status() == statusClosed {
|
||||
return ErrBrokenPipe
|
||||
}
|
||||
|
||||
if len(a.chSend) >= agentWriteBacklog {
|
||||
return ErrBufferExceed
|
||||
}
|
||||
|
||||
if env.Debug {
|
||||
switch d := v.(type) {
|
||||
case []byte:
|
||||
log.Println(fmt.Sprintf("Type=Push, ID=%d, UID=%d, Route=%s, Data=%dbytes",
|
||||
a.session.ID(), a.session.UID(), route, len(d)))
|
||||
default:
|
||||
log.Println(fmt.Sprintf("Type=Push, ID=%d, UID=%d, Route=%s, Data=%+v",
|
||||
a.session.ID(), a.session.UID(), route, v))
|
||||
}
|
||||
}
|
||||
|
||||
return a.send(pendingMessage{typ: message.Push, route: route, payload: v})
|
||||
}
|
||||
|
||||
// RPC implementation for session.NetworkEntity interface
|
||||
func (a *agent) RPC(route string, v interface{}) error {
|
||||
if a.status() == statusClosed {
|
||||
return ErrBrokenPipe
|
||||
}
|
||||
|
||||
// TODO: buffer
|
||||
data, err := message.Serialize(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := &message.Message{
|
||||
Type: message.Notify,
|
||||
Route: route,
|
||||
Data: data,
|
||||
}
|
||||
a.rpcHandler(a.session, msg, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Response implementation for session.NetworkEntity interface
|
||||
// Response message to session
|
||||
func (a *agent) Response(v interface{}) error {
|
||||
return a.ResponseMid(a.lastMid, v)
|
||||
}
|
||||
|
||||
// ResponseMid implementation for session.NetworkEntity interface
|
||||
// Response message to session
|
||||
func (a *agent) ResponseMid(mid uint64, v interface{}) error {
|
||||
if a.status() == statusClosed {
|
||||
return ErrBrokenPipe
|
||||
}
|
||||
|
||||
if mid <= 0 {
|
||||
return ErrSessionOnNotify
|
||||
}
|
||||
|
||||
if len(a.chSend) >= agentWriteBacklog {
|
||||
return ErrBufferExceed
|
||||
}
|
||||
|
||||
if env.Debug {
|
||||
switch d := v.(type) {
|
||||
case []byte:
|
||||
log.Println(fmt.Sprintf("Type=Response, ID=%d, UID=%d, MID=%d, Data=%dbytes",
|
||||
a.session.ID(), a.session.UID(), mid, len(d)))
|
||||
default:
|
||||
log.Println(fmt.Sprintf("Type=Response, ID=%d, UID=%d, MID=%d, Data=%+v",
|
||||
a.session.ID(), a.session.UID(), mid, v))
|
||||
}
|
||||
}
|
||||
|
||||
return a.send(pendingMessage{typ: message.Response, mid: mid, payload: v})
|
||||
}
|
||||
|
||||
// Close implementation for session.NetworkEntity interface
|
||||
// Close closes the agent, clean inner state and close low-level connection.
|
||||
// Any blocked Read or Write operations will be unblocked and return errors.
|
||||
func (a *agent) Close() error {
|
||||
if a.status() == statusClosed {
|
||||
return ErrCloseClosedSession
|
||||
}
|
||||
a.setStatus(statusClosed)
|
||||
|
||||
if env.Debug {
|
||||
log.Println(fmt.Sprintf("Session closed, ID=%d, UID=%d, IP=%s",
|
||||
a.session.ID(), a.session.UID(), a.conn.RemoteAddr()))
|
||||
}
|
||||
|
||||
// prevent closing closed channel
|
||||
select {
|
||||
case <-a.chDie:
|
||||
// expect
|
||||
default:
|
||||
close(a.chDie)
|
||||
scheduler.PushTask(func() { session.Lifetime.Close(a.session) })
|
||||
}
|
||||
|
||||
return a.conn.Close()
|
||||
}
|
||||
|
||||
// RemoteAddr implementation for session.NetworkEntity interface
|
||||
// returns the remote network address.
|
||||
func (a *agent) RemoteAddr() net.Addr {
|
||||
return a.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
// String, implementation for Stringer interface
|
||||
func (a *agent) String() string {
|
||||
return fmt.Sprintf("Remote=%s, LastTime=%d", a.conn.RemoteAddr().String(), atomic.LoadInt64(&a.lastAt))
|
||||
}
|
||||
|
||||
func (a *agent) status() int32 {
|
||||
return atomic.LoadInt32(&a.state)
|
||||
}
|
||||
|
||||
func (a *agent) setStatus(state int32) {
|
||||
atomic.StoreInt32(&a.state, state)
|
||||
}
|
||||
|
||||
func (a *agent) write() {
|
||||
ticker := time.NewTicker(env.Heartbeat)
|
||||
chWrite := make(chan []byte, agentWriteBacklog)
|
||||
// clean func
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
close(a.chSend)
|
||||
close(chWrite)
|
||||
_ = a.Close()
|
||||
if env.Debug {
|
||||
log.Println(fmt.Sprintf("Session write goroutine exit, SessionID=%d, UID=%d", a.session.ID(), a.session.UID()))
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
deadline := time.Now().Add(-2 * env.Heartbeat).Unix()
|
||||
if atomic.LoadInt64(&a.lastAt) < deadline {
|
||||
log.Println(fmt.Sprintf("Session heartbeat timeout, LastTime=%d, Deadline=%d", atomic.LoadInt64(&a.lastAt), deadline))
|
||||
return
|
||||
}
|
||||
chWrite <- hbd
|
||||
|
||||
case data := <-chWrite:
|
||||
// close agent while low-level conn broken
|
||||
if _, err := a.conn.Write(data); err != nil {
|
||||
log.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
case data := <-a.chSend:
|
||||
payload, err := message.Serialize(data.payload)
|
||||
if err != nil {
|
||||
switch data.typ {
|
||||
case message.Push:
|
||||
log.Println(fmt.Sprintf("Push: %s error: %s", data.route, err.Error()))
|
||||
case message.Response:
|
||||
log.Println(fmt.Sprintf("Response message(id: %d) error: %s", data.mid, err.Error()))
|
||||
default:
|
||||
// expect
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// construct message and encode
|
||||
m := &message.Message{
|
||||
Type: data.typ,
|
||||
Data: payload,
|
||||
Route: data.route,
|
||||
ID: data.mid,
|
||||
}
|
||||
if pipe := a.pipeline; pipe != nil {
|
||||
err := pipe.Outbound().Process(a.session, m)
|
||||
if err != nil {
|
||||
log.Println("broken pipeline", err.Error())
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
em, err := m.Encode()
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
// packet encode
|
||||
p, err := codec.Encode(packet.Data, em)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
break
|
||||
}
|
||||
chWrite <- p
|
||||
|
||||
case <-a.chDie: // agent closed signal
|
||||
return
|
||||
|
||||
case <-env.Die: // application quit
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"ngs/cluster/clusterpb"
|
||||
"ngs/internal/log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// cluster represents a ngs cluster, which contains a bunch of ngs nodes
|
||||
// and each of them provide a group of different services. All services requests
|
||||
// from client will send to gate firstly and be forwarded to appropriate node.
|
||||
type cluster struct {
|
||||
// If cluster is not large enough, use slice is OK
|
||||
currentNode *Node
|
||||
rpcClient *rpcClient
|
||||
|
||||
mu sync.RWMutex
|
||||
members []*Member
|
||||
}
|
||||
|
||||
func newCluster(currentNode *Node) *cluster {
|
||||
return &cluster{currentNode: currentNode}
|
||||
}
|
||||
|
||||
// Register implements the MasterServer gRPC service
|
||||
func (c *cluster) Register(_ context.Context, req *clusterpb.RegisterRequest) (*clusterpb.RegisterResponse, error) {
|
||||
if req.MemberInfo == nil {
|
||||
return nil, ErrInvalidRegisterReq
|
||||
}
|
||||
|
||||
resp := &clusterpb.RegisterResponse{}
|
||||
for _, m := range c.members {
|
||||
if m.memberInfo.ServiceAddr == req.MemberInfo.ServiceAddr {
|
||||
return nil, fmt.Errorf("address %s has registered", req.MemberInfo.ServiceAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// Notify registered node to update remote services
|
||||
newMember := &clusterpb.NewMemberRequest{MemberInfo: req.MemberInfo}
|
||||
for _, m := range c.members {
|
||||
resp.Members = append(resp.Members, m.memberInfo)
|
||||
if m.isMaster {
|
||||
continue
|
||||
}
|
||||
pool, err := c.rpcClient.getConnPool(m.memberInfo.ServiceAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := clusterpb.NewMemberClient(pool.Get())
|
||||
_, err = client.NewMember(context.Background(), newMember)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("New peer register to cluster", req.MemberInfo.ServiceAddr)
|
||||
|
||||
// Register services to current node
|
||||
c.currentNode.handler.addRemoteService(req.MemberInfo)
|
||||
c.mu.Lock()
|
||||
c.members = append(c.members, &Member{isMaster: false, memberInfo: req.MemberInfo})
|
||||
c.mu.Unlock()
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Unregister implements the MasterServer gRPC service
|
||||
func (c *cluster) Unregister(_ context.Context, req *clusterpb.UnregisterRequest) (*clusterpb.UnregisterResponse, error) {
|
||||
if req.ServiceAddr == "" {
|
||||
return nil, ErrInvalidRegisterReq
|
||||
}
|
||||
|
||||
var index = -1
|
||||
resp := &clusterpb.UnregisterResponse{}
|
||||
for i, m := range c.members {
|
||||
if m.memberInfo.ServiceAddr == req.ServiceAddr {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index < 0 {
|
||||
return nil, fmt.Errorf("address %s has notregistered", req.ServiceAddr)
|
||||
}
|
||||
|
||||
// Notify registered node to update remote services
|
||||
delMember := &clusterpb.DelMemberRequest{ServiceAddr: req.ServiceAddr}
|
||||
for _, m := range c.members {
|
||||
if m.MemberInfo().ServiceAddr == c.currentNode.ServiceAddr {
|
||||
continue
|
||||
}
|
||||
pool, err := c.rpcClient.getConnPool(m.memberInfo.ServiceAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := clusterpb.NewMemberClient(pool.Get())
|
||||
_, err = client.DelMember(context.Background(), delMember)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Exists peer unregister to cluster", req.ServiceAddr)
|
||||
|
||||
// Register services to current node
|
||||
c.currentNode.handler.delMember(req.ServiceAddr)
|
||||
c.mu.Lock()
|
||||
if index == len(c.members)-1 {
|
||||
c.members = c.members[:index]
|
||||
} else {
|
||||
c.members = append(c.members[:index], c.members[index+1:]...)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *cluster) setRpcClient(client *rpcClient) {
|
||||
c.rpcClient = client
|
||||
}
|
||||
|
||||
func (c *cluster) remoteAddrs() []string {
|
||||
var addrs []string
|
||||
c.mu.RLock()
|
||||
for _, m := range c.members {
|
||||
addrs = append(addrs, m.memberInfo.ServiceAddr)
|
||||
}
|
||||
c.mu.RUnlock()
|
||||
return addrs
|
||||
}
|
||||
|
||||
func (c *cluster) initMembers(members []*clusterpb.MemberInfo) {
|
||||
c.mu.Lock()
|
||||
for _, info := range members {
|
||||
c.members = append(c.members, &Member{
|
||||
memberInfo: info,
|
||||
})
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *cluster) addMember(info *clusterpb.MemberInfo) {
|
||||
c.mu.Lock()
|
||||
var found bool
|
||||
for _, member := range c.members {
|
||||
if member.memberInfo.ServiceAddr == info.ServiceAddr {
|
||||
member.memberInfo = info
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
c.members = append(c.members, &Member{
|
||||
memberInfo: info,
|
||||
})
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *cluster) delMember(addr string) {
|
||||
c.mu.Lock()
|
||||
var index = -1
|
||||
for i, member := range c.members {
|
||||
if member.memberInfo.ServiceAddr == addr {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index != -1 {
|
||||
c.members = append(c.members[:index], c.members[index+1:]...)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}
|
@ -0,0 +1,475 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc v3.19.4
|
||||
// source: cluster.proto
|
||||
|
||||
package clusterpb
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// MasterClient is the client API for Master service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type MasterClient interface {
|
||||
Register(ctx context.Context, in *RegisterRequest, opts ...grpc.CallOption) (*RegisterResponse, error)
|
||||
Unregister(ctx context.Context, in *UnregisterRequest, opts ...grpc.CallOption) (*UnregisterResponse, error)
|
||||
}
|
||||
|
||||
type masterClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewMasterClient(cc grpc.ClientConnInterface) MasterClient {
|
||||
return &masterClient{cc}
|
||||
}
|
||||
|
||||
func (c *masterClient) Register(ctx context.Context, in *RegisterRequest, opts ...grpc.CallOption) (*RegisterResponse, error) {
|
||||
out := new(RegisterResponse)
|
||||
err := c.cc.Invoke(ctx, "/clusterpb.Master/Register", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *masterClient) Unregister(ctx context.Context, in *UnregisterRequest, opts ...grpc.CallOption) (*UnregisterResponse, error) {
|
||||
out := new(UnregisterResponse)
|
||||
err := c.cc.Invoke(ctx, "/clusterpb.Master/Unregister", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// MasterServer is the server API for Master service.
|
||||
// All implementations should embed UnimplementedMasterServer
|
||||
// for forward compatibility
|
||||
type MasterServer interface {
|
||||
Register(context.Context, *RegisterRequest) (*RegisterResponse, error)
|
||||
Unregister(context.Context, *UnregisterRequest) (*UnregisterResponse, error)
|
||||
}
|
||||
|
||||
// UnimplementedMasterServer should be embedded to have forward compatible implementations.
|
||||
type UnimplementedMasterServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedMasterServer) Register(context.Context, *RegisterRequest) (*RegisterResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Register not implemented")
|
||||
}
|
||||
func (UnimplementedMasterServer) Unregister(context.Context, *UnregisterRequest) (*UnregisterResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Unregister not implemented")
|
||||
}
|
||||
|
||||
// UnsafeMasterServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to MasterServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeMasterServer interface {
|
||||
mustEmbedUnimplementedMasterServer()
|
||||
}
|
||||
|
||||
func RegisterMasterServer(s grpc.ServiceRegistrar, srv MasterServer) {
|
||||
s.RegisterService(&Master_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _Master_Register_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(RegisterRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MasterServer).Register(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/clusterpb.Master/Register",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MasterServer).Register(ctx, req.(*RegisterRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Master_Unregister_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UnregisterRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MasterServer).Unregister(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/clusterpb.Master/Unregister",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MasterServer).Unregister(ctx, req.(*UnregisterRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// Master_ServiceDesc is the grpc.ServiceDesc for Master service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var Master_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "clusterpb.Master",
|
||||
HandlerType: (*MasterServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Register",
|
||||
Handler: _Master_Register_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Unregister",
|
||||
Handler: _Master_Unregister_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "cluster.proto",
|
||||
}
|
||||
|
||||
// MemberClient is the client API for Member service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type MemberClient interface {
|
||||
HandleRequest(ctx context.Context, in *RequestMessage, opts ...grpc.CallOption) (*MemberHandleResponse, error)
|
||||
HandleNotify(ctx context.Context, in *NotifyMessage, opts ...grpc.CallOption) (*MemberHandleResponse, error)
|
||||
HandlePush(ctx context.Context, in *PushMessage, opts ...grpc.CallOption) (*MemberHandleResponse, error)
|
||||
HandleResponse(ctx context.Context, in *ResponseMessage, opts ...grpc.CallOption) (*MemberHandleResponse, error)
|
||||
NewMember(ctx context.Context, in *NewMemberRequest, opts ...grpc.CallOption) (*NewMemberResponse, error)
|
||||
DelMember(ctx context.Context, in *DelMemberRequest, opts ...grpc.CallOption) (*DelMemberResponse, error)
|
||||
SessionClosed(ctx context.Context, in *SessionClosedRequest, opts ...grpc.CallOption) (*SessionClosedResponse, error)
|
||||
CloseSession(ctx context.Context, in *CloseSessionRequest, opts ...grpc.CallOption) (*CloseSessionResponse, error)
|
||||
}
|
||||
|
||||
type memberClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewMemberClient(cc grpc.ClientConnInterface) MemberClient {
|
||||
return &memberClient{cc}
|
||||
}
|
||||
|
||||
func (c *memberClient) HandleRequest(ctx context.Context, in *RequestMessage, opts ...grpc.CallOption) (*MemberHandleResponse, error) {
|
||||
out := new(MemberHandleResponse)
|
||||
err := c.cc.Invoke(ctx, "/clusterpb.Member/HandleRequest", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *memberClient) HandleNotify(ctx context.Context, in *NotifyMessage, opts ...grpc.CallOption) (*MemberHandleResponse, error) {
|
||||
out := new(MemberHandleResponse)
|
||||
err := c.cc.Invoke(ctx, "/clusterpb.Member/HandleNotify", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *memberClient) HandlePush(ctx context.Context, in *PushMessage, opts ...grpc.CallOption) (*MemberHandleResponse, error) {
|
||||
out := new(MemberHandleResponse)
|
||||
err := c.cc.Invoke(ctx, "/clusterpb.Member/HandlePush", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *memberClient) HandleResponse(ctx context.Context, in *ResponseMessage, opts ...grpc.CallOption) (*MemberHandleResponse, error) {
|
||||
out := new(MemberHandleResponse)
|
||||
err := c.cc.Invoke(ctx, "/clusterpb.Member/HandleResponse", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *memberClient) NewMember(ctx context.Context, in *NewMemberRequest, opts ...grpc.CallOption) (*NewMemberResponse, error) {
|
||||
out := new(NewMemberResponse)
|
||||
err := c.cc.Invoke(ctx, "/clusterpb.Member/NewMember", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *memberClient) DelMember(ctx context.Context, in *DelMemberRequest, opts ...grpc.CallOption) (*DelMemberResponse, error) {
|
||||
out := new(DelMemberResponse)
|
||||
err := c.cc.Invoke(ctx, "/clusterpb.Member/DelMember", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *memberClient) SessionClosed(ctx context.Context, in *SessionClosedRequest, opts ...grpc.CallOption) (*SessionClosedResponse, error) {
|
||||
out := new(SessionClosedResponse)
|
||||
err := c.cc.Invoke(ctx, "/clusterpb.Member/SessionClosed", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *memberClient) CloseSession(ctx context.Context, in *CloseSessionRequest, opts ...grpc.CallOption) (*CloseSessionResponse, error) {
|
||||
out := new(CloseSessionResponse)
|
||||
err := c.cc.Invoke(ctx, "/clusterpb.Member/CloseSession", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// MemberServer is the server API for Member service.
|
||||
// All implementations should embed UnimplementedMemberServer
|
||||
// for forward compatibility
|
||||
type MemberServer interface {
|
||||
HandleRequest(context.Context, *RequestMessage) (*MemberHandleResponse, error)
|
||||
HandleNotify(context.Context, *NotifyMessage) (*MemberHandleResponse, error)
|
||||
HandlePush(context.Context, *PushMessage) (*MemberHandleResponse, error)
|
||||
HandleResponse(context.Context, *ResponseMessage) (*MemberHandleResponse, error)
|
||||
NewMember(context.Context, *NewMemberRequest) (*NewMemberResponse, error)
|
||||
DelMember(context.Context, *DelMemberRequest) (*DelMemberResponse, error)
|
||||
SessionClosed(context.Context, *SessionClosedRequest) (*SessionClosedResponse, error)
|
||||
CloseSession(context.Context, *CloseSessionRequest) (*CloseSessionResponse, error)
|
||||
}
|
||||
|
||||
// UnimplementedMemberServer should be embedded to have forward compatible implementations.
|
||||
type UnimplementedMemberServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedMemberServer) HandleRequest(context.Context, *RequestMessage) (*MemberHandleResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method HandleRequest not implemented")
|
||||
}
|
||||
func (UnimplementedMemberServer) HandleNotify(context.Context, *NotifyMessage) (*MemberHandleResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method HandleNotify not implemented")
|
||||
}
|
||||
func (UnimplementedMemberServer) HandlePush(context.Context, *PushMessage) (*MemberHandleResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method HandlePush not implemented")
|
||||
}
|
||||
func (UnimplementedMemberServer) HandleResponse(context.Context, *ResponseMessage) (*MemberHandleResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method HandleResponse not implemented")
|
||||
}
|
||||
func (UnimplementedMemberServer) NewMember(context.Context, *NewMemberRequest) (*NewMemberResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method NewMember not implemented")
|
||||
}
|
||||
func (UnimplementedMemberServer) DelMember(context.Context, *DelMemberRequest) (*DelMemberResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DelMember not implemented")
|
||||
}
|
||||
func (UnimplementedMemberServer) SessionClosed(context.Context, *SessionClosedRequest) (*SessionClosedResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SessionClosed not implemented")
|
||||
}
|
||||
func (UnimplementedMemberServer) CloseSession(context.Context, *CloseSessionRequest) (*CloseSessionResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CloseSession not implemented")
|
||||
}
|
||||
|
||||
// UnsafeMemberServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to MemberServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeMemberServer interface {
|
||||
mustEmbedUnimplementedMemberServer()
|
||||
}
|
||||
|
||||
func RegisterMemberServer(s grpc.ServiceRegistrar, srv MemberServer) {
|
||||
s.RegisterService(&Member_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _Member_HandleRequest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(RequestMessage)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MemberServer).HandleRequest(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/clusterpb.Member/HandleRequest",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MemberServer).HandleRequest(ctx, req.(*RequestMessage))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Member_HandleNotify_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(NotifyMessage)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MemberServer).HandleNotify(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/clusterpb.Member/HandleNotify",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MemberServer).HandleNotify(ctx, req.(*NotifyMessage))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Member_HandlePush_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(PushMessage)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MemberServer).HandlePush(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/clusterpb.Member/HandlePush",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MemberServer).HandlePush(ctx, req.(*PushMessage))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Member_HandleResponse_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ResponseMessage)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MemberServer).HandleResponse(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/clusterpb.Member/HandleResponse",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MemberServer).HandleResponse(ctx, req.(*ResponseMessage))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Member_NewMember_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(NewMemberRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MemberServer).NewMember(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/clusterpb.Member/NewMember",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MemberServer).NewMember(ctx, req.(*NewMemberRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Member_DelMember_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DelMemberRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MemberServer).DelMember(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/clusterpb.Member/DelMember",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MemberServer).DelMember(ctx, req.(*DelMemberRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Member_SessionClosed_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SessionClosedRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MemberServer).SessionClosed(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/clusterpb.Member/SessionClosed",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MemberServer).SessionClosed(ctx, req.(*SessionClosedRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Member_CloseSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(CloseSessionRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(MemberServer).CloseSession(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/clusterpb.Member/CloseSession",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(MemberServer).CloseSession(ctx, req.(*CloseSessionRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// Member_ServiceDesc is the grpc.ServiceDesc for Member service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var Member_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "clusterpb.Member",
|
||||
HandlerType: (*MemberServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "HandleRequest",
|
||||
Handler: _Member_HandleRequest_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "HandleNotify",
|
||||
Handler: _Member_HandleNotify_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "HandlePush",
|
||||
Handler: _Member_HandlePush_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "HandleResponse",
|
||||
Handler: _Member_HandleResponse_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "NewMember",
|
||||
Handler: _Member_NewMember_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DelMember",
|
||||
Handler: _Member_DelMember_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SessionClosed",
|
||||
Handler: _Member_SessionClosed_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CloseSession",
|
||||
Handler: _Member_CloseSession_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "cluster.proto",
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package clusterpb;
|
||||
|
||||
option go_package = "/clusterpb";
|
||||
|
||||
message MemberInfo {
|
||||
string label = 1;
|
||||
string serviceAddr = 2;
|
||||
repeated string services = 3;
|
||||
}
|
||||
|
||||
message RegisterRequest {
|
||||
MemberInfo memberInfo = 1;
|
||||
}
|
||||
|
||||
message RegisterResponse {
|
||||
repeated MemberInfo members = 1;
|
||||
}
|
||||
|
||||
message UnregisterRequest {
|
||||
string serviceAddr = 1;
|
||||
}
|
||||
|
||||
message UnregisterResponse {}
|
||||
|
||||
service Master {
|
||||
rpc Register (RegisterRequest) returns (RegisterResponse) {}
|
||||
rpc Unregister (UnregisterRequest) returns (UnregisterResponse) {}
|
||||
}
|
||||
|
||||
message RequestMessage {
|
||||
string gateAddr = 1;
|
||||
int64 sessionId = 2;
|
||||
uint64 id = 3;
|
||||
string route = 4;
|
||||
bytes data = 5;
|
||||
}
|
||||
|
||||
message NotifyMessage {
|
||||
string gateAddr = 1;
|
||||
int64 sessionId = 2;
|
||||
string route = 3;
|
||||
bytes data = 4;
|
||||
}
|
||||
|
||||
message ResponseMessage {
|
||||
int64 sessionId = 1;
|
||||
uint64 id = 2;
|
||||
bytes data = 3;
|
||||
}
|
||||
|
||||
message PushMessage {
|
||||
int64 sessionId = 1;
|
||||
string route = 2;
|
||||
bytes data = 3;
|
||||
}
|
||||
|
||||
message MemberHandleResponse {}
|
||||
|
||||
message NewMemberRequest {
|
||||
MemberInfo memberInfo = 1;
|
||||
}
|
||||
|
||||
message NewMemberResponse {}
|
||||
|
||||
message DelMemberRequest {
|
||||
string serviceAddr = 1;
|
||||
}
|
||||
|
||||
message DelMemberResponse {}
|
||||
|
||||
message SessionClosedRequest {
|
||||
int64 sessionId = 1;
|
||||
}
|
||||
|
||||
message SessionClosedResponse {}
|
||||
|
||||
message CloseSessionRequest {
|
||||
int64 sessionId = 1;
|
||||
}
|
||||
|
||||
message CloseSessionResponse {}
|
||||
|
||||
service Member {
|
||||
rpc HandleRequest (RequestMessage) returns (MemberHandleResponse) {}
|
||||
rpc HandleNotify (NotifyMessage) returns (MemberHandleResponse) {}
|
||||
rpc HandlePush (PushMessage) returns (MemberHandleResponse) {}
|
||||
rpc HandleResponse (ResponseMessage) returns (MemberHandleResponse) {}
|
||||
|
||||
rpc NewMember (NewMemberRequest) returns (NewMemberResponse) {}
|
||||
rpc DelMember (DelMemberRequest) returns (DelMemberResponse) {}
|
||||
rpc SessionClosed(SessionClosedRequest) returns(SessionClosedResponse) {}
|
||||
rpc CloseSession(CloseSessionRequest) returns(CloseSessionResponse) {}
|
||||
}
|
@ -0,0 +1 @@
|
||||
protoc --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative --go-grpc_opt=require_unimplemented_servers=false --go_out=.. --go-grpc_out=.. --proto_path=. *.proto
|
@ -0,0 +1,122 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"google.golang.org/grpc"
|
||||
"ngs/internal/env"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type connPool struct {
|
||||
index uint32
|
||||
v []*grpc.ClientConn
|
||||
}
|
||||
|
||||
type rpcClient struct {
|
||||
sync.RWMutex
|
||||
isClosed bool
|
||||
pools map[string]*connPool
|
||||
}
|
||||
|
||||
func newConnArray(maxSize uint, addr string) (*connPool, error) {
|
||||
a := &connPool{
|
||||
index: 0,
|
||||
v: make([]*grpc.ClientConn, maxSize),
|
||||
}
|
||||
if err := a.init(addr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (a *connPool) init(addr string) error {
|
||||
for i := range a.v {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
conn, err := grpc.DialContext(
|
||||
ctx,
|
||||
addr,
|
||||
env.GrpcOptions...,
|
||||
)
|
||||
cancel()
|
||||
if err != nil {
|
||||
// Cleanup if the initialization fails.
|
||||
a.Close()
|
||||
return err
|
||||
}
|
||||
a.v[i] = conn
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *connPool) Get() *grpc.ClientConn {
|
||||
next := atomic.AddUint32(&a.index, 1) % uint32(len(a.v))
|
||||
return a.v[next]
|
||||
}
|
||||
|
||||
func (a *connPool) Close() {
|
||||
for i, c := range a.v {
|
||||
if c != nil {
|
||||
err := c.Close()
|
||||
if err != nil {
|
||||
// TODO: error handling
|
||||
}
|
||||
a.v[i] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newRPCClient() *rpcClient {
|
||||
return &rpcClient{
|
||||
pools: make(map[string]*connPool),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *rpcClient) getConnPool(addr string) (*connPool, error) {
|
||||
c.RLock()
|
||||
if c.isClosed {
|
||||
c.RUnlock()
|
||||
return nil, errors.New("rpc client is closed")
|
||||
}
|
||||
array, ok := c.pools[addr]
|
||||
c.RUnlock()
|
||||
if !ok {
|
||||
var err error
|
||||
array, err = c.createConnPool(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return array, nil
|
||||
}
|
||||
|
||||
func (c *rpcClient) createConnPool(addr string) (*connPool, error) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
array, ok := c.pools[addr]
|
||||
if !ok {
|
||||
var err error
|
||||
// TODO: make conn count configurable
|
||||
array, err = newConnArray(10, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.pools[addr] = array
|
||||
}
|
||||
return array, nil
|
||||
}
|
||||
|
||||
func (c *rpcClient) closePool() {
|
||||
c.Lock()
|
||||
if !c.isClosed {
|
||||
c.isClosed = true
|
||||
// close all connections
|
||||
for _, array := range c.pools {
|
||||
array.Close()
|
||||
}
|
||||
}
|
||||
c.Unlock()
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package cluster
|
||||
|
||||
import "errors"
|
||||
|
||||
// Errors that could be occurred during message handling.
|
||||
var (
|
||||
ErrSessionOnNotify = errors.New("current session working on notify mode")
|
||||
ErrCloseClosedSession = errors.New("close closed session")
|
||||
ErrInvalidRegisterReq = errors.New("invalid register request")
|
||||
)
|
@ -0,0 +1,445 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gorilla/websocket"
|
||||
"math/rand"
|
||||
"net"
|
||||
"ngs/cluster/clusterpb"
|
||||
"ngs/component"
|
||||
"ngs/internal/codec"
|
||||
"ngs/internal/env"
|
||||
"ngs/internal/log"
|
||||
"ngs/internal/message"
|
||||
"ngs/internal/packet"
|
||||
"ngs/pipeline"
|
||||
"ngs/scheduler"
|
||||
"ngs/session"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// cached serialized data
|
||||
hrd []byte // handshake response data
|
||||
hbd []byte // heartbeat packet data
|
||||
)
|
||||
|
||||
type rpcHandler func(session *session.Session, msg *message.Message, noCopy bool)
|
||||
|
||||
func cache() {
|
||||
data, err := json.Marshal(map[string]interface{}{
|
||||
"code": 200,
|
||||
"sys": map[string]float64{"heartbeat": env.Heartbeat.Seconds()},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
hrd, err = codec.Encode(packet.Handshake, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
hbd, err = codec.Encode(packet.Heartbeat, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type LocalHandler struct {
|
||||
localServices map[string]*component.Service // all registered service
|
||||
localHandlers map[string]*component.Handler // all handler method
|
||||
|
||||
mu sync.RWMutex
|
||||
remoteServices map[string][]*clusterpb.MemberInfo
|
||||
|
||||
pipeline pipeline.Pipeline
|
||||
currentNode *Node
|
||||
}
|
||||
|
||||
func NewHandler(currentNode *Node, pipeline pipeline.Pipeline) *LocalHandler {
|
||||
h := &LocalHandler{
|
||||
localServices: make(map[string]*component.Service),
|
||||
localHandlers: make(map[string]*component.Handler),
|
||||
remoteServices: map[string][]*clusterpb.MemberInfo{},
|
||||
pipeline: pipeline,
|
||||
currentNode: currentNode,
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *LocalHandler) register(comp component.Component, opts []component.Option) error {
|
||||
s := component.NewService(comp, opts)
|
||||
|
||||
if _, ok := h.localServices[s.Name]; ok {
|
||||
return fmt.Errorf("handler: service already defined: %s", s.Name)
|
||||
}
|
||||
|
||||
if err := s.ExtractHandler(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// register all localHandlers
|
||||
h.localServices[s.Name] = s
|
||||
for name, handler := range s.Handlers {
|
||||
n := fmt.Sprintf("%s.%s", s.Name, name)
|
||||
log.Println("Register local handler", n)
|
||||
h.localHandlers[n] = handler
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *LocalHandler) initRemoteService(members []*clusterpb.MemberInfo) {
|
||||
for _, m := range members {
|
||||
h.addRemoteService(m)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *LocalHandler) addRemoteService(member *clusterpb.MemberInfo) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
for _, s := range member.Services {
|
||||
log.Println("Register remote service", s)
|
||||
h.remoteServices[s] = append(h.remoteServices[s], member)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *LocalHandler) delMember(addr string) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
for name, members := range h.remoteServices {
|
||||
for i, maddr := range members {
|
||||
if addr == maddr.ServiceAddr {
|
||||
members = append(members[:i], members[i+1:]...)
|
||||
}
|
||||
}
|
||||
if len(members) == 0 {
|
||||
delete(h.remoteServices, name)
|
||||
} else {
|
||||
h.remoteServices[name] = members
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *LocalHandler) LocalService() []string {
|
||||
var result []string
|
||||
for service := range h.localServices {
|
||||
result = append(result, service)
|
||||
}
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
|
||||
func (h *LocalHandler) RemoteService() []string {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
|
||||
var result []string
|
||||
for service := range h.remoteServices {
|
||||
result = append(result, service)
|
||||
}
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
|
||||
func (h *LocalHandler) handle(conn net.Conn) {
|
||||
// create a client agent and startup write gorontine
|
||||
agent := newAgent(conn, h.pipeline, h.remoteProcess)
|
||||
h.currentNode.storeSession(agent.session)
|
||||
|
||||
// startup write goroutine
|
||||
go agent.write()
|
||||
|
||||
if env.Debug {
|
||||
log.Println(fmt.Sprintf("New session established: %s", agent.String()))
|
||||
}
|
||||
|
||||
// guarantee agent related resource be destroyed
|
||||
defer func() {
|
||||
request := &clusterpb.SessionClosedRequest{
|
||||
SessionId: agent.session.ID(),
|
||||
}
|
||||
|
||||
members := h.currentNode.cluster.remoteAddrs()
|
||||
for _, remote := range members {
|
||||
log.Println("Notify remote server success", remote)
|
||||
pool, err := h.currentNode.rpcClient.getConnPool(remote)
|
||||
if err != nil {
|
||||
log.Println("Cannot retrieve connection pool for address", remote, err)
|
||||
continue
|
||||
}
|
||||
client := clusterpb.NewMemberClient(pool.Get())
|
||||
_, err = client.SessionClosed(context.Background(), request)
|
||||
if err != nil {
|
||||
log.Println("Cannot closed session in remote address", remote, err)
|
||||
continue
|
||||
}
|
||||
if env.Debug {
|
||||
log.Println("Notify remote server success", remote)
|
||||
}
|
||||
}
|
||||
|
||||
agent.Close()
|
||||
if env.Debug {
|
||||
log.Println(fmt.Sprintf("Session read goroutine exit, SessionID=%d, UID=%d", agent.session.ID(), agent.session.UID()))
|
||||
}
|
||||
}()
|
||||
|
||||
// read loop
|
||||
buf := make([]byte, 2048)
|
||||
for {
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
log.Println(fmt.Sprintf("Read message error: %s, session will be closed immediately", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(warning): decoder use slice for performance, packet data should be copy before next Decode
|
||||
packets, err := agent.decoder.Decode(buf[:n])
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
|
||||
// process packets decoded
|
||||
for _, p := range packets {
|
||||
if err := h.processPacket(agent, p); err != nil {
|
||||
log.Println(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// process all packets
|
||||
for _, p := range packets {
|
||||
if err := h.processPacket(agent, p); err != nil {
|
||||
log.Println(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *LocalHandler) processPacket(agent *agent, p *packet.Packet) error {
|
||||
switch p.Type {
|
||||
case packet.Handshake:
|
||||
if err := env.HandshakeValidator(p.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := agent.conn.Write(hrd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
agent.setStatus(statusHandshake)
|
||||
if env.Debug {
|
||||
log.Println(fmt.Sprintf("Session handshake Id=%d, Remote=%s", agent.session.ID(), agent.conn.RemoteAddr()))
|
||||
}
|
||||
|
||||
case packet.HandshakeAck:
|
||||
agent.setStatus(statusWorking)
|
||||
if env.Debug {
|
||||
log.Println(fmt.Sprintf("Receive handshake ACK Id=%d, Remote=%s", agent.session.ID(), agent.conn.RemoteAddr()))
|
||||
}
|
||||
|
||||
case packet.Data:
|
||||
if agent.status() < statusWorking {
|
||||
return fmt.Errorf("receive data on socket which not yet ACK, session will be closed immediately, remote=%s",
|
||||
agent.conn.RemoteAddr().String())
|
||||
}
|
||||
|
||||
msg, err := message.Decode(p.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.processMessage(agent, msg)
|
||||
|
||||
case packet.Heartbeat:
|
||||
// expected
|
||||
}
|
||||
|
||||
agent.lastAt = time.Now().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *LocalHandler) findMembers(service string) []*clusterpb.MemberInfo {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
return h.remoteServices[service]
|
||||
}
|
||||
|
||||
func (h *LocalHandler) remoteProcess(session *session.Session, msg *message.Message, noCopy bool) {
|
||||
index := strings.LastIndex(msg.Route, ".")
|
||||
if index < 0 {
|
||||
log.Println(fmt.Sprintf("ngs/handler: invalid route %s", msg.Route))
|
||||
return
|
||||
}
|
||||
|
||||
service := msg.Route[:index]
|
||||
members := h.findMembers(service)
|
||||
if len(members) == 0 {
|
||||
log.Println(fmt.Sprintf("ngs/handler: %s not found(forgot registered?)", msg.Route))
|
||||
return
|
||||
}
|
||||
|
||||
// Select a remote service address
|
||||
// 1. Use the service address directly if the router contains binding item
|
||||
// 2. Select a remote service address randomly and bind to router
|
||||
var remoteAddr string
|
||||
if addr, found := session.Router().Find(service); found {
|
||||
remoteAddr = addr
|
||||
} else {
|
||||
remoteAddr = members[rand.Intn(len(members))].ServiceAddr
|
||||
session.Router().Bind(service, remoteAddr)
|
||||
}
|
||||
pool, err := h.currentNode.rpcClient.getConnPool(remoteAddr)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
var data = msg.Data
|
||||
if !noCopy && len(msg.Data) > 0 {
|
||||
data = make([]byte, len(msg.Data))
|
||||
copy(data, msg.Data)
|
||||
}
|
||||
|
||||
// Retrieve gate address and session id
|
||||
gateAddr := h.currentNode.ServiceAddr
|
||||
sessionId := session.ID()
|
||||
switch v := session.NetworkEntity().(type) {
|
||||
case *acceptor:
|
||||
gateAddr = v.gateAddr
|
||||
sessionId = v.sid
|
||||
}
|
||||
|
||||
client := clusterpb.NewMemberClient(pool.Get())
|
||||
switch msg.Type {
|
||||
case message.Request:
|
||||
request := &clusterpb.RequestMessage{
|
||||
GateAddr: gateAddr,
|
||||
SessionId: sessionId,
|
||||
Id: msg.ID,
|
||||
Route: msg.Route,
|
||||
Data: data,
|
||||
}
|
||||
_, err = client.HandleRequest(context.Background(), request)
|
||||
case message.Notify:
|
||||
request := &clusterpb.NotifyMessage{
|
||||
GateAddr: gateAddr,
|
||||
SessionId: sessionId,
|
||||
Route: msg.Route,
|
||||
Data: data,
|
||||
}
|
||||
_, err = client.HandleNotify(context.Background(), request)
|
||||
}
|
||||
if err != nil {
|
||||
log.Println(fmt.Sprintf("Process remote message (%d:%s) error: %+v", msg.ID, msg.Route, err))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *LocalHandler) processMessage(agent *agent, msg *message.Message) {
|
||||
var lastMid uint64
|
||||
switch msg.Type {
|
||||
case message.Request:
|
||||
lastMid = msg.ID
|
||||
case message.Notify:
|
||||
lastMid = 0
|
||||
default:
|
||||
log.Println("Invalid message type: " + msg.Type.String())
|
||||
return
|
||||
}
|
||||
|
||||
handler, found := h.localHandlers[msg.Route]
|
||||
if !found {
|
||||
h.remoteProcess(agent.session, msg, false)
|
||||
} else {
|
||||
h.localProcess(handler, lastMid, agent.session, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *LocalHandler) handleWS(conn *websocket.Conn) {
|
||||
c, err := newWSConn(conn)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
go h.handle(c)
|
||||
}
|
||||
|
||||
func (h *LocalHandler) localProcess(handler *component.Handler, lastMid uint64, session *session.Session, msg *message.Message) {
|
||||
if pipe := h.pipeline; pipe != nil {
|
||||
err := pipe.Inbound().Process(session, msg)
|
||||
if err != nil {
|
||||
log.Println("Pipeline process failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var payload = msg.Data
|
||||
var data interface{}
|
||||
if handler.IsRawArg {
|
||||
data = payload
|
||||
} else {
|
||||
data = reflect.New(handler.Type.Elem()).Interface()
|
||||
err := env.Serializer.Unmarshal(payload, data)
|
||||
if err != nil {
|
||||
log.Println(fmt.Sprintf("Deserialize to %T failed: %+v (%v)", data, err, payload))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if env.Debug {
|
||||
log.Println(fmt.Sprintf("UID=%d, Message={%s}, Data=%+v", session.UID(), msg.String(), data))
|
||||
}
|
||||
|
||||
args := []reflect.Value{handler.Receiver, reflect.ValueOf(session), reflect.ValueOf(data)}
|
||||
task := func() {
|
||||
switch v := session.NetworkEntity().(type) {
|
||||
case *agent:
|
||||
v.lastMid = lastMid
|
||||
case *acceptor:
|
||||
v.lastMid = lastMid
|
||||
}
|
||||
|
||||
result := handler.Method.Func.Call(args)
|
||||
if len(result) > 0 {
|
||||
if err := result[0].Interface(); err != nil {
|
||||
log.Println(fmt.Sprintf("Service %s error: %+v", msg.Route, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
index := strings.LastIndex(msg.Route, ".")
|
||||
if index < 0 {
|
||||
log.Println(fmt.Sprintf("ngs/handler: invalid route %s", msg.Route))
|
||||
return
|
||||
}
|
||||
|
||||
// A message can be dispatch to global thread or a user customized thread
|
||||
service := msg.Route[:index]
|
||||
if s, found := h.localServices[service]; found && s.SchedulerName != "" {
|
||||
sched := session.Value(s.SchedulerName)
|
||||
if sched == nil {
|
||||
log.Println(fmt.Sprintf("nanl/handler: cannot found `schedular.LocalScheduler` by %s", s.SchedulerName))
|
||||
return
|
||||
}
|
||||
|
||||
local, ok := sched.(scheduler.LocalScheduler)
|
||||
if !ok {
|
||||
log.Println(fmt.Sprintf("nanl/handler: Type %T does not implement the `schedular.LocalScheduler` interface",
|
||||
sched))
|
||||
return
|
||||
}
|
||||
local.Schedule(task)
|
||||
} else {
|
||||
scheduler.PushTask(task)
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package cluster
|
||||
|
||||
import "ngs/cluster/clusterpb"
|
||||
|
||||
type Member struct {
|
||||
isMaster bool
|
||||
memberInfo *clusterpb.MemberInfo
|
||||
}
|
||||
|
||||
func (m *Member) MemberInfo() *clusterpb.MemberInfo {
|
||||
return m.memberInfo
|
||||
}
|
@ -0,0 +1,393 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gorilla/websocket"
|
||||
"google.golang.org/grpc"
|
||||
"net"
|
||||
"net/http"
|
||||
"ngs/cluster/clusterpb"
|
||||
"ngs/component"
|
||||
"ngs/internal/env"
|
||||
"ngs/internal/log"
|
||||
"ngs/internal/message"
|
||||
"ngs/pipeline"
|
||||
"ngs/scheduler"
|
||||
"ngs/session"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Options contains some configurations for current node
|
||||
type Options struct {
|
||||
Pipeline pipeline.Pipeline
|
||||
IsMaster bool
|
||||
AdvertiseAddr string
|
||||
RetryInterval time.Duration
|
||||
ClientAddr string
|
||||
Components *component.Components
|
||||
Label string
|
||||
IsWebsocket bool
|
||||
TSLCertificate string
|
||||
TSLKey string
|
||||
}
|
||||
|
||||
// Node represents a node in ngs cluster, which will contains a group of services.
|
||||
// All services will register to cluster and messages will be forwarded to the node
|
||||
// which provides respective service
|
||||
type Node struct {
|
||||
Options // current node options
|
||||
ServiceAddr string // current server service address (RPC)
|
||||
|
||||
cluster *cluster
|
||||
handler *LocalHandler
|
||||
server *grpc.Server
|
||||
rpcClient *rpcClient
|
||||
|
||||
mu sync.RWMutex
|
||||
sessions map[int64]*session.Session
|
||||
}
|
||||
|
||||
func (n *Node) Startup() error {
|
||||
if n.ServiceAddr == "" {
|
||||
return errors.New("service address cannot be empty in master node")
|
||||
}
|
||||
n.sessions = map[int64]*session.Session{}
|
||||
n.cluster = newCluster(n)
|
||||
n.handler = NewHandler(n, n.Pipeline)
|
||||
components := n.Components.List()
|
||||
for _, c := range components {
|
||||
err := n.handler.register(c.Comp, c.Opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cache()
|
||||
if err := n.initNode(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize all components
|
||||
for _, c := range components {
|
||||
c.Comp.Init()
|
||||
}
|
||||
for _, c := range components {
|
||||
c.Comp.AfterInit()
|
||||
}
|
||||
|
||||
if n.ClientAddr != "" {
|
||||
go func() {
|
||||
if n.IsWebsocket {
|
||||
if len(n.TSLCertificate) != 0 {
|
||||
n.listenAndServeWSTLS()
|
||||
} else {
|
||||
n.listenAndServeWS()
|
||||
}
|
||||
} else {
|
||||
n.listenAndServe()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Node) Handler() *LocalHandler {
|
||||
return n.handler
|
||||
}
|
||||
|
||||
func (n *Node) initNode() error {
|
||||
// Current node is not master server and does not contains master
|
||||
// address, so running in singleton mode
|
||||
if !n.IsMaster && n.AdvertiseAddr == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", n.ServiceAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the gRPC server and register service
|
||||
n.server = grpc.NewServer()
|
||||
n.rpcClient = newRPCClient()
|
||||
clusterpb.RegisterMemberServer(n.server, n)
|
||||
|
||||
go func() {
|
||||
err := n.server.Serve(listener)
|
||||
if err != nil {
|
||||
log.Fatalf("Start current node failed: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if n.IsMaster {
|
||||
clusterpb.RegisterMasterServer(n.server, n.cluster)
|
||||
member := &Member{
|
||||
isMaster: true,
|
||||
memberInfo: &clusterpb.MemberInfo{
|
||||
Label: n.Label,
|
||||
ServiceAddr: n.ServiceAddr,
|
||||
Services: n.handler.LocalService(),
|
||||
},
|
||||
}
|
||||
n.cluster.members = append(n.cluster.members, member)
|
||||
n.cluster.setRpcClient(n.rpcClient)
|
||||
} else {
|
||||
pool, err := n.rpcClient.getConnPool(n.AdvertiseAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := clusterpb.NewMasterClient(pool.Get())
|
||||
request := &clusterpb.RegisterRequest{
|
||||
MemberInfo: &clusterpb.MemberInfo{
|
||||
Label: n.Label,
|
||||
ServiceAddr: n.ServiceAddr,
|
||||
Services: n.handler.LocalService(),
|
||||
},
|
||||
}
|
||||
for {
|
||||
resp, err := client.Register(context.Background(), request)
|
||||
if err == nil {
|
||||
n.handler.initRemoteService(resp.Members)
|
||||
n.cluster.initMembers(resp.Members)
|
||||
break
|
||||
}
|
||||
log.Println("Register current node to cluster failed", err, "and will retry in", n.RetryInterval.String())
|
||||
time.Sleep(n.RetryInterval)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shutdowns all components registered by application, that
|
||||
// call by reverse order against register
|
||||
func (n *Node) Shutdown() {
|
||||
// reverse call `BeforeShutdown` hooks
|
||||
components := n.Components.List()
|
||||
length := len(components)
|
||||
for i := length - 1; i >= 0; i-- {
|
||||
components[i].Comp.BeforeShutdown()
|
||||
}
|
||||
|
||||
// reverse call `Shutdown` hooks
|
||||
for i := length - 1; i >= 0; i-- {
|
||||
components[i].Comp.Shutdown()
|
||||
}
|
||||
|
||||
if !n.IsMaster && n.AdvertiseAddr != "" {
|
||||
pool, err := n.rpcClient.getConnPool(n.AdvertiseAddr)
|
||||
if err != nil {
|
||||
log.Println("Retrieve master address error", err)
|
||||
goto EXIT
|
||||
}
|
||||
client := clusterpb.NewMasterClient(pool.Get())
|
||||
request := &clusterpb.UnregisterRequest{
|
||||
ServiceAddr: n.ServiceAddr,
|
||||
}
|
||||
_, err = client.Unregister(context.Background(), request)
|
||||
if err != nil {
|
||||
log.Println("Unregister current node failed", err)
|
||||
goto EXIT
|
||||
}
|
||||
}
|
||||
|
||||
EXIT:
|
||||
if n.server != nil {
|
||||
n.server.GracefulStop()
|
||||
}
|
||||
}
|
||||
|
||||
// Enable current server accept connection
|
||||
func (n *Node) listenAndServe() {
|
||||
listener, err := net.Listen("tcp", n.ClientAddr)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
defer listener.Close()
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
go n.handler.handle(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) listenAndServeWS() {
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: env.CheckOrigin,
|
||||
}
|
||||
|
||||
http.HandleFunc("/"+strings.TrimPrefix(env.WSPath, "/"), func(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println(fmt.Sprintf("Upgrade failure, URI=%s, Error=%s", r.RequestURI, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
n.handler.handleWS(conn)
|
||||
})
|
||||
|
||||
if err := http.ListenAndServe(n.ClientAddr, nil); err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) listenAndServeWSTLS() {
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: env.CheckOrigin,
|
||||
}
|
||||
|
||||
http.HandleFunc("/"+strings.TrimPrefix(env.WSPath, "/"), func(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println(fmt.Sprintf("Upgrade failure, URI=%s, Error=%s", r.RequestURI, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
n.handler.handleWS(conn)
|
||||
})
|
||||
|
||||
if err := http.ListenAndServeTLS(n.ClientAddr, n.TSLCertificate, n.TSLKey, nil); err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) storeSession(s *session.Session) {
|
||||
n.mu.Lock()
|
||||
n.sessions[s.ID()] = s
|
||||
n.mu.Unlock()
|
||||
}
|
||||
|
||||
func (n *Node) findSession(sid int64) *session.Session {
|
||||
n.mu.RLock()
|
||||
s := n.sessions[sid]
|
||||
n.mu.RUnlock()
|
||||
return s
|
||||
}
|
||||
|
||||
func (n *Node) findOrCreateSession(sid int64, gateAddr string) (*session.Session, error) {
|
||||
n.mu.RLock()
|
||||
s, found := n.sessions[sid]
|
||||
n.mu.RUnlock()
|
||||
if !found {
|
||||
conns, err := n.rpcClient.getConnPool(gateAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ac := &acceptor{
|
||||
sid: sid,
|
||||
gateClient: clusterpb.NewMemberClient(conns.Get()),
|
||||
rpcHandler: n.handler.remoteProcess,
|
||||
gateAddr: gateAddr,
|
||||
}
|
||||
s = session.New(ac)
|
||||
ac.session = s
|
||||
n.mu.Lock()
|
||||
n.sessions[sid] = s
|
||||
n.mu.Unlock()
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (n *Node) HandleRequest(_ context.Context, req *clusterpb.RequestMessage) (*clusterpb.MemberHandleResponse, error) {
|
||||
handler, found := n.handler.localHandlers[req.Route]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("service not found in current node: %v", req.Route)
|
||||
}
|
||||
s, err := n.findOrCreateSession(req.SessionId, req.GateAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg := &message.Message{
|
||||
Type: message.Request,
|
||||
ID: req.Id,
|
||||
Route: req.Route,
|
||||
Data: req.Data,
|
||||
}
|
||||
n.handler.localProcess(handler, req.Id, s, msg)
|
||||
return &clusterpb.MemberHandleResponse{}, nil
|
||||
}
|
||||
|
||||
func (n *Node) HandleNotify(_ context.Context, req *clusterpb.NotifyMessage) (*clusterpb.MemberHandleResponse, error) {
|
||||
handler, found := n.handler.localHandlers[req.Route]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("service not found in current node: %v", req.Route)
|
||||
}
|
||||
s, err := n.findOrCreateSession(req.SessionId, req.GateAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg := &message.Message{
|
||||
Type: message.Notify,
|
||||
Route: req.Route,
|
||||
Data: req.Data,
|
||||
}
|
||||
n.handler.localProcess(handler, 0, s, msg)
|
||||
return &clusterpb.MemberHandleResponse{}, nil
|
||||
}
|
||||
|
||||
func (n *Node) HandlePush(_ context.Context, req *clusterpb.PushMessage) (*clusterpb.MemberHandleResponse, error) {
|
||||
s := n.findSession(req.SessionId)
|
||||
if s == nil {
|
||||
return &clusterpb.MemberHandleResponse{}, fmt.Errorf("session not found: %v", req.SessionId)
|
||||
}
|
||||
return &clusterpb.MemberHandleResponse{}, s.Push(req.Route, req.Data)
|
||||
}
|
||||
|
||||
func (n *Node) HandleResponse(_ context.Context, req *clusterpb.ResponseMessage) (*clusterpb.MemberHandleResponse, error) {
|
||||
s := n.findSession(req.SessionId)
|
||||
if s == nil {
|
||||
return &clusterpb.MemberHandleResponse{}, fmt.Errorf("session not found: %v", req.SessionId)
|
||||
}
|
||||
return &clusterpb.MemberHandleResponse{}, s.ResponseMID(req.Id, req.Data)
|
||||
}
|
||||
|
||||
func (n *Node) NewMember(_ context.Context, req *clusterpb.NewMemberRequest) (*clusterpb.NewMemberResponse, error) {
|
||||
n.handler.addRemoteService(req.MemberInfo)
|
||||
n.cluster.addMember(req.MemberInfo)
|
||||
return &clusterpb.NewMemberResponse{}, nil
|
||||
}
|
||||
|
||||
func (n *Node) DelMember(_ context.Context, req *clusterpb.DelMemberRequest) (*clusterpb.DelMemberResponse, error) {
|
||||
n.handler.delMember(req.ServiceAddr)
|
||||
n.cluster.delMember(req.ServiceAddr)
|
||||
return &clusterpb.DelMemberResponse{}, nil
|
||||
}
|
||||
|
||||
// SessionClosed implements the MemberServer interface
|
||||
func (n *Node) SessionClosed(_ context.Context, req *clusterpb.SessionClosedRequest) (*clusterpb.SessionClosedResponse, error) {
|
||||
n.mu.Lock()
|
||||
s, found := n.sessions[req.SessionId]
|
||||
delete(n.sessions, req.SessionId)
|
||||
n.mu.Unlock()
|
||||
if found {
|
||||
scheduler.PushTask(func() { session.Lifetime.Close(s) })
|
||||
}
|
||||
return &clusterpb.SessionClosedResponse{}, nil
|
||||
}
|
||||
|
||||
// CloseSession implements the MemberServer interface
|
||||
func (n *Node) CloseSession(_ context.Context, req *clusterpb.CloseSessionRequest) (*clusterpb.CloseSessionResponse, error) {
|
||||
n.mu.Lock()
|
||||
s, found := n.sessions[req.SessionId]
|
||||
delete(n.sessions, req.SessionId)
|
||||
n.mu.Unlock()
|
||||
if found {
|
||||
s.Close()
|
||||
}
|
||||
return &clusterpb.CloseSessionResponse{}, nil
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package cluster
|
||||
|
||||
const (
|
||||
_ int32 = iota
|
||||
statusStart // 1
|
||||
statusHandshake
|
||||
statusWorking
|
||||
statusClosed
|
||||
)
|
@ -0,0 +1,116 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"github.com/gorilla/websocket"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// wsConn is an adapter to t.Conn, which implements all t.Conn
|
||||
// interface base on *websocket.Conn
|
||||
type wsConn struct {
|
||||
conn *websocket.Conn
|
||||
typ int // message type
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
// newWSConn return an initialized *wsConn
|
||||
func newWSConn(conn *websocket.Conn) (*wsConn, error) {
|
||||
c := &wsConn{conn: conn}
|
||||
|
||||
t, r, err := conn.NextReader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.typ = t
|
||||
c.reader = r
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Read reads data from the connection.
|
||||
// Read can be made to time out and return an Error with Timeout() == true
|
||||
// after a fixed time limit; see SetDeadline and SetReadDeadline.
|
||||
func (c *wsConn) Read(b []byte) (int, error) {
|
||||
n, err := c.reader.Read(b)
|
||||
if err != nil && err != io.EOF {
|
||||
return n, err
|
||||
} else if err == io.EOF {
|
||||
_, r, err := c.conn.NextReader()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
c.reader = r
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Write writes data to the connection.
|
||||
// Write can be made to time out and return an Error with Timeout() == true
|
||||
// after a fixed time limit; see SetDeadline and SetWriteDeadline.
|
||||
func (c *wsConn) Write(b []byte) (int, error) {
|
||||
err := c.conn.WriteMessage(websocket.BinaryMessage, b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// Close closes the connection.
|
||||
// Any blocked Read or Write operations will be unblocked and return errors.
|
||||
func (c *wsConn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
// LocalAddr returns the local network address.
|
||||
func (c *wsConn) LocalAddr() net.Addr {
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote network address.
|
||||
func (c *wsConn) RemoteAddr() net.Addr {
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
// SetDeadline sets the read and write deadlines associated
|
||||
// with the connection. It is equivalent to calling both
|
||||
// SetReadDeadline and SetWriteDeadline.
|
||||
//
|
||||
// A deadline is an absolute time after which I/O operations
|
||||
// fail with a timeout (see type Error) instead of
|
||||
// blocking. The deadline applies to all future and pending
|
||||
// I/O, not just the immediately following call to Read or
|
||||
// Write. After a deadline has been exceeded, the connection
|
||||
// can be refreshed by setting a deadline in the future.
|
||||
//
|
||||
// An idle timeout can be implemented by repeatedly extending
|
||||
// the deadline after successful Read or Write calls.
|
||||
//
|
||||
// A zero value for t means I/O operations will not time out.
|
||||
func (c *wsConn) SetDeadline(t time.Time) error {
|
||||
if err := c.conn.SetReadDeadline(t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
// SetReadDeadline sets the deadline for future Read calls
|
||||
// and any currently-blocked Read call.
|
||||
// A zero value for t means Read will not time out.
|
||||
func (c *wsConn) SetReadDeadline(t time.Time) error {
|
||||
return c.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
// SetWriteDeadline sets the deadline for future Write calls
|
||||
// and any currently-blocked Write call.
|
||||
// Even if write times out, it may return n > 0, indicating that
|
||||
// some of the data was successfully written.
|
||||
// A zero value for t means Write will not time out.
|
||||
func (c *wsConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.conn.SetWriteDeadline(t)
|
||||
}
|
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,11 @@
|
||||
package ngs
|
||||
|
||||
import "errors"
|
||||
|
||||
// Errors that could be occurred during message handling.
|
||||
var (
|
||||
ErrCloseClosedGroup = errors.New("close closed group")
|
||||
ErrClosedGroup = errors.New("group closed")
|
||||
ErrMemberNotFound = errors.New("member not found in the group")
|
||||
ErrSessionDuplication = errors.New("session has existed in the current group")
|
||||
)
|
@ -1,3 +1,17 @@
|
||||
module ng
|
||||
module ngs
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
google.golang.org/grpc v1.45.0
|
||||
google.golang.org/protobuf v1.28.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2 // indirect
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4 // indirect
|
||||
)
|
||||
|
@ -0,0 +1,135 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2 h1:6mzvA99KwZxbOrxww4EvWVQUnN1+xEu9tafK5ZxkYeA=
|
||||
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4 h1:myaecH64R0bIEDjNORIel4iXubqzaHU1K2z8ajBwWcM=
|
||||
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
|
||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
@ -0,0 +1,209 @@
|
||||
package ngs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"ngs/internal/env"
|
||||
"ngs/internal/log"
|
||||
"ngs/internal/message"
|
||||
"ngs/session"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
const (
|
||||
groupStatusWorking = 0
|
||||
groupStatusClosed = 1
|
||||
)
|
||||
|
||||
// SessionFilter represents a filter which was used to filter session when Multicast,
|
||||
// the session will receive the message while filter returns true.
|
||||
type SessionFilter func(*session.Session) bool
|
||||
|
||||
// Group represents a session group which used to manage a number of
|
||||
// sessions, data send to the group will send to all session in it.
|
||||
type Group struct {
|
||||
mu sync.RWMutex
|
||||
status int32 // channel current status
|
||||
name string // channel name
|
||||
sessions map[int64]*session.Session // session id map to session instance
|
||||
}
|
||||
|
||||
// NewGroup returns a new group instance
|
||||
func NewGroup(n string) *Group {
|
||||
return &Group{
|
||||
status: groupStatusWorking,
|
||||
name: n,
|
||||
sessions: make(map[int64]*session.Session),
|
||||
}
|
||||
}
|
||||
|
||||
// Member returns specified UID's session
|
||||
func (c *Group) Member(uid int64) (*session.Session, error) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
for _, s := range c.sessions {
|
||||
if s.UID() == uid {
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrMemberNotFound
|
||||
}
|
||||
|
||||
// Members returns all member's UID in current group
|
||||
func (c *Group) Members() []int64 {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
var members []int64
|
||||
for _, s := range c.sessions {
|
||||
members = append(members, s.UID())
|
||||
}
|
||||
|
||||
return members
|
||||
}
|
||||
|
||||
// Multicast push the message to the filtered clients
|
||||
func (c *Group) Multicast(route string, v interface{}, filter SessionFilter) error {
|
||||
if c.isClosed() {
|
||||
return ErrClosedGroup
|
||||
}
|
||||
|
||||
data, err := message.Serialize(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if env.Debug {
|
||||
log.Println(fmt.Sprintf("Multicast %s, Data=%+v", route, v))
|
||||
}
|
||||
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
for _, s := range c.sessions {
|
||||
if !filter(s) {
|
||||
continue
|
||||
}
|
||||
if err = s.Push(route, data); err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Broadcast push the message(s) to all members
|
||||
func (c *Group) Broadcast(route string, v interface{}) error {
|
||||
if c.isClosed() {
|
||||
return ErrClosedGroup
|
||||
}
|
||||
|
||||
data, err := message.Serialize(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if env.Debug {
|
||||
log.Println(fmt.Sprintf("Broadcast %s, Data=%+v", route, v))
|
||||
}
|
||||
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
for _, s := range c.sessions {
|
||||
if err = s.Push(route, data); err != nil {
|
||||
log.Println(fmt.Sprintf("Session push message error, ID=%d, UID=%d, Error=%s", s.ID(), s.UID(), err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Contains check whether a UID is contained in current group or not
|
||||
func (c *Group) Contains(uid int64) bool {
|
||||
_, err := c.Member(uid)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Add add session to group
|
||||
func (c *Group) Add(session *session.Session) error {
|
||||
if c.isClosed() {
|
||||
return ErrClosedGroup
|
||||
}
|
||||
|
||||
if env.Debug {
|
||||
log.Println(fmt.Sprintf("Add session to group %s, ID=%d, UID=%d", c.name, session.ID(), session.UID()))
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
id := session.ID()
|
||||
_, ok := c.sessions[session.ID()]
|
||||
if ok {
|
||||
return ErrSessionDuplication
|
||||
}
|
||||
|
||||
c.sessions[id] = session
|
||||
return nil
|
||||
}
|
||||
|
||||
// Leave remove specified UID related session from group
|
||||
func (c *Group) Leave(s *session.Session) error {
|
||||
if c.isClosed() {
|
||||
return ErrClosedGroup
|
||||
}
|
||||
|
||||
if env.Debug {
|
||||
log.Println(fmt.Sprintf("Remove session from group %s, UID=%d", c.name, s.UID()))
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
delete(c.sessions, s.ID())
|
||||
return nil
|
||||
}
|
||||
|
||||
// LeaveAll clear all sessions in the group
|
||||
func (c *Group) LeaveAll() error {
|
||||
if c.isClosed() {
|
||||
return ErrClosedGroup
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.sessions = make(map[int64]*session.Session)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count get current member amount in the group
|
||||
func (c *Group) Count() int {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
return len(c.sessions)
|
||||
}
|
||||
|
||||
func (c *Group) isClosed() bool {
|
||||
if atomic.LoadInt32(&c.status) == groupStatusClosed {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Close destroy group, which will release all resource in the group
|
||||
func (c *Group) Close() error {
|
||||
if c.isClosed() {
|
||||
return ErrCloseClosedGroup
|
||||
}
|
||||
|
||||
atomic.StoreInt32(&c.status, groupStatusClosed)
|
||||
|
||||
// release all reference
|
||||
c.sessions = make(map[int64]*session.Session)
|
||||
return nil
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package ngs
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"ngs/session"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestChannel_Add(t *testing.T) {
|
||||
c := NewGroup("test_add")
|
||||
|
||||
var paraCount = 100
|
||||
w := make(chan bool, paraCount)
|
||||
for i := 0; i < paraCount; i++ {
|
||||
go func(id int) {
|
||||
s := session.New(nil)
|
||||
s.Bind(int64(id + 1))
|
||||
c.Add(s)
|
||||
w <- true
|
||||
}(i)
|
||||
}
|
||||
|
||||
for i := 0; i < paraCount; i++ {
|
||||
<-w
|
||||
}
|
||||
|
||||
if c.Count() != paraCount {
|
||||
t.Fatalf("count expect: %d, got: %d", paraCount, c.Count())
|
||||
}
|
||||
|
||||
n := rand.Int63n(int64(paraCount)) + 1
|
||||
if !c.Contains(n) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// leave
|
||||
c.LeaveAll()
|
||||
if c.Count() != 0 {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
package ngs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"ngs/cluster"
|
||||
"ngs/component"
|
||||
"ngs/internal/env"
|
||||
"ngs/internal/log"
|
||||
"ngs/internal/runtime"
|
||||
"ngs/scheduler"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
var running int32
|
||||
|
||||
// VERSION returns current ngs version
|
||||
var VERSION = "0.1.0"
|
||||
|
||||
var (
|
||||
// app represents the current server process
|
||||
app = &struct {
|
||||
name string // current application name
|
||||
startAt time.Time // startup time
|
||||
}{}
|
||||
)
|
||||
|
||||
// Listen listens on the TCP network address addr
|
||||
// and then calls Serve with handler to handle requests
|
||||
// on incoming connections.
|
||||
func Listen(addr string, opts ...Option) {
|
||||
if atomic.AddInt32(&running, 1) != 1 {
|
||||
log.Println("Ngs has running")
|
||||
return
|
||||
}
|
||||
|
||||
// application initialize
|
||||
app.name = strings.TrimLeft(filepath.Base(os.Args[0]), "/")
|
||||
app.startAt = time.Now()
|
||||
|
||||
// environment initialize
|
||||
if wd, err := os.Getwd(); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
env.Wd, _ = filepath.Abs(wd)
|
||||
}
|
||||
|
||||
opt := cluster.Options{
|
||||
Components: &component.Components{},
|
||||
}
|
||||
for _, option := range opts {
|
||||
option(&opt)
|
||||
}
|
||||
|
||||
// Use listen address as client address in non-cluster mode
|
||||
if !opt.IsMaster && opt.AdvertiseAddr == "" && opt.ClientAddr == "" {
|
||||
log.Println("The current server running in singleton mode")
|
||||
opt.ClientAddr = addr
|
||||
}
|
||||
|
||||
// Set the retry interval to 3 secondes if doesn't set by user
|
||||
if opt.RetryInterval == 0 {
|
||||
opt.RetryInterval = time.Second * 3
|
||||
}
|
||||
|
||||
node := &cluster.Node{
|
||||
Options: opt,
|
||||
ServiceAddr: addr,
|
||||
}
|
||||
err := node.Startup()
|
||||
if err != nil {
|
||||
log.Fatalf("Node startup failed: %v", err)
|
||||
}
|
||||
runtime.CurrentNode = node
|
||||
|
||||
if node.ClientAddr != "" {
|
||||
log.Println(fmt.Sprintf("Startup *Ngs gate server* %s, client address: %v, service address: %s",
|
||||
app.name, node.ClientAddr, node.ServiceAddr))
|
||||
} else {
|
||||
log.Println(fmt.Sprintf("Startup *Ngs backend server* %s, service address %s",
|
||||
app.name, node.ServiceAddr))
|
||||
}
|
||||
|
||||
go scheduler.Schedule()
|
||||
sg := make(chan os.Signal)
|
||||
signal.Notify(sg, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM)
|
||||
|
||||
select {
|
||||
case <-env.Die:
|
||||
log.Println("The app will shutdown in a few seconds")
|
||||
case s := <-sg:
|
||||
log.Println("Ngs server got signal", s)
|
||||
}
|
||||
|
||||
log.Println("Ngs server is stopping...")
|
||||
|
||||
node.Shutdown()
|
||||
runtime.CurrentNode = nil
|
||||
scheduler.Close()
|
||||
atomic.StoreInt32(&running, 0)
|
||||
}
|
||||
|
||||
// Shutdown send a signal to let 'ngs' shutdown itself.
|
||||
func Shutdown() {
|
||||
close(env.Die)
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"net/http"
|
||||
"ngs/serialize"
|
||||
"ngs/serialize/protobuf"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
Wd string // working path
|
||||
Die chan bool // wait for end application
|
||||
Heartbeat time.Duration // Heartbeat internal
|
||||
CheckOrigin func(*http.Request) bool // check origin when websocket enabled
|
||||
Debug bool // enable Debug
|
||||
WSPath string // WebSocket path(eg: ws://127.0.0.1/WSPath)
|
||||
HandshakeValidator func([]byte) error // When you need to verify the custom data of the handshake request
|
||||
|
||||
// TimerPrecision indicates the precision of timer, default is time.Second
|
||||
TimerPrecision = time.Second
|
||||
|
||||
// GlobalTicker represents global ticker that all cron job will be executed
|
||||
// in globalTicker.
|
||||
GlobalTicker *time.Ticker
|
||||
|
||||
Serializer serialize.Serializer
|
||||
|
||||
GrpcOptions = []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
|
||||
)
|
||||
|
||||
func init() {
|
||||
Die = make(chan bool)
|
||||
Heartbeat = 30 * time.Second
|
||||
Debug = false
|
||||
CheckOrigin = func(_ *http.Request) bool { return true }
|
||||
HandshakeValidator = func(_ []byte) error { return nil }
|
||||
Serializer = protobuf.NewSerializer()
|
||||
}
|
@ -0,0 +1,222 @@
|
||||
package message
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"ngs/internal/log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Type represents the type of message, which could be Request/Notify/Response/Push
|
||||
type Type byte
|
||||
|
||||
// Message types
|
||||
const (
|
||||
Request Type = 0x00
|
||||
Notify = 0x01
|
||||
Response = 0x02
|
||||
Push = 0x03
|
||||
)
|
||||
|
||||
const (
|
||||
msgRouteCompressMask = 0x01 // 0000 0001 last bit
|
||||
msgTypeMask = 0x07 // 0000 0111 1-3 bit (需要>>)
|
||||
msgRouteLengthMask = 0xFF // 1111 1111 last 8 bit
|
||||
msgHeadLength = 0x02 // 0000 0010 2 bit
|
||||
)
|
||||
|
||||
var types = map[Type]string{
|
||||
Request: "Request",
|
||||
Notify: "Notify",
|
||||
Response: "Response",
|
||||
Push: "Push",
|
||||
}
|
||||
|
||||
func (t Type) String() string {
|
||||
return types[t]
|
||||
}
|
||||
|
||||
var (
|
||||
routes = make(map[string]uint16) // route map to code
|
||||
codes = make(map[uint16]string) // code map to route
|
||||
)
|
||||
|
||||
// Errors that could be occurred in message codec
|
||||
var (
|
||||
ErrWrongMessageType = errors.New("wrong message type")
|
||||
ErrInvalidMessage = errors.New("invalid message")
|
||||
ErrRouteInfoNotFound = errors.New("route info not found in dictionary")
|
||||
ErrWrongMessage = errors.New("wrong message")
|
||||
)
|
||||
|
||||
// Message represents an unmarshaler message or a message which to be marshaled
|
||||
type Message struct {
|
||||
Type Type // message type (flag)
|
||||
ID uint64 // unique id, zero while notify mode
|
||||
Route string // route for locating service
|
||||
Data []byte // payload
|
||||
compressed bool // if message compressed
|
||||
}
|
||||
|
||||
// New returns a new message instance
|
||||
func New() *Message {
|
||||
return &Message{}
|
||||
}
|
||||
|
||||
// String, implementation of fmt.Stringer interface
|
||||
func (m *Message) String() string {
|
||||
return fmt.Sprintf("%s %s (%dbytes)", types[m.Type], m.Route, len(m.Data))
|
||||
}
|
||||
|
||||
// Encode marshals message to binary format.
|
||||
func (m *Message) Encode() ([]byte, error) {
|
||||
return Encode(m)
|
||||
}
|
||||
|
||||
func routable(t Type) bool {
|
||||
return t == Request || t == Notify || t == Push
|
||||
}
|
||||
|
||||
func invalidType(t Type) bool {
|
||||
return t < Request || t > Push
|
||||
}
|
||||
|
||||
// Encode marshals message to binary format. Different message types is corresponding to
|
||||
// different message header, message types is identified by 2-4 bit of flag field. The
|
||||
// relationship between message types and message header is presented as follows:
|
||||
// ------------------------------------------
|
||||
// | type | flag | other |
|
||||
// |----------|--------|--------------------|
|
||||
// | request |----000-|<message id>|<route>|
|
||||
// | notify |----001-|<route> |
|
||||
// | response |----010-|<message id> |
|
||||
// | push |----011-|<route> |
|
||||
// ------------------------------------------
|
||||
// The figure above indicates that the bit does not affect the type of message.
|
||||
func Encode(m *Message) ([]byte, error) {
|
||||
if invalidType(m.Type) {
|
||||
return nil, ErrWrongMessageType
|
||||
}
|
||||
|
||||
buf := make([]byte, 0)
|
||||
flag := byte(m.Type << 1) // 编译器提示,此处 byte 转换不能删
|
||||
|
||||
code, compressed := routes[m.Route]
|
||||
if compressed {
|
||||
flag |= msgRouteCompressMask
|
||||
}
|
||||
buf = append(buf, flag)
|
||||
|
||||
if m.Type == Request || m.Type == Response {
|
||||
n := m.ID
|
||||
// variant length encode
|
||||
for {
|
||||
b := byte(n % 128)
|
||||
n >>= 7
|
||||
if n != 0 {
|
||||
buf = append(buf, b+128)
|
||||
} else {
|
||||
buf = append(buf, b)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if routable(m.Type) {
|
||||
if compressed {
|
||||
buf = append(buf, byte((code>>8)&0xFF))
|
||||
buf = append(buf, byte(code&0xFF))
|
||||
} else {
|
||||
buf = append(buf, byte(len(m.Route)))
|
||||
buf = append(buf, []byte(m.Route)...)
|
||||
}
|
||||
}
|
||||
|
||||
buf = append(buf, m.Data...)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Decode unmarshal the bytes slice to a message
|
||||
func Decode(data []byte) (*Message, error) {
|
||||
if len(data) < msgHeadLength {
|
||||
return nil, ErrInvalidMessage
|
||||
}
|
||||
m := New()
|
||||
flag := data[0]
|
||||
offset := 1
|
||||
m.Type = Type((flag >> 1) & msgTypeMask) // 编译器提示,此处Type转换不能删
|
||||
|
||||
if invalidType(m.Type) {
|
||||
return nil, ErrWrongMessageType
|
||||
}
|
||||
|
||||
if m.Type == Request || m.Type == Response {
|
||||
id := uint64(0)
|
||||
// little end byte order
|
||||
// WARNING: must can be stored in 64 bits integer
|
||||
// variant length encode
|
||||
for i := offset; i < len(data); i++ {
|
||||
b := data[i]
|
||||
id += uint64(b&0x7F) << uint64(7*(i-offset))
|
||||
if b < 128 {
|
||||
offset = i + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
m.ID = id
|
||||
}
|
||||
|
||||
if offset >= len(data) {
|
||||
return nil, ErrWrongMessage
|
||||
}
|
||||
|
||||
if routable(m.Type) {
|
||||
if flag&msgRouteCompressMask == 1 {
|
||||
m.compressed = true
|
||||
code := binary.BigEndian.Uint16(data[offset:(offset + 2)])
|
||||
route, ok := codes[code]
|
||||
if !ok {
|
||||
return nil, ErrRouteInfoNotFound
|
||||
}
|
||||
m.Route = route
|
||||
offset += 2
|
||||
} else {
|
||||
m.compressed = false
|
||||
rl := data[offset]
|
||||
offset++
|
||||
if offset+int(rl) > len(data) {
|
||||
return nil, ErrWrongMessage
|
||||
}
|
||||
m.Route = string(data[offset:(offset + int(rl))])
|
||||
offset += int(rl)
|
||||
}
|
||||
}
|
||||
|
||||
if offset > len(data) {
|
||||
return nil, ErrWrongMessage
|
||||
}
|
||||
m.Data = data[offset:]
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// SetDictionary set routes map which be used to compress route.
|
||||
// TODO(warning): set dictionary in runtime would be a dangerous operation!!!!!!
|
||||
func SetDictionary(dict map[string]uint16) {
|
||||
for route, code := range dict {
|
||||
r := strings.TrimSpace(route)
|
||||
|
||||
// duplication check
|
||||
if _, ok := routes[r]; ok {
|
||||
log.Println(fmt.Sprintf("duplicated route(route: %s, code: %d)", r, code))
|
||||
}
|
||||
|
||||
if _, ok := codes[code]; ok {
|
||||
log.Println(fmt.Sprintf("duplicated route(route: %s, code: %d)", r, code))
|
||||
}
|
||||
|
||||
// update map, using last value when key duplicated
|
||||
routes[r] = code
|
||||
codes[code] = r
|
||||
}
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
package message
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
dict := map[string]uint16{
|
||||
"test.test.test": 100,
|
||||
"test.test.test1": 101,
|
||||
"test.test.test2": 102,
|
||||
"test.test.test3": 103,
|
||||
}
|
||||
SetDictionary(dict)
|
||||
m1 := &Message{
|
||||
Type: Request,
|
||||
ID: 100,
|
||||
Route: "test.test.test",
|
||||
Data: []byte(`hello world`),
|
||||
compressed: true,
|
||||
}
|
||||
em1, err := m1.Encode()
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
dm1, err := Decode(em1)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(m1, dm1) {
|
||||
t.Error("not equal")
|
||||
}
|
||||
|
||||
m2 := &Message{
|
||||
Type: Request,
|
||||
ID: 100,
|
||||
Route: "test.test.test4",
|
||||
Data: []byte(`hello world`),
|
||||
}
|
||||
em2, err := m2.Encode()
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
dm2, err := Decode(em2)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(m2, dm2) {
|
||||
t.Error("not equal")
|
||||
}
|
||||
|
||||
m3 := &Message{
|
||||
Type: Response,
|
||||
ID: 100,
|
||||
Data: []byte(`hello world`),
|
||||
}
|
||||
em3, err := m3.Encode()
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
dm3, err := Decode(em3)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(m3, dm3) {
|
||||
t.Error("not equal")
|
||||
}
|
||||
|
||||
m4 := &Message{
|
||||
Type: Response,
|
||||
ID: 100,
|
||||
Data: []byte(`hello world`),
|
||||
}
|
||||
em4, err := m4.Encode()
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
dm4, err := Decode(em4)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(m4, dm4) {
|
||||
t.Error("not equal")
|
||||
}
|
||||
|
||||
m5 := &Message{
|
||||
Type: Notify,
|
||||
Route: "test.test.test",
|
||||
Data: []byte(`hello world`),
|
||||
compressed: true,
|
||||
}
|
||||
em5, err := m5.Encode()
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
dm5, err := Decode(em5)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(m5, dm5) {
|
||||
t.Error("not equal")
|
||||
}
|
||||
|
||||
m6 := &Message{
|
||||
Type: Notify,
|
||||
Route: "test.test.test20",
|
||||
Data: []byte(`hello world`),
|
||||
}
|
||||
em6, err := m6.Encode()
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
dm6, err := Decode(em6)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(m6, dm6) {
|
||||
t.Error("not equal")
|
||||
}
|
||||
|
||||
m7 := &Message{
|
||||
Type: Push,
|
||||
Route: "test.test.test9",
|
||||
Data: []byte(`hello world`),
|
||||
}
|
||||
em7, err := m7.Encode()
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
dm7, err := Decode(em7)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(m7, dm7) {
|
||||
t.Error("not equal")
|
||||
}
|
||||
|
||||
m8 := &Message{
|
||||
Type: Push,
|
||||
Route: "test.test.test3",
|
||||
Data: []byte(`hello world`),
|
||||
compressed: true,
|
||||
}
|
||||
em8, err := m8.Encode()
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
dm8, err := Decode(em8)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(m8, dm8) {
|
||||
t.Error("not equal")
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package message
|
||||
|
||||
import "ngs/internal/env"
|
||||
|
||||
func Serialize(v interface{}) ([]byte, error) {
|
||||
if data, ok := v.([]byte); ok {
|
||||
return data, nil
|
||||
}
|
||||
data, err := env.Serializer.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package runtime
|
||||
|
||||
import "ngs/cluster"
|
||||
|
||||
var CurrentNode *cluster.Node
|
@ -0,0 +1,10 @@
|
||||
package mock
|
||||
|
||||
// NetAddr mock the net.Addr interface
|
||||
type NetAddr struct{}
|
||||
|
||||
// Network implements the net.Addr interface
|
||||
func (a NetAddr) Network() string { return "mock" }
|
||||
|
||||
// String implements the net.Addr interface
|
||||
func (a NetAddr) String() string { return "mock-addr" }
|
@ -0,0 +1,153 @@
|
||||
package ngs
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc"
|
||||
"net/http"
|
||||
"ngs/cluster"
|
||||
"ngs/component"
|
||||
"ngs/internal/env"
|
||||
"ngs/internal/log"
|
||||
"ngs/internal/message"
|
||||
"ngs/pipeline"
|
||||
"ngs/serialize"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Option func(*cluster.Options)
|
||||
|
||||
func WithPipeline(pipeline pipeline.Pipeline) Option {
|
||||
return func(opt *cluster.Options) {
|
||||
opt.Pipeline = pipeline
|
||||
}
|
||||
}
|
||||
|
||||
// WithAdvertiseAddr sets the advertisement address option, it will be the listen address in
|
||||
// master node and an advertisement address which cluster member to connect
|
||||
func WithAdvertiseAddr(addr string, retryInterval ...time.Duration) Option {
|
||||
return func(opt *cluster.Options) {
|
||||
opt.AdvertiseAddr = addr
|
||||
if len(retryInterval) > 0 {
|
||||
opt.RetryInterval = retryInterval[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithClientAddr sets the listen address which is used to establish connection between
|
||||
// cluster members. Will select an available port automatically if no member address
|
||||
// setting and panic if no available port
|
||||
func WithClientAddr(addr string) Option {
|
||||
return func(opt *cluster.Options) {
|
||||
opt.ClientAddr = addr
|
||||
}
|
||||
}
|
||||
|
||||
// WithMaster sets the option to indicate whether the current node is master node
|
||||
func WithMaster() Option {
|
||||
return func(opt *cluster.Options) {
|
||||
opt.IsMaster = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithGrpcOptions sets the grpc dial options
|
||||
func WithGrpcOptions(opts ...grpc.DialOption) Option {
|
||||
return func(_ *cluster.Options) {
|
||||
env.GrpcOptions = append(env.GrpcOptions, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
// WithComponents sets the Components
|
||||
func WithComponents(components *component.Components) Option {
|
||||
return func(opt *cluster.Options) {
|
||||
opt.Components = components
|
||||
}
|
||||
}
|
||||
|
||||
// WithHeartbeatInterval sets Heartbeat time interval
|
||||
func WithHeartbeatInterval(d time.Duration) Option {
|
||||
return func(_ *cluster.Options) {
|
||||
env.Heartbeat = d
|
||||
}
|
||||
}
|
||||
|
||||
// WithCheckOriginFunc sets the function that check `Origin` in http headers
|
||||
func WithCheckOriginFunc(fn func(*http.Request) bool) Option {
|
||||
return func(opt *cluster.Options) {
|
||||
env.CheckOrigin = fn
|
||||
}
|
||||
}
|
||||
|
||||
// WithDebugMode let 'ngs' to run under Debug mode.
|
||||
func WithDebugMode() Option {
|
||||
return func(_ *cluster.Options) {
|
||||
env.Debug = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithDictionary sets routes map
|
||||
func WithDictionary(dict map[string]uint16) Option {
|
||||
return func(_ *cluster.Options) {
|
||||
message.SetDictionary(dict)
|
||||
}
|
||||
}
|
||||
|
||||
func WithWSPath(path string) Option {
|
||||
return func(_ *cluster.Options) {
|
||||
env.WSPath = path
|
||||
}
|
||||
}
|
||||
|
||||
// WithTimerPrecision sets the ticker precision, and time precision can not less
|
||||
// than a Millisecond, and can not change after application running. The default
|
||||
// precision is time.Second
|
||||
func WithTimerPrecision(precision time.Duration) Option {
|
||||
if precision < time.Millisecond {
|
||||
panic("time precision can not less than a Millisecond")
|
||||
}
|
||||
return func(_ *cluster.Options) {
|
||||
env.TimerPrecision = precision
|
||||
}
|
||||
}
|
||||
|
||||
// WithSerializer customizes application serializer, which automatically Marshal
|
||||
// and UnMarshal handler payload
|
||||
func WithSerializer(serializer serialize.Serializer) Option {
|
||||
return func(opt *cluster.Options) {
|
||||
env.Serializer = serializer
|
||||
}
|
||||
}
|
||||
|
||||
// WithLabel sets the current node label in cluster
|
||||
func WithLabel(label string) Option {
|
||||
return func(opt *cluster.Options) {
|
||||
opt.Label = label
|
||||
}
|
||||
}
|
||||
|
||||
// WithIsWebsocket indicates whether current node WebSocket is enabled
|
||||
func WithIsWebsocket(enableWs bool) Option {
|
||||
return func(opt *cluster.Options) {
|
||||
opt.IsWebsocket = enableWs
|
||||
}
|
||||
}
|
||||
|
||||
// WithTSLConfig sets the `key` and `certificate` of TSL
|
||||
func WithTSLConfig(certificate, key string) Option {
|
||||
return func(opt *cluster.Options) {
|
||||
opt.TSLCertificate = certificate
|
||||
opt.TSLKey = key
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger overrides the default logger
|
||||
func WithLogger(l log.Logger) Option {
|
||||
return func(opt *cluster.Options) {
|
||||
log.SetLogger(l)
|
||||
}
|
||||
}
|
||||
|
||||
// WithHandshakeValidator sets the function that Verify `handshake` data
|
||||
func WithHandshakeValidator(fn func([]byte) error) Option {
|
||||
return func(opt *cluster.Options) {
|
||||
env.HandshakeValidator = fn
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package pipeline
|
||||
|
||||
import (
|
||||
"ngs/internal/message"
|
||||
"ngs/session"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type (
|
||||
// Message is the alias of `message.Message`
|
||||
Message = message.Message
|
||||
|
||||
Func func(s *session.Session, msg *message.Message) error
|
||||
|
||||
Pipeline interface {
|
||||
Outbound() Channel
|
||||
Inbound() Channel
|
||||
}
|
||||
|
||||
pipeline struct {
|
||||
outbound, inbound *pipelineChannel
|
||||
}
|
||||
|
||||
Channel interface {
|
||||
PushFront(h Func)
|
||||
PushBack(h Func)
|
||||
Process(s *session.Session, msg *message.Message) error
|
||||
}
|
||||
|
||||
pipelineChannel struct {
|
||||
mu sync.RWMutex
|
||||
handlers []Func
|
||||
}
|
||||
)
|
||||
|
||||
func New() Pipeline {
|
||||
return &pipeline{
|
||||
outbound: &pipelineChannel{},
|
||||
inbound: &pipelineChannel{},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipeline) Outbound() Channel { return p.outbound }
|
||||
func (p *pipeline) Inbound() Channel { return p.inbound }
|
||||
|
||||
// PushFront push a function to the front of the pipeline
|
||||
func (p *pipelineChannel) PushFront(h Func) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
handlers := make([]Func, len(p.handlers)+1)
|
||||
handlers[0] = h
|
||||
copy(handlers[1:], p.handlers)
|
||||
p.handlers = handlers
|
||||
}
|
||||
|
||||
// PushBack push a function to the end of the pipeline
|
||||
func (p *pipelineChannel) PushBack(h Func) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.handlers = append(p.handlers, h)
|
||||
}
|
||||
|
||||
// Process message with all pipeline functions
|
||||
func (p *pipelineChannel) Process(s *session.Session, msg *message.Message) error {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
if len(p.handlers) < 1 {
|
||||
return nil
|
||||
}
|
||||
for _, h := range p.handlers {
|
||||
err := h(s, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"ngs/internal/env"
|
||||
"ngs/internal/log"
|
||||
"runtime/debug"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
messageQueueBacklog = 1 << 10 // 1024
|
||||
sessionCloseBacklog = 1 << 8 // 256
|
||||
)
|
||||
|
||||
// LocalScheduler schedules task to a customized goroutine
|
||||
type LocalScheduler interface {
|
||||
Schedule(Task)
|
||||
}
|
||||
|
||||
type Task func()
|
||||
|
||||
type Hook func()
|
||||
|
||||
var (
|
||||
chDie = make(chan struct{})
|
||||
chExit = make(chan struct{})
|
||||
chTasks = make(chan Task, 1<<8)
|
||||
started int32
|
||||
closed int32
|
||||
)
|
||||
|
||||
func try(f func()) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Println(fmt.Sprintf("Handle message panic: %+v\n%s", err, debug.Stack()))
|
||||
}
|
||||
}()
|
||||
f()
|
||||
}
|
||||
|
||||
func Schedule() {
|
||||
if atomic.AddInt32(&started, 1) != 1 {
|
||||
return
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(env.TimerPrecision)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
close(chExit)
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
cron()
|
||||
|
||||
case f := <-chTasks:
|
||||
try(f)
|
||||
|
||||
case <-chDie:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Close() {
|
||||
if atomic.AddInt32(&closed, 1) != 1 {
|
||||
return
|
||||
}
|
||||
close(chDie)
|
||||
<-chExit
|
||||
log.Println("Scheduler stopped")
|
||||
}
|
||||
|
||||
func PushTask(task Task) {
|
||||
chTasks <- task
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewTimer(t *testing.T) {
|
||||
var exists = struct {
|
||||
timers int
|
||||
createdTimes int
|
||||
closingTimers int
|
||||
}{
|
||||
timers: len(timerManager.timers),
|
||||
createdTimes: len(timerManager.createdTimer),
|
||||
closingTimers: len(timerManager.closingTimer),
|
||||
}
|
||||
|
||||
const tc = 1000
|
||||
var counter int64
|
||||
for i := 0; i < tc; i++ {
|
||||
NewTimer(1*time.Millisecond, func() {
|
||||
atomic.AddInt64(&counter, 1)
|
||||
})
|
||||
}
|
||||
|
||||
<-time.After(5 * time.Millisecond)
|
||||
cron()
|
||||
cron()
|
||||
if counter != tc*2 {
|
||||
t.Fatalf("expect: %d, got: %d", tc*2, counter)
|
||||
}
|
||||
|
||||
if len(timerManager.timers) != exists.timers+tc {
|
||||
t.Fatalf("timers: %d", len(timerManager.timers))
|
||||
}
|
||||
|
||||
if len(timerManager.createdTimer) != exists.createdTimes {
|
||||
t.Fatalf("createdTimer: %d", len(timerManager.createdTimer))
|
||||
}
|
||||
|
||||
if len(timerManager.closingTimer) != exists.closingTimers {
|
||||
t.Fatalf("closingTimer: %d", len(timerManager.closingTimer))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAfterTimer(t *testing.T) {
|
||||
var exists = struct {
|
||||
timers int
|
||||
createdTimes int
|
||||
closingTimers int
|
||||
}{
|
||||
timers: len(timerManager.timers),
|
||||
createdTimes: len(timerManager.createdTimer),
|
||||
closingTimers: len(timerManager.closingTimer),
|
||||
}
|
||||
|
||||
const tc = 1000
|
||||
var counter int64
|
||||
for i := 0; i < tc; i++ {
|
||||
NewAfterTimer(1*time.Millisecond, func() {
|
||||
atomic.AddInt64(&counter, 1)
|
||||
})
|
||||
}
|
||||
|
||||
<-time.After(5 * time.Millisecond)
|
||||
cron()
|
||||
if counter != tc {
|
||||
t.Fatalf("expect: %d, got: %d", tc, counter)
|
||||
}
|
||||
|
||||
if len(timerManager.timers) != exists.timers {
|
||||
t.Fatalf("timers: %d", len(timerManager.timers))
|
||||
}
|
||||
|
||||
if len(timerManager.createdTimer) != exists.createdTimes {
|
||||
t.Fatalf("createdTimer: %d", len(timerManager.createdTimer))
|
||||
}
|
||||
|
||||
if len(timerManager.closingTimer) != exists.closingTimers {
|
||||
t.Fatalf("closingTimer: %d", len(timerManager.closingTimer))
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"ngs/serialize"
|
||||
)
|
||||
|
||||
// Serializer implements the serialize.Serializer interface
|
||||
type Serializer struct{}
|
||||
|
||||
// NewSerializer returns a new serialize.Serializer.
|
||||
func NewSerializer() serialize.Serializer {
|
||||
return &Serializer{}
|
||||
}
|
||||
|
||||
// Marshal returns the JSON encoding of v.
|
||||
func (s *Serializer) Marshal(v interface{}) ([]byte, error) {
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
// Unmarshal parses the JSON-encoded data and stores the result
|
||||
// in the value pointed to by v.
|
||||
func (s *Serializer) Unmarshal(data []byte, v interface{}) error {
|
||||
return json.Unmarshal(data, v)
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Code int `json:"code"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
func TestSerializer_Serialize(t *testing.T) {
|
||||
m := Message{1, "hello world"}
|
||||
s := NewSerializer()
|
||||
b, err := s.Marshal(m)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
m2 := Message{}
|
||||
if err := s.Unmarshal(b, &m2); err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(m, m2) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSerializer_Serialize(b *testing.B) {
|
||||
m := &Message{100, "hell world"}
|
||||
s := NewSerializer()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := s.Marshal(m); err != nil {
|
||||
b.Fatalf("unmarshal failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
}
|
||||
|
||||
func BenchmarkSerializer_Deserialize(b *testing.B) {
|
||||
m := &Message{100, "hell world"}
|
||||
s := NewSerializer()
|
||||
|
||||
d, err := s.Marshal(m)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m1 := &Message{}
|
||||
if err := s.Unmarshal(d, m1); err != nil {
|
||||
b.Fatalf("unmarshal failed: %v", err)
|
||||
}
|
||||
}
|
||||
b.ReportAllocs()
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package protobuf
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"ngs/serialize"
|
||||
)
|
||||
|
||||
// ErrWrongValueType is the error used for marshal the value with protobuf encoding.
|
||||
var ErrWrongValueType = errors.New("protobuf: convert on wrong type value")
|
||||
|
||||
// Serializer implements the serialize.Serializer interface
|
||||
type Serializer struct{}
|
||||
|
||||
// NewSerializer returns a new Serializer.
|
||||
func NewSerializer() serialize.Serializer {
|
||||
return &Serializer{}
|
||||
}
|
||||
|
||||
// Marshal returns the protobuf encoding of v.
|
||||
func (s *Serializer) Marshal(v interface{}) ([]byte, error) {
|
||||
pb, ok := v.(proto.Message)
|
||||
if !ok {
|
||||
return nil, ErrWrongValueType
|
||||
}
|
||||
return proto.Marshal(pb)
|
||||
}
|
||||
|
||||
// Unmarshal parses the protobuf-encoded data and stores the result
|
||||
// in the value pointed to by v.
|
||||
func (s *Serializer) Unmarshal(data []byte, v interface{}) error {
|
||||
pb, ok := v.(proto.Message)
|
||||
if !ok {
|
||||
return ErrWrongValueType
|
||||
}
|
||||
return proto.Unmarshal(data, pb)
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package protobuf
|
||||
|
||||
import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
"ngs/benchmark/testdata"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProtobufSerialezer_Serialize(t *testing.T) {
|
||||
m := &testdata.Ping{Content: "hello"}
|
||||
s := NewSerializer()
|
||||
|
||||
b, err := s.Marshal(m)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
m1 := &testdata.Ping{}
|
||||
if err := s.Unmarshal(b, m1); err != nil {
|
||||
t.Fatalf("unmarshal failed: %v", err)
|
||||
}
|
||||
// refer: https://developers.google.com/protocol-buffers/docs/reference/go/faq#deepequal
|
||||
if !proto.Equal(m, m1) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSerializer_Serialize(b *testing.B) {
|
||||
m := &testdata.Ping{Content: "hello"}
|
||||
s := NewSerializer()
|
||||
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := s.Marshal(m); err != nil {
|
||||
b.Fatalf("unmarshal failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSerializer_Deserialize(b *testing.B) {
|
||||
m := &testdata.Ping{Content: "hello"}
|
||||
s := NewSerializer()
|
||||
|
||||
d, err := s.Marshal(m)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m1 := &testdata.Ping{}
|
||||
if err := s.Unmarshal(d, m1); err != nil {
|
||||
b.Fatalf("unmarshal failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package serialize
|
||||
|
||||
type (
|
||||
// Marshaler is the interface implemented by types that
|
||||
Marshaler interface {
|
||||
Marshal(interface{}) ([]byte, error)
|
||||
}
|
||||
|
||||
// Unmarshaler represents an Unmarshal interface
|
||||
Unmarshaler interface {
|
||||
Unmarshal([]byte, interface{}) error
|
||||
}
|
||||
|
||||
// Serializer is the interface that groups the basic Marshal and Unmarshal methods.
|
||||
Serializer interface {
|
||||
Marshaler
|
||||
Unmarshaler
|
||||
}
|
||||
)
|
@ -0,0 +1,41 @@
|
||||
package service
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
// Connections is a global variable which is used by session. thread-safe
|
||||
var Connections = newConnectionService()
|
||||
|
||||
type connectionService struct {
|
||||
count int64
|
||||
sid int64
|
||||
}
|
||||
|
||||
func newConnectionService() *connectionService {
|
||||
return &connectionService{sid: 0}
|
||||
}
|
||||
|
||||
// Increment the connection count
|
||||
func (c *connectionService) Increment() {
|
||||
atomic.AddInt64(&c.count, 1)
|
||||
}
|
||||
|
||||
// Decrement the connection count
|
||||
func (c *connectionService) Decrement() {
|
||||
atomic.AddInt64(&c.count, -1)
|
||||
}
|
||||
|
||||
// Count returns the connection numbers in current
|
||||
func (c *connectionService) Count() int64 {
|
||||
return atomic.LoadInt64(&c.count)
|
||||
}
|
||||
|
||||
// Reset the connection service status
|
||||
func (c *connectionService) Reset() {
|
||||
atomic.StoreInt64(&c.count, 0)
|
||||
atomic.StoreInt64(&c.sid, 0)
|
||||
}
|
||||
|
||||
// SessionID returns the session id
|
||||
func (c *connectionService) SessionID() int64 {
|
||||
return atomic.AddInt64(&c.sid, 1)
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package service
|
||||
|
||||
import "testing"
|
||||
|
||||
const paraCount = 500000
|
||||
|
||||
func TestNewConnectionService(t *testing.T) {
|
||||
service := newConnectionService()
|
||||
w := make(chan bool, paraCount)
|
||||
for i := 0; i < paraCount; i++ {
|
||||
go func() {
|
||||
service.Increment()
|
||||
service.SessionID()
|
||||
w <- true
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < paraCount; i++ {
|
||||
<-w
|
||||
}
|
||||
|
||||
if service.Count() != paraCount {
|
||||
t.Error("wrong connection count")
|
||||
}
|
||||
|
||||
if service.SessionID() != paraCount+1 {
|
||||
t.Error("wrong session id")
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package session
|
||||
|
||||
type (
|
||||
// LifetimeHandler represents a callback
|
||||
// that will be called when a session close or
|
||||
// session low-level connection broken.
|
||||
LifetimeHandler func(*Session)
|
||||
|
||||
lifetime struct {
|
||||
// callbacks that emitted on session closed
|
||||
onClosed []LifetimeHandler
|
||||
}
|
||||
)
|
||||
|
||||
var Lifetime = &lifetime{}
|
||||
|
||||
// OnClosed set the Callback which will be called
|
||||
// when session is closed Waring: session has closed.
|
||||
func (lt *lifetime) OnClosed(h LifetimeHandler) {
|
||||
lt.onClosed = append(lt.onClosed, h)
|
||||
}
|
||||
|
||||
func (lt *lifetime) Close(s *Session) {
|
||||
if len(lt.onClosed) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, h := range lt.onClosed {
|
||||
h(s)
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package session
|
||||
|
||||
import "sync"
|
||||
|
||||
// Router is used to select remote service address
|
||||
type Router struct {
|
||||
routes sync.Map
|
||||
}
|
||||
|
||||
func newRouter() *Router {
|
||||
return &Router{}
|
||||
}
|
||||
|
||||
// Bind bound an address to remote service
|
||||
func (r *Router) Bind(service, address string) {
|
||||
r.routes.Store(service, address)
|
||||
}
|
||||
|
||||
// Find finds the address corresponding a remote service
|
||||
func (r *Router) Find(service string) (string, bool) {
|
||||
v, found := r.routes.Load(service)
|
||||
if !found {
|
||||
return "", false
|
||||
}
|
||||
return v.(string), true
|
||||
}
|
@ -0,0 +1,398 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"ngs/service"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NetworkEntity represent low-level network instance
|
||||
type NetworkEntity interface {
|
||||
Push(route string, v interface{}) error
|
||||
RPC(route string, v interface{}) error
|
||||
LastMid() uint64
|
||||
Response(v interface{}) error
|
||||
ResponseMid(mid uint64, v interface{}) error
|
||||
Close() error
|
||||
RemoteAddr() net.Addr
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrIllegalUID represents a invalid uid
|
||||
ErrIllegalUID = errors.New("illegal uid")
|
||||
)
|
||||
|
||||
// Session represents a client session which could storage temp data during low-level
|
||||
// keep connected, all data will be released when the low-level connection was broken.
|
||||
// Session instance related to the client will be passed to Handler method as the first
|
||||
// parameter.
|
||||
type Session struct {
|
||||
sync.RWMutex // protect data
|
||||
id int64 // session global unique id
|
||||
uid int64 // binding user id
|
||||
lastTime int64 // last heartbeat time
|
||||
entity NetworkEntity // low-level network entity
|
||||
data map[string]interface{} // session data store
|
||||
router *Router
|
||||
}
|
||||
|
||||
// New returns a new session instance
|
||||
// a NetworkEntity is a low-level network instance
|
||||
func New(entity NetworkEntity) *Session {
|
||||
return &Session{
|
||||
id: service.Connections.SessionID(),
|
||||
entity: entity,
|
||||
data: make(map[string]interface{}),
|
||||
lastTime: time.Now().Unix(),
|
||||
router: newRouter(),
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkEntity returns the low-level network agent object
|
||||
func (s *Session) NetworkEntity() NetworkEntity {
|
||||
return s.entity
|
||||
}
|
||||
|
||||
// Router returns the service router
|
||||
func (s *Session) Router() *Router {
|
||||
return s.router
|
||||
}
|
||||
|
||||
// RPC sends message to remote server
|
||||
func (s *Session) RPC(route string, v interface{}) error {
|
||||
return s.entity.RPC(route, v)
|
||||
}
|
||||
|
||||
// Push message to client
|
||||
func (s *Session) Push(route string, v interface{}) error {
|
||||
return s.entity.Push(route, v)
|
||||
}
|
||||
|
||||
// Response message to client
|
||||
func (s *Session) Response(v interface{}) error {
|
||||
return s.entity.Response(v)
|
||||
}
|
||||
|
||||
// ResponseMID responses message to client, mid is
|
||||
// request message ID
|
||||
func (s *Session) ResponseMID(mid uint64, v interface{}) error {
|
||||
return s.entity.ResponseMid(mid, v)
|
||||
}
|
||||
|
||||
// ID returns the session id
|
||||
func (s *Session) ID() int64 {
|
||||
return s.id
|
||||
}
|
||||
|
||||
// UID returns uid that bind to current session
|
||||
func (s *Session) UID() int64 {
|
||||
return atomic.LoadInt64(&s.uid)
|
||||
}
|
||||
|
||||
// LastMid returns the last message id
|
||||
func (s *Session) LastMid() uint64 {
|
||||
return s.entity.LastMid()
|
||||
}
|
||||
|
||||
// Bind UID to current session
|
||||
func (s *Session) Bind(uid int64) error {
|
||||
if uid < 1 {
|
||||
return ErrIllegalUID
|
||||
}
|
||||
|
||||
atomic.StoreInt64(&s.uid, uid)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close terminate current session, session related data will not be released,
|
||||
// all related data should be Clear explicitly in Session closed callback
|
||||
func (s *Session) Close() {
|
||||
_ = s.entity.Close()
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote network address.
|
||||
func (s *Session) RemoteAddr() net.Addr {
|
||||
return s.entity.RemoteAddr()
|
||||
}
|
||||
|
||||
// Remove delete data associated with the key from session storage
|
||||
func (s *Session) Remove(key string) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
delete(s.data, key)
|
||||
}
|
||||
|
||||
// Set associates value with the key in session storage
|
||||
func (s *Session) Set(key string, value interface{}) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
s.data[key] = value
|
||||
}
|
||||
|
||||
// HasKey decides whether a key has associated value
|
||||
func (s *Session) HasKey(key string) bool {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
_, has := s.data[key]
|
||||
return has
|
||||
}
|
||||
|
||||
// Int returns the value associated with the key as a int.
|
||||
func (s *Session) Int(key string) int {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
v, ok := s.data[key]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
value, ok := v.(int)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Int8 returns the value associated with the key as a int8.
|
||||
func (s *Session) Int8(key string) int8 {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
v, ok := s.data[key]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
value, ok := v.(int8)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Int16 returns the value associated with the key as a int16.
|
||||
func (s *Session) Int16(key string) int16 {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
v, ok := s.data[key]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
value, ok := v.(int16)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Int32 returns the value associated with the key as a int32.
|
||||
func (s *Session) Int32(key string) int32 {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
v, ok := s.data[key]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
value, ok := v.(int32)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Int64 returns the value associated with the key as a int64.
|
||||
func (s *Session) Int64(key string) int64 {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
v, ok := s.data[key]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
value, ok := v.(int64)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Uint returns the value associated with the key as a uint.
|
||||
func (s *Session) Uint(key string) uint {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
v, ok := s.data[key]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
value, ok := v.(uint)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Uint8 returns the value associated with the key as a uint8.
|
||||
func (s *Session) Uint8(key string) uint8 {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
v, ok := s.data[key]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
value, ok := v.(uint8)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Uint16 returns the value associated with the key as a uint16.
|
||||
func (s *Session) Uint16(key string) uint16 {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
v, ok := s.data[key]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
value, ok := v.(uint16)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Uint32 returns the value associated with the key as a uint32.
|
||||
func (s *Session) Uint32(key string) uint32 {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
v, ok := s.data[key]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
value, ok := v.(uint32)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Uint64 returns the value associated with the key as a uint64.
|
||||
func (s *Session) Uint64(key string) uint64 {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
v, ok := s.data[key]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
value, ok := v.(uint64)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Float32 returns the value associated with the key as a float32.
|
||||
func (s *Session) Float32(key string) float32 {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
v, ok := s.data[key]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
value, ok := v.(float32)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Float64 returns the value associated with the key as a float64.
|
||||
func (s *Session) Float64(key string) float64 {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
v, ok := s.data[key]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
value, ok := v.(float64)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// String returns the value associated with the key as a string.
|
||||
func (s *Session) String(key string) string {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
v, ok := s.data[key]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
value, ok := v.(string)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Value returns the value associated with the key as a interface{}.
|
||||
func (s *Session) Value(key string) interface{} {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.data[key]
|
||||
}
|
||||
|
||||
// State returns all session state
|
||||
func (s *Session) State() map[string]interface{} {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.data
|
||||
}
|
||||
|
||||
// Restore session state after reconnect
|
||||
func (s *Session) Restore(data map[string]interface{}) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
s.data = data
|
||||
}
|
||||
|
||||
// Clear releases all data related to current session
|
||||
func (s *Session) Clear() {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
s.uid = 0
|
||||
s.data = map[string]interface{}{}
|
||||
}
|
@ -0,0 +1,175 @@
|
||||
package session
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNewSession(t *testing.T) {
|
||||
s := New(nil)
|
||||
if s.ID() < 1 {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_Bind(t *testing.T) {
|
||||
s := New(nil)
|
||||
uids := []int64{100, 1000, 10000000}
|
||||
for i, uid := range uids {
|
||||
s.Bind(uid)
|
||||
if s.UID() != uids[i] {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_HasKey(t *testing.T) {
|
||||
s := New(nil)
|
||||
key := "hello"
|
||||
value := "world"
|
||||
s.Set(key, value)
|
||||
if !s.HasKey(key) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_Float32(t *testing.T) {
|
||||
s := New(nil)
|
||||
key := "hello"
|
||||
value := float32(1.2000)
|
||||
s.Set(key, value)
|
||||
if value != s.Float32(key) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_Float64(t *testing.T) {
|
||||
s := New(nil)
|
||||
key := "hello"
|
||||
value := 1.2000
|
||||
s.Set(key, value)
|
||||
if value != s.Float64(key) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_Int(t *testing.T) {
|
||||
s := New(nil)
|
||||
key := "testkey"
|
||||
value := 234
|
||||
s.Set(key, value)
|
||||
if value != s.Int(key) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_Int8(t *testing.T) {
|
||||
s := New(nil)
|
||||
key := "testkey"
|
||||
value := int8(123)
|
||||
s.Set(key, value)
|
||||
if value != s.Int8(key) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_Int16(t *testing.T) {
|
||||
s := New(nil)
|
||||
key := "testkey"
|
||||
value := int16(3245)
|
||||
s.Set(key, value)
|
||||
if value != s.Int16(key) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_Int32(t *testing.T) {
|
||||
s := New(nil)
|
||||
key := "testkey"
|
||||
value := int32(5454)
|
||||
s.Set(key, value)
|
||||
if value != s.Int32(key) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_Int64(t *testing.T) {
|
||||
s := New(nil)
|
||||
key := "testkey"
|
||||
value := int64(444454)
|
||||
s.Set(key, value)
|
||||
if value != s.Int64(key) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_Uint(t *testing.T) {
|
||||
s := New(nil)
|
||||
key := "testkey"
|
||||
value := uint(24254)
|
||||
s.Set(key, value)
|
||||
if value != s.Uint(key) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_Uint8(t *testing.T) {
|
||||
s := New(nil)
|
||||
key := "testkey"
|
||||
value := uint8(34)
|
||||
s.Set(key, value)
|
||||
if value != s.Uint8(key) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_Uint16(t *testing.T) {
|
||||
s := New(nil)
|
||||
key := "testkey"
|
||||
value := uint16(4645)
|
||||
s.Set(key, value)
|
||||
if value != s.Uint16(key) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_Uint32(t *testing.T) {
|
||||
s := New(nil)
|
||||
key := "testkey"
|
||||
value := uint32(12365)
|
||||
s.Set(key, value)
|
||||
if value != s.Uint32(key) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_Uint64(t *testing.T) {
|
||||
s := New(nil)
|
||||
key := "testkey"
|
||||
value := uint64(1000)
|
||||
s.Set(key, value)
|
||||
if value != s.Uint64(key) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_State(t *testing.T) {
|
||||
s := New(nil)
|
||||
key := "testkey"
|
||||
value := uint64(1000)
|
||||
s.Set(key, value)
|
||||
state := s.State()
|
||||
if value != state[key].(uint64) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestSession_Restore(t *testing.T) {
|
||||
s := New(nil)
|
||||
s2 := New(nil)
|
||||
key := "testkey"
|
||||
value := uint64(1000)
|
||||
s.Set(key, value)
|
||||
state := s.State()
|
||||
s2.Restore(state)
|
||||
if value != s2.Uint64(key) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|