wip: nnet
parent
5a9cbde34b
commit
26ad30fd23
@ -1,49 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.noahlan.cn/noahlan/nnet/packet"
|
||||
"git.noahlan.cn/noahlan/ntools-go/core/nlog"
|
||||
)
|
||||
|
||||
type nNetRouter struct {
|
||||
handlers map[string]Handler
|
||||
notFound Handler
|
||||
}
|
||||
|
||||
func NewRouter() Router {
|
||||
return &nNetRouter{
|
||||
handlers: make(map[string]Handler),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *nNetRouter) Handle(conn *Connection, p packet.IPacket) {
|
||||
pkg, ok := p.(*packet.Packet)
|
||||
if !ok {
|
||||
nlog.Error(packet.ErrWrongMessage)
|
||||
return
|
||||
}
|
||||
handler, ok := r.handlers[pkg.Header.Route]
|
||||
if !ok {
|
||||
if r.notFound == nil {
|
||||
nlog.Error("message handler not found")
|
||||
return
|
||||
}
|
||||
r.notFound.Handle(conn, p)
|
||||
return
|
||||
}
|
||||
handler.Handle(conn, p)
|
||||
}
|
||||
|
||||
func (r *nNetRouter) Register(matches interface{}, handler Handler) error {
|
||||
route, ok := matches.(string)
|
||||
if !ok {
|
||||
return errors.New("the type of matches must be string")
|
||||
}
|
||||
r.handlers[route] = handler
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *nNetRouter) SetNotFoundHandler(handler Handler) {
|
||||
r.notFound = handler
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package core
|
||||
|
||||
import "git.noahlan.cn/noahlan/nnet/packet"
|
||||
|
||||
type (
|
||||
Processor interface {
|
||||
Process(conn *Connection, packet packet.IPacket) error
|
||||
}
|
||||
)
|
@ -1,72 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.noahlan.cn/noahlan/nnet/packet"
|
||||
"git.noahlan.cn/noahlan/ntools-go/core/nlog"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
hrd []byte // handshake response data
|
||||
hbd []byte // heartbeat packet data
|
||||
)
|
||||
|
||||
type NNetProcessor struct {
|
||||
}
|
||||
|
||||
func NewNNetProcessor() *NNetProcessor {
|
||||
// TODO custom hrd hbd
|
||||
data, _ := json.Marshal(map[string]interface{}{
|
||||
"code": 200,
|
||||
"sys": map[string]float64{"heartbeat": time.Second.Seconds()},
|
||||
})
|
||||
packer := packet.NewNNetPacker()
|
||||
hrd, _ = packer.Pack(packet.Handshake, data)
|
||||
|
||||
return &NNetProcessor{}
|
||||
}
|
||||
|
||||
func (n *NNetProcessor) Process(conn *Connection, p packet.IPacket) error {
|
||||
h, ok := p.(*packet.Packet)
|
||||
if !ok {
|
||||
return packet.ErrWrongPacketType
|
||||
}
|
||||
|
||||
switch h.PacketType {
|
||||
case packet.Handshake:
|
||||
// TODO validate handshake
|
||||
if err := conn.SendBytes(hrd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.SetStatus(StatusPrepare)
|
||||
nlog.Debugf("connection handshake Id=%d, Remote=%s", conn.ID(), conn.Conn().RemoteAddr())
|
||||
case packet.HandshakeAck:
|
||||
conn.SetStatus(StatusPending)
|
||||
nlog.Debugf("receive handshake ACK Id=%d, Remote=%s", conn.ID(), conn.Conn().RemoteAddr())
|
||||
case packet.Heartbeat:
|
||||
// Expected
|
||||
case packet.Data:
|
||||
if conn.Status() < StatusPending {
|
||||
return errors.New(fmt.Sprintf("receive data on socket which not yet ACK, session will be closed immediately, remote=%s",
|
||||
conn.Conn().RemoteAddr()))
|
||||
}
|
||||
conn.SetStatus(StatusWorking)
|
||||
|
||||
var lastMid uint64
|
||||
switch h.MsgType {
|
||||
case packet.Request:
|
||||
lastMid = h.ID
|
||||
case packet.Notify:
|
||||
lastMid = 0
|
||||
default:
|
||||
return fmt.Errorf("Invalid message type: %s ", h.MsgType.String())
|
||||
}
|
||||
conn.SetLastMID(lastMid)
|
||||
}
|
||||
conn.SetLastHeartbeatAt(time.Now().Unix())
|
||||
return nil
|
||||
}
|
@ -1,15 +1,20 @@
|
||||
package core
|
||||
|
||||
import "git.noahlan.cn/noahlan/nnet/packet"
|
||||
import (
|
||||
"git.noahlan.cn/noahlan/nnet/entity"
|
||||
"git.noahlan.cn/noahlan/nnet/packet"
|
||||
)
|
||||
|
||||
type (
|
||||
Handler interface {
|
||||
Handle(conn *Connection, pkg packet.IPacket)
|
||||
Handle(entity entity.NetworkEntity, pkg packet.IPacket)
|
||||
}
|
||||
|
||||
HandlerFunc func(conn *Connection, pkg packet.IPacket)
|
||||
HandlerFunc func(entity entity.NetworkEntity, pkg packet.IPacket)
|
||||
|
||||
Middleware func(next HandlerFunc) HandlerFunc
|
||||
)
|
||||
|
||||
func (f HandlerFunc) Handle(conn *Connection, pkg packet.IPacket) {
|
||||
f(conn, pkg)
|
||||
func (f HandlerFunc) Handle(entity entity.NetworkEntity, pkg packet.IPacket) {
|
||||
f(entity, pkg)
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
package entity
|
||||
|
||||
type Session interface {
|
||||
// ID 获取 Session ID
|
||||
ID() int64
|
||||
// UID 获取UID
|
||||
UID() string
|
||||
// Bind 绑定uid
|
||||
Bind(uid string)
|
||||
// Attribute 获取指定key对应参数
|
||||
Attribute(key string) interface{}
|
||||
// Keys 获取所有参数key
|
||||
Keys() []string
|
||||
// Exists 指定key是否存在
|
||||
Exists(key string) bool
|
||||
// Attributes 获取所有参数
|
||||
Attributes() map[string]interface{}
|
||||
// RemoveAttribute 移除指定key对应参数
|
||||
RemoveAttribute(key string)
|
||||
// SetAttribute 设置参数
|
||||
SetAttribute(key string, value interface{})
|
||||
// Invalidate 清理
|
||||
Invalidate()
|
||||
// Close 关闭 Session
|
||||
Close()
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package pool
|
||||
|
||||
import "github.com/panjf2000/ants/v2"
|
||||
|
||||
var _pool *pool
|
||||
|
||||
type pool struct {
|
||||
connPool *ants.Pool
|
||||
workerPool *ants.Pool
|
||||
}
|
||||
|
||||
func InitPool(size int) {
|
||||
p := &pool{}
|
||||
|
||||
p.connPool, _ = ants.NewPool(size, ants.WithNonblocking(true))
|
||||
p.workerPool, _ = ants.NewPool(size*2, ants.WithNonblocking(true))
|
||||
|
||||
_pool = p
|
||||
}
|
||||
|
||||
func SubmitConn(h func()) error {
|
||||
return _pool.connPool.Submit(h)
|
||||
}
|
||||
|
||||
func SubmitWorker(h func()) error {
|
||||
return _pool.workerPool.Submit(h)
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"git.noahlan.cn/noahlan/nnet/core"
|
||||
"git.noahlan.cn/noahlan/nnet/entity"
|
||||
"git.noahlan.cn/noahlan/nnet/packet"
|
||||
"git.noahlan.cn/noahlan/ntools-go/core/nlog"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HeartbeatMiddleware struct {
|
||||
lastAt int64
|
||||
interval time.Duration
|
||||
hbdFn func(entity entity.NetworkEntity) []byte
|
||||
}
|
||||
|
||||
func WithHeartbeat(interval time.Duration, dataFn func(entity entity.NetworkEntity) []byte) core.RunOption {
|
||||
m := &HeartbeatMiddleware{
|
||||
lastAt: time.Now().Unix(),
|
||||
interval: interval,
|
||||
hbdFn: dataFn,
|
||||
}
|
||||
if dataFn == nil {
|
||||
nlog.Error("dataFn must not be nil")
|
||||
panic("dataFn must not be nil")
|
||||
}
|
||||
core.Lifetime.OnOpen(m.start)
|
||||
|
||||
return func(server *core.Server) {
|
||||
server.Use(func(next core.HandlerFunc) core.HandlerFunc {
|
||||
return func(entity entity.NetworkEntity, pkg packet.IPacket) {
|
||||
m.handle(entity, pkg)
|
||||
|
||||
next(entity, pkg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (m *HeartbeatMiddleware) start(entity entity.NetworkEntity) {
|
||||
ticker := time.NewTicker(m.interval)
|
||||
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
deadline := time.Now().Add(-2 * m.interval).Unix()
|
||||
if atomic.LoadInt64(&m.lastAt) < deadline {
|
||||
nlog.Errorf("Heartbeat timeout, LastTime=%d, Deadline=%d", atomic.LoadInt64(&m.lastAt), deadline)
|
||||
return
|
||||
}
|
||||
err := entity.SendBytes(m.hbdFn(entity))
|
||||
if err != nil {
|
||||
nlog.Errorf("Heartbeat err: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *HeartbeatMiddleware) handle(_ entity.NetworkEntity, _ packet.IPacket) {
|
||||
atomic.StoreInt64(&m.lastAt, time.Now().Unix())
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"git.noahlan.cn/noahlan/nnet/core"
|
||||
"git.noahlan.cn/noahlan/nnet/entity"
|
||||
"git.noahlan.cn/noahlan/nnet/middleware"
|
||||
"git.noahlan.cn/noahlan/nnet/packet"
|
||||
"git.noahlan.cn/noahlan/ntools-go/core/nlog"
|
||||
"time"
|
||||
)
|
||||
|
||||
type NNetConfig struct {
|
||||
}
|
||||
|
||||
func WithNNetProtocol(
|
||||
handshakeValidator func([]byte) error,
|
||||
heartbeatInterval time.Duration,
|
||||
) []core.RunOption {
|
||||
if handshakeValidator == nil {
|
||||
handshakeValidator = func(bytes []byte) error { return nil }
|
||||
}
|
||||
packer := NewNNetPacker()
|
||||
hbd, err := packer.Pack(Handshake, nil)
|
||||
nlog.Must(err)
|
||||
|
||||
return []core.RunOption{
|
||||
WithNNetPipeline(handshakeValidator),
|
||||
core.WithRouter(NewNNetRouter()),
|
||||
core.WithPacker(func() packet.Packer { return NewNNetPacker() }),
|
||||
middleware.WithHeartbeat(heartbeatInterval, func(_ entity.NetworkEntity) []byte {
|
||||
return hbd
|
||||
}),
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package packet
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
@ -0,0 +1,86 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.noahlan.cn/noahlan/nnet/core"
|
||||
"git.noahlan.cn/noahlan/nnet/entity"
|
||||
"git.noahlan.cn/noahlan/ntools-go/core/nlog"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
handshakeData struct {
|
||||
Version string `json:"version"` // 客户端版本号,服务器以此判断是否合适与客户端通信
|
||||
Type string `json:"type"` // 客户端类型,与客户端版本号一起来确定客户端是否合适
|
||||
|
||||
// 透传信息
|
||||
Payload interface{} `json:"payload,optional,omitempty"`
|
||||
}
|
||||
handshakeAckData struct {
|
||||
Heartbeat int64 `json:"heartbeat"` // 心跳间隔,单位秒 0表示不需要心跳
|
||||
// 路由
|
||||
Routes map[string]uint16 `json:"routes"` // route map to code
|
||||
Codes map[uint16]string `json:"codes"` // code map to route
|
||||
|
||||
// 服务端支持的body部分消息传输协议
|
||||
//Protocol string `json:"protocol,options=[plain,json,protobuf]"` // plain/json/protobuf
|
||||
|
||||
// 透传信息
|
||||
Payload interface{} `json:"payload,optional,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
func WithNNetPipeline(heartbeatInterval time.Duration, handshakeValidator func([]byte) error) core.RunOption {
|
||||
handshakeAck := &handshakeAckData{}
|
||||
data, err := json.Marshal(handshakeAck)
|
||||
nlog.Must(err)
|
||||
|
||||
packer := NewNNetPacker()
|
||||
hrd, _ := packer.Pack(Handshake, data)
|
||||
|
||||
return func(server *core.Server) {
|
||||
server.Pipeline().Inbound().PushFront(func(entity entity.NetworkEntity, v interface{}) error {
|
||||
pkg, ok := v.(*NNetPacket)
|
||||
if !ok {
|
||||
return ErrWrongPacketType
|
||||
}
|
||||
conn, _ := entity.Conn()
|
||||
|
||||
switch pkg.PacketType {
|
||||
case Handshake:
|
||||
if err := handshakeValidator(pkg.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := entity.SendBytes(hrd); err != nil {
|
||||
return err
|
||||
}
|
||||
entity.SetStatus(core.StatusPrepare)
|
||||
nlog.Debugf("connection handshake Id=%d, Remote=%s", entity.Session().ID(), conn.RemoteAddr())
|
||||
case HandshakeAck:
|
||||
entity.SetStatus(core.StatusPending)
|
||||
nlog.Debugf("receive handshake ACK Id=%d, Remote=%s", entity.Session().ID(), conn.RemoteAddr())
|
||||
case Heartbeat:
|
||||
// Expected
|
||||
case Data:
|
||||
if entity.Status() < core.StatusPending {
|
||||
return errors.New(fmt.Sprintf("receive data on socket which not yet ACK, session will be closed immediately, remote=%s",
|
||||
conn.RemoteAddr()))
|
||||
}
|
||||
entity.SetStatus(core.StatusWorking)
|
||||
|
||||
var lastMid uint64
|
||||
switch pkg.MsgType {
|
||||
case Request:
|
||||
lastMid = pkg.ID
|
||||
case Notify:
|
||||
lastMid = 0
|
||||
default:
|
||||
return fmt.Errorf("Invalid message type: %s ", pkg.MsgType.String())
|
||||
}
|
||||
entity.SetLastMID(lastMid)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.noahlan.cn/noahlan/nnet/core"
|
||||
"git.noahlan.cn/noahlan/nnet/entity"
|
||||
"git.noahlan.cn/noahlan/nnet/packet"
|
||||
"git.noahlan.cn/noahlan/ntools-go/core/nlog"
|
||||
)
|
||||
|
||||
type nNetRouter struct {
|
||||
handlers map[string]core.Handler
|
||||
notFound core.Handler
|
||||
}
|
||||
|
||||
func NewNNetRouter() core.Router {
|
||||
return &nNetRouter{
|
||||
handlers: make(map[string]core.Handler),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *nNetRouter) Handle(entity entity.NetworkEntity, p packet.IPacket) {
|
||||
pkg, ok := p.(*NNetPacket)
|
||||
if !ok {
|
||||
nlog.Error(ErrWrongMessage)
|
||||
return
|
||||
}
|
||||
handler, ok := r.handlers[pkg.Header.Route]
|
||||
if !ok {
|
||||
if r.notFound == nil {
|
||||
nlog.Error("message handler not found")
|
||||
return
|
||||
}
|
||||
r.notFound.Handle(entity, p)
|
||||
return
|
||||
}
|
||||
handler.Handle(entity, p)
|
||||
}
|
||||
|
||||
func (r *nNetRouter) Register(matches interface{}, handler core.Handler) error {
|
||||
route, ok := matches.(string)
|
||||
if !ok {
|
||||
return errors.New("the type of matches must be string")
|
||||
}
|
||||
r.handlers[route] = handler
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *nNetRouter) SetNotFoundHandler(handler core.Handler) {
|
||||
r.notFound = handler
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
sync.RWMutex
|
||||
sessions map[int64]*Session
|
||||
}
|
||||
|
||||
func NewSessionMgr() *Manager {
|
||||
return &Manager{
|
||||
RWMutex: sync.RWMutex{},
|
||||
sessions: make(map[int64]*Session),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) StoreSession(s *Session) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
m.sessions[s.ID()] = s
|
||||
}
|
||||
|
||||
func (m *Manager) FindSession(sid int64) *Session {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
return m.sessions[sid]
|
||||
}
|
||||
|
||||
func (m *Manager) FindOrCreateSession(sid int64) *Session {
|
||||
m.RLock()
|
||||
s, ok := m.sessions[sid]
|
||||
m.RUnlock()
|
||||
|
||||
if !ok {
|
||||
s = NewSession()
|
||||
|
||||
m.Lock()
|
||||
m.sessions[s.ID()] = s
|
||||
m.Unlock()
|
||||
}
|
||||
return s
|
||||
}
|
Loading…
Reference in New Issue