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
|
package core
|
||||||
|
|
||||||
import "git.noahlan.cn/noahlan/nnet/packet"
|
import (
|
||||||
|
"git.noahlan.cn/noahlan/nnet/entity"
|
||||||
|
"git.noahlan.cn/noahlan/nnet/packet"
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Handler interface {
|
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) {
|
func (f HandlerFunc) Handle(entity entity.NetworkEntity, pkg packet.IPacket) {
|
||||||
f(conn, pkg)
|
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 (
|
import (
|
||||||
"encoding/hex"
|
"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