first commit
commit
9d7de95373
@ -0,0 +1,17 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
.idea/
|
||||
.vscode/
|
@ -0,0 +1,14 @@
|
||||
package component
|
||||
|
||||
var _ Component = (*Base)(nil)
|
||||
|
||||
// Base 空组件
|
||||
type Base struct{}
|
||||
|
||||
func (*Base) Init() {}
|
||||
|
||||
func (*Base) AfterInit() {}
|
||||
|
||||
func (*Base) BeforeShutdown() {}
|
||||
|
||||
func (*Base) Shutdown() {}
|
@ -0,0 +1,16 @@
|
||||
package component
|
||||
|
||||
// Component 具体组件的接口.
|
||||
type Component interface {
|
||||
// Init 初始化组件时调用.
|
||||
Init()
|
||||
|
||||
// AfterInit 初始化组件后调用.
|
||||
AfterInit()
|
||||
|
||||
// BeforeShutdown 组件被停止前调用.
|
||||
BeforeShutdown()
|
||||
|
||||
// Shutdown 停止组件时调用.
|
||||
Shutdown()
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package component
|
||||
|
||||
type CompWithOptions struct {
|
||||
Comp Component
|
||||
Opts []Option
|
||||
}
|
||||
|
||||
type Components struct {
|
||||
comps []CompWithOptions
|
||||
}
|
||||
|
||||
// Register registers a component to hub with options
|
||||
func (cs *Components) Register(c Component, options ...Option) {
|
||||
cs.comps = append(cs.comps, CompWithOptions{c, options})
|
||||
}
|
||||
|
||||
// List returns all components with it's options
|
||||
func (cs *Components) List() []CompWithOptions {
|
||||
return cs.comps
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var (
|
||||
typeOfError = reflect.TypeOf((*error)(nil)).Elem()
|
||||
typeOfBytes = reflect.TypeOf(([]byte)(nil))
|
||||
//typeOfSession = reflect.TypeOf(session.New(nil))
|
||||
)
|
||||
|
||||
func isExported(name string) bool {
|
||||
w, _ := utf8.DecodeRuneInString(name)
|
||||
return unicode.IsUpper(w)
|
||||
}
|
||||
|
||||
func isExportedOrBuiltinType(t reflect.Type) bool {
|
||||
for t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
// PkgPath will be non-empty even for an exported type,
|
||||
// so we need to check the type name as well.
|
||||
return isExported(t.Name()) || t.PkgPath() == ""
|
||||
}
|
||||
|
||||
// isHandlerMethod decide a method is suitable handler method
|
||||
func isHandlerMethod(method reflect.Method) bool {
|
||||
mt := method.Type
|
||||
// Method must be exported.
|
||||
if method.PkgPath != "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Method needs three ins: receiver, *Session, []byte or pointer.
|
||||
if mt.NumIn() != 3 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Method needs one outs: error
|
||||
if mt.NumOut() != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
if t1 := mt.In(1); t1.Kind() != reflect.Ptr || t1 != typeOfSession {
|
||||
return false
|
||||
}
|
||||
|
||||
if (mt.In(2).Kind() != reflect.Ptr && mt.In(2) != typeOfBytes) || mt.Out(0) != typeOfError {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package component
|
||||
|
||||
type (
|
||||
options struct {
|
||||
name string // component name
|
||||
nameFunc func(string) string // rename handler name
|
||||
schedulerName string // schedulerName name
|
||||
}
|
||||
|
||||
// Option used to customize handler
|
||||
Option func(options *options)
|
||||
)
|
||||
|
||||
// WithName used to rename component name
|
||||
func WithName(name string) Option {
|
||||
return func(opt *options) {
|
||||
opt.name = name
|
||||
}
|
||||
}
|
||||
|
||||
// WithNameFunc override handler name by specific function
|
||||
// such as: strings.ToUpper/strings.ToLower
|
||||
func WithNameFunc(fn func(string) string) Option {
|
||||
return func(opt *options) {
|
||||
opt.nameFunc = fn
|
||||
}
|
||||
}
|
||||
|
||||
// WithSchedulerName set the name of the service scheduler
|
||||
func WithSchedulerName(name string) Option {
|
||||
return func(opt *options) {
|
||||
opt.schedulerName = name
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type (
|
||||
// Handler represents a message.Message's handler's meta information.
|
||||
Handler struct {
|
||||
Receiver reflect.Value // receiver of method
|
||||
Method reflect.Method // method stub
|
||||
Type reflect.Type // arg type of method
|
||||
IsRawArg bool // whether the data need to unserialize
|
||||
}
|
||||
|
||||
// Service implements a specific service, some of it's methods will be
|
||||
// called when the correspond events is occurred.
|
||||
Service struct {
|
||||
Name string // name of service
|
||||
Type reflect.Type // type of the receiver
|
||||
Receiver reflect.Value // receiver of methods for the service
|
||||
Handlers map[string]*Handler // registered methods
|
||||
SchedulerName string // name of scheduler variable in session data
|
||||
Options options // options
|
||||
}
|
||||
)
|
||||
|
||||
func NewService(comp Component, opts []Option) *Service {
|
||||
s := &Service{
|
||||
Type: reflect.TypeOf(comp),
|
||||
Receiver: reflect.ValueOf(comp),
|
||||
}
|
||||
|
||||
// apply options
|
||||
for i := range opts {
|
||||
opt := opts[i]
|
||||
opt(&s.Options)
|
||||
}
|
||||
if name := s.Options.name; name != "" {
|
||||
s.Name = name
|
||||
} else {
|
||||
s.Name = reflect.Indirect(s.Receiver).Type().Name()
|
||||
}
|
||||
s.SchedulerName = s.Options.schedulerName
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// suitableMethods returns suitable methods of typ
|
||||
func (s *Service) suitableHandlerMethods(typ reflect.Type) map[string]*Handler {
|
||||
methods := make(map[string]*Handler)
|
||||
for m := 0; m < typ.NumMethod(); m++ {
|
||||
method := typ.Method(m)
|
||||
mt := method.Type
|
||||
mn := method.Name
|
||||
if isHandlerMethod(method) {
|
||||
raw := false
|
||||
if mt.In(2) == typeOfBytes {
|
||||
raw = true
|
||||
}
|
||||
// rewrite handler name
|
||||
if s.Options.nameFunc != nil {
|
||||
mn = s.Options.nameFunc(mn)
|
||||
}
|
||||
methods[mn] = &Handler{Method: method, Type: mt.In(2), IsRawArg: raw}
|
||||
}
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
// ExtractHandler extract the set of methods from the
|
||||
// receiver value which satisfy the following conditions:
|
||||
// - exported method of exported type
|
||||
// - two arguments, both of exported type
|
||||
// - the first argument is *session.Session
|
||||
// - the second argument is []byte or a pointer
|
||||
func (s *Service) ExtractHandler() error {
|
||||
typeName := reflect.Indirect(s.Receiver).Type().Name()
|
||||
if typeName == "" {
|
||||
return errors.New("no service name for type " + s.Type.String())
|
||||
}
|
||||
if !isExported(typeName) {
|
||||
return errors.New("type " + typeName + " is not exported")
|
||||
}
|
||||
|
||||
// Install the methods
|
||||
s.Handlers = s.suitableHandlerMethods(s.Type)
|
||||
|
||||
if len(s.Handlers) == 0 {
|
||||
str := ""
|
||||
// To help the user, see if a pointer receiver would work.
|
||||
method := s.suitableHandlerMethods(reflect.PtrTo(s.Type))
|
||||
if len(method) != 0 {
|
||||
str = "type " + s.Name + " has no exported methods of suitable type (hint: pass a pointer to value of that type)"
|
||||
} else {
|
||||
str = "type " + s.Name + " has no exported methods of suitable type"
|
||||
}
|
||||
return errors.New(str)
|
||||
}
|
||||
|
||||
for i := range s.Handlers {
|
||||
s.Handlers[i].Receiver = s.Receiver
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
package codec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"ng/internal/packet"
|
||||
)
|
||||
|
||||
// Codec constants.
|
||||
const (
|
||||
HeadLength = 4
|
||||
MaxPacketSize = 64 * 1024
|
||||
)
|
||||
|
||||
// ErrPacketSizeExceed is the error used for encode/decode.
|
||||
var ErrPacketSizeExceed = errors.New("codec: packet size exceed")
|
||||
|
||||
// A Decoder reads and decodes network data slice
|
||||
type Decoder struct {
|
||||
buf *bytes.Buffer
|
||||
size int // last packet length
|
||||
typ byte // last packet type
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder that used for decode network bytes slice.
|
||||
func NewDecoder() *Decoder {
|
||||
return &Decoder{
|
||||
buf: bytes.NewBuffer(nil),
|
||||
size: -1,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Decoder) forward() error {
|
||||
header := c.buf.Next(HeadLength)
|
||||
c.typ = header[0]
|
||||
if c.typ < packet.Handshake || c.typ > packet.Kick {
|
||||
return packet.ErrWrongPacketType
|
||||
}
|
||||
c.size = bytesToInt(header[1:])
|
||||
|
||||
// packet length limitation
|
||||
if c.size > MaxPacketSize {
|
||||
return ErrPacketSizeExceed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode decode the network bytes slice to packet.Packet(s)
|
||||
// TODO(Warning): shared slice
|
||||
func (c *Decoder) Decode(data []byte) ([]*packet.Packet, error) {
|
||||
c.buf.Write(data)
|
||||
|
||||
var (
|
||||
packets []*packet.Packet
|
||||
err error
|
||||
)
|
||||
// check length
|
||||
if c.buf.Len() < HeadLength {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// first time
|
||||
if c.size < 0 {
|
||||
if err = c.forward(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for c.size <= c.buf.Len() {
|
||||
p := &packet.Packet{Type: packet.Type(c.typ), Length: c.size, Data: c.buf.Next(c.size)}
|
||||
packets = append(packets, p)
|
||||
|
||||
// more packet
|
||||
if c.buf.Len() < HeadLength {
|
||||
c.size = -1
|
||||
break
|
||||
}
|
||||
|
||||
if err = c.forward(); err != nil {
|
||||
return packets, err
|
||||
}
|
||||
}
|
||||
|
||||
return packets, nil
|
||||
}
|
||||
|
||||
// Encode create a packet.Packet from the raw bytes slice and then encode to network bytes slice
|
||||
// Protocol refs: https://github.com/NetEase/pomelo/wiki/Communication-Protocol
|
||||
//
|
||||
// -<type>-|--------<length>--------|-<data>-
|
||||
// --------|------------------------|--------
|
||||
// 1 byte packet type, 3 bytes packet data length(big end), and data segment
|
||||
func Encode(typ packet.Type, data []byte) ([]byte, error) {
|
||||
if typ < packet.Handshake || typ > packet.Kick {
|
||||
return nil, packet.ErrWrongPacketType
|
||||
}
|
||||
|
||||
p := &packet.Packet{Type: typ, Length: len(data)}
|
||||
buf := make([]byte, p.Length+HeadLength)
|
||||
buf[0] = byte(p.Type)
|
||||
|
||||
copy(buf[1:HeadLength], intToBytes(p.Length))
|
||||
copy(buf[HeadLength:], data)
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// Decode packet data length byte to int(Big end)
|
||||
func bytesToInt(b []byte) int {
|
||||
result := 0
|
||||
for _, v := range b {
|
||||
result = result<<8 + int(v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Encode packet data length to bytes(Big end)
|
||||
func intToBytes(n int) []byte {
|
||||
buf := make([]byte, 3)
|
||||
buf[0] = byte((n >> 16) & 0xFF)
|
||||
buf[1] = byte((n >> 8) & 0xFF)
|
||||
buf[2] = byte(n & 0xFF)
|
||||
return buf
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package codec
|
||||
|
||||
import (
|
||||
. "ng/internal/packet"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkDecoder_Decode(b *testing.B) {
|
||||
data := []byte("hello world")
|
||||
pp1, err := Encode(Handshake, data)
|
||||
if err != nil {
|
||||
b.Error(err.Error())
|
||||
}
|
||||
|
||||
d1 := NewDecoder()
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
packets, err := d1.Decode(pp1)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if len(packets) != 1 {
|
||||
b.Fatal("decode error")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Logger represents the log interface
|
||||
type Logger interface {
|
||||
Println(v ...interface{})
|
||||
Fatal(v ...interface{})
|
||||
Fatalf(format string, v ...interface{})
|
||||
}
|
||||
|
||||
func init() {
|
||||
SetLogger(log.New(os.Stderr, "[NGServer]", log.LstdFlags|log.Lshortfile))
|
||||
}
|
||||
|
||||
var (
|
||||
Println func(v ...interface{})
|
||||
Fatal func(v ...interface{})
|
||||
Fatalf func(format string, v ...interface{})
|
||||
)
|
||||
|
||||
// SetLogger rewrites the default logger
|
||||
func SetLogger(logger Logger) {
|
||||
if logger == nil {
|
||||
return
|
||||
}
|
||||
Println = logger.Println
|
||||
Fatal = logger.Fatal
|
||||
Fatalf = logger.Fatalf
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package packet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Type represents the network packet's type such as: handshake and so on.
|
||||
type Type byte
|
||||
|
||||
const (
|
||||
_ Type = iota
|
||||
// Handshake represents a handshake: request(client) <====> handshake response(server)
|
||||
Handshake = 0x01
|
||||
|
||||
// HandshakeAck represents a handshake ack from client to server
|
||||
HandshakeAck = 0x02
|
||||
|
||||
// Heartbeat represents a heartbeat
|
||||
Heartbeat = 0x03
|
||||
|
||||
// Data represents a common data packet
|
||||
Data = 0x04
|
||||
|
||||
// Kick represents a kick off packet
|
||||
Kick = 0x05 // disconnect message from server
|
||||
)
|
||||
|
||||
// ErrWrongPacketType represents a wrong packet type.
|
||||
var ErrWrongPacketType = errors.New("wrong packet type")
|
||||
|
||||
// Packet represents a network packet.
|
||||
type Packet struct {
|
||||
Type Type
|
||||
Length int
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// New create a Packet instance.
|
||||
func New() *Packet {
|
||||
return &Packet{}
|
||||
}
|
||||
|
||||
// String represents the Packet's in text mode.
|
||||
func (p *Packet) String() string {
|
||||
return fmt.Sprintf("Type: %d, Length: %d, Data: %s", p.Type, p.Length, string(p.Data))
|
||||
}
|
Loading…
Reference in New Issue