parent
c65fd5961b
commit
ebcbd0f88f
@ -0,0 +1,23 @@
|
|||||||
|
package nnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.noahlan.cn/noahlan/nnet/connection"
|
||||||
|
"git.noahlan.cn/noahlan/ntools-go/core/nlog"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dial 连接服务器
|
||||||
|
func (ngin *Engine) Dial(addr string) (*connection.Connection, error) {
|
||||||
|
err := ngin.setup()
|
||||||
|
if err != nil {
|
||||||
|
nlog.Errorf("%s failed to setup server, err:%v", ngin.LogPrefix(), err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.Dial("tcp", addr)
|
||||||
|
nlog.Must(err)
|
||||||
|
|
||||||
|
nlog.Infof("%s now connect to %s...", ngin.LogPrefix(), addr)
|
||||||
|
|
||||||
|
return ngin.handle(conn), nil
|
||||||
|
}
|
@ -1,286 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"git.noahlan.cn/noahlan/nnet/config"
|
|
||||||
conn2 "git.noahlan.cn/noahlan/nnet/conn"
|
|
||||||
"git.noahlan.cn/noahlan/nnet/entity"
|
|
||||||
"git.noahlan.cn/noahlan/nnet/lifetime"
|
|
||||||
"git.noahlan.cn/noahlan/nnet/packet"
|
|
||||||
"git.noahlan.cn/noahlan/nnet/pipeline"
|
|
||||||
"git.noahlan.cn/noahlan/nnet/scheduler"
|
|
||||||
"git.noahlan.cn/noahlan/nnet/serialize"
|
|
||||||
"git.noahlan.cn/noahlan/ntools-go/core/nlog"
|
|
||||||
"git.noahlan.cn/noahlan/ntools-go/core/pool"
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NotFound(conn entity.NetworkEntity, _ packet.IPacket) {
|
|
||||||
nlog.Error("handler not found")
|
|
||||||
_ = conn.SendBytes([]byte("handler not found"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func NotFoundHandler() Handler {
|
|
||||||
return HandlerFunc(NotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
// engine TCP-engine
|
|
||||||
engine struct {
|
|
||||||
conf config.EngineConf // conf 配置
|
|
||||||
taskTimerPrecision time.Duration
|
|
||||||
|
|
||||||
middlewares []Middleware // 中间件
|
|
||||||
routes []Route // 路由
|
|
||||||
// handler 消息处理器
|
|
||||||
handler Handler
|
|
||||||
// dieChan 应用程序退出信号
|
|
||||||
dieChan chan struct{}
|
|
||||||
|
|
||||||
pipeline pipeline.Pipeline // 消息管道
|
|
||||||
lifetime *lifetime.Mgr // 连接的生命周期管理器
|
|
||||||
|
|
||||||
packerFn packet.NewPackerFunc // 封包、拆包器
|
|
||||||
serializer serialize.Serializer // 消息 序列化/反序列化
|
|
||||||
|
|
||||||
wsOpt wsOptions // websocket
|
|
||||||
|
|
||||||
connManager *conn2.Manager
|
|
||||||
sessIdMgr *sessionIDMgr
|
|
||||||
}
|
|
||||||
|
|
||||||
wsOptions struct {
|
|
||||||
IsWebsocket bool // 是否为websocket服务端
|
|
||||||
WebsocketPath string // ws地址(ws://127.0.0.1/WebsocketPath)
|
|
||||||
TLSCertificate string // TLS 证书地址 (websocket)
|
|
||||||
TLSKey string // TLS 证书key地址 (websocket)
|
|
||||||
CheckOrigin func(*http.Request) bool // check origin
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func newEngine(conf config.EngineConf) *engine {
|
|
||||||
s := &engine{
|
|
||||||
conf: conf,
|
|
||||||
dieChan: make(chan struct{}),
|
|
||||||
pipeline: pipeline.New(),
|
|
||||||
middlewares: make([]Middleware, 0),
|
|
||||||
routes: make([]Route, 0),
|
|
||||||
taskTimerPrecision: conf.TaskTimerPrecision,
|
|
||||||
connManager: conn2.NewManager(),
|
|
||||||
sessIdMgr: newSessionIDMgr(),
|
|
||||||
lifetime: lifetime.NewLifetime(),
|
|
||||||
}
|
|
||||||
pool.InitPool(conf.Pool)
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *engine) shallLogDebug() bool {
|
|
||||||
return config.ShallLogDebug(ng.conf.Mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *engine) logPrefix() string {
|
|
||||||
return fmt.Sprintf("[NNet-%s]", ng.conf.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *engine) use(middleware ...Middleware) {
|
|
||||||
ng.middlewares = append(ng.middlewares, middleware...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *engine) addRoutes(route ...Route) {
|
|
||||||
ng.routes = append(ng.routes, route...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *engine) bindRoutes(router Router) error {
|
|
||||||
for _, fr := range ng.routes {
|
|
||||||
if err := ng.bindRoute(router, fr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *engine) bindRoute(router Router, route Route) error {
|
|
||||||
// TODO default middleware
|
|
||||||
chain := newChain()
|
|
||||||
// build chain
|
|
||||||
for _, middleware := range ng.middlewares {
|
|
||||||
chain.Append(convertMiddleware(middleware))
|
|
||||||
}
|
|
||||||
return router.Register(route.Matches, route.Handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertMiddleware(ware Middleware) func(Handler) Handler {
|
|
||||||
return func(next Handler) Handler {
|
|
||||||
return ware(next.Handle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *engine) dial(addr string, router Router) (entity.NetworkEntity, error) {
|
|
||||||
ng.handler = router
|
|
||||||
|
|
||||||
if err := ng.bindRoutes(router); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
go scheduler.Schedule(ng.taskTimerPrecision)
|
|
||||||
|
|
||||||
// connection
|
|
||||||
conn, err := net.Dial("tcp", addr)
|
|
||||||
nlog.Must(err)
|
|
||||||
|
|
||||||
c := newConnection(ng, conn)
|
|
||||||
c.serve()
|
|
||||||
// hook
|
|
||||||
ng.lifetime.Open(c)
|
|
||||||
// connection manager
|
|
||||||
err = ng.connManager.Store(conn2.DefaultGroupName, c)
|
|
||||||
nlog.Must(err)
|
|
||||||
|
|
||||||
// 连接成功,客户端已启动
|
|
||||||
if ng.shallLogDebug() {
|
|
||||||
nlog.Debugf("now connect to %s.", addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *engine) serve(router Router) error {
|
|
||||||
ng.handler = router
|
|
||||||
|
|
||||||
if err := ng.bindRoutes(router); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
go scheduler.Schedule(ng.taskTimerPrecision)
|
|
||||||
defer func() {
|
|
||||||
nlog.Infof("%s is stopping...", ng.logPrefix())
|
|
||||||
|
|
||||||
ng.shutdown()
|
|
||||||
scheduler.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
if ng.wsOpt.IsWebsocket {
|
|
||||||
if len(ng.wsOpt.TLSCertificate) != 0 && len(ng.wsOpt.TLSKey) != 0 {
|
|
||||||
ng.listenAndServeWSTLS()
|
|
||||||
} else {
|
|
||||||
ng.listenAndServeWS()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ng.listenAndServe()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *engine) close() {
|
|
||||||
close(ng.dieChan)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *engine) shutdown() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *engine) listenAndServe() {
|
|
||||||
listener, err := net.Listen(ng.conf.Protocol, ng.conf.Addr)
|
|
||||||
nlog.Must(err)
|
|
||||||
|
|
||||||
// 监听成功,服务已启动
|
|
||||||
if ng.shallLogDebug() {
|
|
||||||
nlog.Debugf("%s now listening %s at %s.", ng.logPrefix(), ng.conf.Protocol, ng.conf.Addr)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = listener.Close()
|
|
||||||
ng.close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
conn, err := listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, net.ErrClosed) {
|
|
||||||
nlog.Errorf("%s 服务器网络错误 %+v", ng.logPrefix(), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
nlog.Errorf("%s 监听错误 %v", ng.logPrefix(), err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err = pool.Submit(func() {
|
|
||||||
ng.handle(conn)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
nlog.Errorf("%s submit conn pool err: %ng", ng.logPrefix(), err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *engine) listenAndServeWS() {
|
|
||||||
ng.setupWS()
|
|
||||||
if ng.shallLogDebug() {
|
|
||||||
nlog.Debugf("%s now listening websocket at %s.", ng.logPrefix(), ng.conf.Addr)
|
|
||||||
}
|
|
||||||
if err := http.ListenAndServe(ng.conf.Addr, nil); err != nil {
|
|
||||||
log.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *engine) listenAndServeWSTLS() {
|
|
||||||
ng.setupWS()
|
|
||||||
if ng.shallLogDebug() {
|
|
||||||
nlog.Debugf("%s now listening websocket with tls at %s.", ng.logPrefix(), ng.conf.Addr)
|
|
||||||
}
|
|
||||||
if err := http.ListenAndServeTLS(ng.conf.Addr, ng.wsOpt.TLSCertificate, ng.wsOpt.TLSKey, nil); err != nil {
|
|
||||||
log.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *engine) setupWS() {
|
|
||||||
upgrade := websocket.Upgrader{
|
|
||||||
ReadBufferSize: 1024,
|
|
||||||
WriteBufferSize: 1024,
|
|
||||||
CheckOrigin: ng.wsOpt.CheckOrigin,
|
|
||||||
}
|
|
||||||
http.HandleFunc("/"+strings.TrimPrefix(ng.wsOpt.WebsocketPath, "/"), func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
conn, err := upgrade.Upgrade(w, r, nil)
|
|
||||||
if err != nil {
|
|
||||||
nlog.Errorf("%s Upgrade failure, URI=%ng, Error=%ng", ng.logPrefix(), r.RequestURI, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = pool.Submit(func() {
|
|
||||||
ng.handleWS(conn)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("%s submit conn pool err: %v", ng.logPrefix(), err.Error())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *engine) handleWS(conn *websocket.Conn) {
|
|
||||||
c := newWSConn(conn)
|
|
||||||
ng.handle(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *engine) handle(conn net.Conn) {
|
|
||||||
c := newConnection(ng, conn)
|
|
||||||
err := ng.connManager.Store(conn2.DefaultGroupName, c)
|
|
||||||
nlog.Must(err)
|
|
||||||
|
|
||||||
c.serve()
|
|
||||||
// hook
|
|
||||||
ng.lifetime.Open(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ng *engine) notFoundHandler(next Handler) Handler {
|
|
||||||
return HandlerFunc(func(entity entity.NetworkEntity, packet packet.IPacket) {
|
|
||||||
h := next
|
|
||||||
if next == nil {
|
|
||||||
h = NotFoundHandler()
|
|
||||||
}
|
|
||||||
// TODO write to client
|
|
||||||
h.Handle(entity, packet)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.noahlan.cn/noahlan/nnet/entity"
|
|
||||||
"git.noahlan.cn/noahlan/nnet/packet"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
Handler interface {
|
|
||||||
Handle(entity entity.NetworkEntity, pkg packet.IPacket)
|
|
||||||
}
|
|
||||||
|
|
||||||
HandlerFunc func(entity entity.NetworkEntity, pkg packet.IPacket)
|
|
||||||
|
|
||||||
Middleware func(next HandlerFunc) HandlerFunc
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f HandlerFunc) Handle(entity entity.NetworkEntity, pkg packet.IPacket) {
|
|
||||||
f(entity, pkg)
|
|
||||||
}
|
|
@ -0,0 +1,135 @@
|
|||||||
|
package nnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.noahlan.cn/noahlan/nnet/config"
|
||||||
|
"git.noahlan.cn/noahlan/nnet/connection"
|
||||||
|
"git.noahlan.cn/noahlan/nnet/lifetime"
|
||||||
|
"git.noahlan.cn/noahlan/nnet/packet"
|
||||||
|
rt "git.noahlan.cn/noahlan/nnet/router"
|
||||||
|
"git.noahlan.cn/noahlan/nnet/scheduler"
|
||||||
|
"git.noahlan.cn/noahlan/nnet/serialize"
|
||||||
|
"git.noahlan.cn/noahlan/nnet/session"
|
||||||
|
"git.noahlan.cn/noahlan/ntools-go/core/nlog"
|
||||||
|
"github.com/panjf2000/ants/v2"
|
||||||
|
"math"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Engine 引擎
|
||||||
|
type Engine struct {
|
||||||
|
config.EngineConf // 引擎配置
|
||||||
|
middlewares []rt.Middleware // 中间件
|
||||||
|
routes []rt.Route // 路由
|
||||||
|
router rt.Router // 消息处理器
|
||||||
|
dieChan chan struct{} // 应用程序退出信号
|
||||||
|
pipeline connection.Pipeline // 消息管道
|
||||||
|
packerBuilder packet.PackerBuilder // 封包、拆包器
|
||||||
|
serializer serialize.Serializer // 消息 序列化/反序列化
|
||||||
|
goPool *ants.Pool // goroutine池
|
||||||
|
connManager *connection.Manager // 连接管理器
|
||||||
|
lifetime *lifetime.Mgr // 生命周期
|
||||||
|
sessIdMgr *session.IDMgr // SessionId管理器
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEngine(conf config.EngineConf, opts ...RunOption) *Engine {
|
||||||
|
ngin := &Engine{
|
||||||
|
EngineConf: conf,
|
||||||
|
middlewares: make([]rt.Middleware, 0),
|
||||||
|
routes: make([]rt.Route, 0),
|
||||||
|
router: rt.NewDefaultRouter(),
|
||||||
|
packerBuilder: nil,
|
||||||
|
serializer: nil,
|
||||||
|
dieChan: make(chan struct{}),
|
||||||
|
pipeline: connection.NewPipeline(),
|
||||||
|
connManager: connection.NewManager(),
|
||||||
|
lifetime: lifetime.NewLifetime(),
|
||||||
|
sessIdMgr: session.NewSessionIDMgr(),
|
||||||
|
goPool: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(ngin)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ngin.goPool == nil {
|
||||||
|
ngin.goPool, _ = ants.NewPool(math.MaxInt32)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ngin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ngin *Engine) Use(middleware ...rt.Middleware) {
|
||||||
|
ngin.middlewares = append(ngin.middlewares, middleware...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ngin *Engine) AddRoutes(rs ...rt.Route) {
|
||||||
|
ngin.routes = append(ngin.routes, rs...)
|
||||||
|
err := ngin.bindRoutes()
|
||||||
|
nlog.Must(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ngin *Engine) bindRoutes() error {
|
||||||
|
for _, fr := range ngin.routes {
|
||||||
|
if err := ngin.bindRoute(fr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ngin *Engine) bindRoute(route rt.Route) error {
|
||||||
|
// TODO default middleware
|
||||||
|
chain := rt.NewChain()
|
||||||
|
// build chain
|
||||||
|
for _, middleware := range ngin.middlewares {
|
||||||
|
chain.Append(rt.ConvertMiddleware(middleware))
|
||||||
|
}
|
||||||
|
return ngin.router.Register(route.Matches, route.Handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ngin *Engine) setup() error {
|
||||||
|
if err := ngin.bindRoutes(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ngin.goPool.Submit(func() {
|
||||||
|
scheduler.Schedule(ngin.TaskTimerPrecision)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ngin *Engine) Stop() {
|
||||||
|
nlog.Infof("%s is stopping...", ngin.LogPrefix())
|
||||||
|
close(ngin.dieChan)
|
||||||
|
scheduler.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ngin *Engine) handle(conn net.Conn) *connection.Connection {
|
||||||
|
nc := connection.NewConnection(
|
||||||
|
ngin.sessIdMgr.SessionID(),
|
||||||
|
conn,
|
||||||
|
connection.Config{LogDebug: ngin.ShallLogDebug(), LogPrefix: ngin.LogPrefix()},
|
||||||
|
ngin.packerBuilder, ngin.serializer, ngin.pipeline,
|
||||||
|
ngin.router.Handle,
|
||||||
|
)
|
||||||
|
|
||||||
|
nc.Serve()
|
||||||
|
|
||||||
|
err := ngin.connManager.Store(connection.DefaultGroupName, nc)
|
||||||
|
nlog.Must(err)
|
||||||
|
|
||||||
|
// dieChan
|
||||||
|
go func() {
|
||||||
|
// lifetime
|
||||||
|
ngin.lifetime.Open(nc)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-nc.DieChan():
|
||||||
|
scheduler.PushTask(func() { ngin.lifetime.Close(nc) })
|
||||||
|
_ = ngin.connManager.Remove(nc)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nc
|
||||||
|
}
|
@ -1,26 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
@ -0,0 +1,43 @@
|
|||||||
|
package modbus
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
var crcTable []uint16
|
||||||
|
var mu sync.Mutex
|
||||||
|
|
||||||
|
// crcModbus 计算modbus的crc
|
||||||
|
func crcModbus(data []byte) (crc uint16) {
|
||||||
|
if crcTable == nil {
|
||||||
|
mu.Lock()
|
||||||
|
if crcTable == nil {
|
||||||
|
initCrcTable()
|
||||||
|
}
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
crc = 0xffff
|
||||||
|
for _, v := range data {
|
||||||
|
crc = (crc >> 8) ^ crcTable[(crc^uint16(v))&0x00FF]
|
||||||
|
}
|
||||||
|
return crc
|
||||||
|
}
|
||||||
|
|
||||||
|
// initCrcTable 初始化crcTable
|
||||||
|
func initCrcTable() {
|
||||||
|
crc16IBM := uint16(0xA001)
|
||||||
|
crcTable = make([]uint16, 256)
|
||||||
|
|
||||||
|
for i := uint16(0); i < 256; i++ {
|
||||||
|
crc := uint16(0)
|
||||||
|
c := i
|
||||||
|
|
||||||
|
for j := uint16(0); j < 8; j++ {
|
||||||
|
if ((crc ^ c) & 0x0001) > 0 {
|
||||||
|
crc = (crc >> 1) ^ crc16IBM
|
||||||
|
} else {
|
||||||
|
crc = crc >> 1
|
||||||
|
}
|
||||||
|
c = c >> 1
|
||||||
|
}
|
||||||
|
crcTable[i] = crc
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package modbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.noahlan.cn/noahlan/nnet/protocol/modbus/internal"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCRC(t *testing.T) {
|
||||||
|
got := crcModbus([]byte{0x01, 0x04, 0x02, 0xFF, 0xFF})
|
||||||
|
expect := uint16(0x80B8)
|
||||||
|
|
||||||
|
assert := internal.NewAssert(t, "TestCRC")
|
||||||
|
assert.Equal(expect, got)
|
||||||
|
}
|
@ -0,0 +1,167 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
compareNotEqual int = iota - 2
|
||||||
|
compareLess
|
||||||
|
compareEqual
|
||||||
|
compareGreater
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assert is a simple implementation of assertion, only for internal usage
|
||||||
|
type Assert struct {
|
||||||
|
T *testing.T
|
||||||
|
CaseName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAssert return instance of Assert
|
||||||
|
func NewAssert(t *testing.T, caseName string) *Assert {
|
||||||
|
return &Assert{T: t, CaseName: caseName}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal check if expected is equal with actual
|
||||||
|
func (a *Assert) Equal(expected, actual any) {
|
||||||
|
if compare(expected, actual) != compareEqual {
|
||||||
|
makeTestFailed(a.T, a.CaseName, expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotEqual check if expected is not equal with actual
|
||||||
|
func (a *Assert) NotEqual(expected, actual any) {
|
||||||
|
if compare(expected, actual) == compareEqual {
|
||||||
|
expectedInfo := fmt.Sprintf("not %v", expected)
|
||||||
|
makeTestFailed(a.T, a.CaseName, expectedInfo, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Greater check if expected is greate than actual
|
||||||
|
func (a *Assert) Greater(expected, actual any) {
|
||||||
|
if compare(expected, actual) != compareGreater {
|
||||||
|
expectedInfo := fmt.Sprintf("> %v", expected)
|
||||||
|
makeTestFailed(a.T, a.CaseName, expectedInfo, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GreaterOrEqual check if expected is greate than or equal with actual
|
||||||
|
func (a *Assert) GreaterOrEqual(expected, actual any) {
|
||||||
|
isGreatOrEqual := compare(expected, actual) == compareGreater || compare(expected, actual) == compareEqual
|
||||||
|
if !isGreatOrEqual {
|
||||||
|
expectedInfo := fmt.Sprintf(">= %v", expected)
|
||||||
|
makeTestFailed(a.T, a.CaseName, expectedInfo, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less check if expected is less than actual
|
||||||
|
func (a *Assert) Less(expected, actual any) {
|
||||||
|
if compare(expected, actual) != compareLess {
|
||||||
|
expectedInfo := fmt.Sprintf("< %v", expected)
|
||||||
|
makeTestFailed(a.T, a.CaseName, expectedInfo, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LessOrEqual check if expected is less than or equal with actual
|
||||||
|
func (a *Assert) LessOrEqual(expected, actual any) {
|
||||||
|
isLessOrEqual := compare(expected, actual) == compareLess || compare(expected, actual) == compareEqual
|
||||||
|
if !isLessOrEqual {
|
||||||
|
expectedInfo := fmt.Sprintf("<= %v", expected)
|
||||||
|
makeTestFailed(a.T, a.CaseName, expectedInfo, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNil check if value is nil
|
||||||
|
func (a *Assert) IsNil(value any) {
|
||||||
|
if value != nil {
|
||||||
|
makeTestFailed(a.T, a.CaseName, nil, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotNil check if value is not nil
|
||||||
|
func (a *Assert) IsNotNil(value any) {
|
||||||
|
if value == nil {
|
||||||
|
makeTestFailed(a.T, a.CaseName, "not nil", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare x and y return :
|
||||||
|
// x > y -> 1, x < y -> -1, x == y -> 0, x != y -> -2
|
||||||
|
func compare(x, y any) int {
|
||||||
|
vx := reflect.ValueOf(x)
|
||||||
|
vy := reflect.ValueOf(y)
|
||||||
|
|
||||||
|
if vx.Type() != vy.Type() {
|
||||||
|
return compareNotEqual
|
||||||
|
}
|
||||||
|
|
||||||
|
switch vx.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
xInt := vx.Int()
|
||||||
|
yInt := vy.Int()
|
||||||
|
if xInt > yInt {
|
||||||
|
return compareGreater
|
||||||
|
}
|
||||||
|
if xInt == yInt {
|
||||||
|
return compareEqual
|
||||||
|
}
|
||||||
|
if xInt < yInt {
|
||||||
|
return compareLess
|
||||||
|
}
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
xUint := vx.Uint()
|
||||||
|
yUint := vy.Uint()
|
||||||
|
if xUint > yUint {
|
||||||
|
return compareGreater
|
||||||
|
}
|
||||||
|
if xUint == yUint {
|
||||||
|
return compareEqual
|
||||||
|
}
|
||||||
|
if xUint < yUint {
|
||||||
|
return compareLess
|
||||||
|
}
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
xFloat := vx.Float()
|
||||||
|
yFloat := vy.Float()
|
||||||
|
if xFloat > yFloat {
|
||||||
|
return compareGreater
|
||||||
|
}
|
||||||
|
if xFloat == yFloat {
|
||||||
|
return compareEqual
|
||||||
|
}
|
||||||
|
if xFloat < yFloat {
|
||||||
|
return compareLess
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
xString := vx.String()
|
||||||
|
yString := vy.String()
|
||||||
|
if xString > yString {
|
||||||
|
return compareGreater
|
||||||
|
}
|
||||||
|
if xString == yString {
|
||||||
|
return compareEqual
|
||||||
|
}
|
||||||
|
if xString < yString {
|
||||||
|
return compareLess
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if reflect.DeepEqual(x, y) {
|
||||||
|
return compareEqual
|
||||||
|
}
|
||||||
|
return compareNotEqual
|
||||||
|
}
|
||||||
|
|
||||||
|
return compareNotEqual
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// logFailedInfo make test failed and log error info
|
||||||
|
func makeTestFailed(t *testing.T, caseName string, expected, actual any) {
|
||||||
|
_, file, line, _ := runtime.Caller(2)
|
||||||
|
errInfo := fmt.Sprintf("Case %v failed. file: %v, line: %v, expected: %v, actual: %v.", caseName, file, line, expected, actual)
|
||||||
|
t.Error(errInfo)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAssert(t *testing.T) {
|
||||||
|
assert := NewAssert(t, "TestAssert")
|
||||||
|
assert.Equal(0, 0)
|
||||||
|
assert.NotEqual(1, 0)
|
||||||
|
|
||||||
|
assert.NotEqual("1", 1)
|
||||||
|
var uInt1 uint
|
||||||
|
var uInt2 uint
|
||||||
|
var uInt8 uint8
|
||||||
|
var uInt16 uint16
|
||||||
|
var uInt32 uint32
|
||||||
|
var uInt64 uint64
|
||||||
|
assert.NotEqual(uInt1, uInt8)
|
||||||
|
assert.NotEqual(uInt8, uInt16)
|
||||||
|
assert.NotEqual(uInt16, uInt32)
|
||||||
|
assert.NotEqual(uInt32, uInt64)
|
||||||
|
|
||||||
|
assert.Equal(uInt1, uInt2)
|
||||||
|
|
||||||
|
uInt1 = 1
|
||||||
|
uInt2 = 2
|
||||||
|
assert.Less(uInt1, uInt2)
|
||||||
|
|
||||||
|
assert.Greater(1, 0)
|
||||||
|
assert.GreaterOrEqual(1, 1)
|
||||||
|
assert.Less(0, 1)
|
||||||
|
assert.LessOrEqual(0, 0)
|
||||||
|
|
||||||
|
assert.Equal(0.1, 0.1)
|
||||||
|
assert.Greater(1.1, 0.1)
|
||||||
|
assert.Less(0.1, 1.1)
|
||||||
|
|
||||||
|
assert.Equal("abc", "abc")
|
||||||
|
assert.NotEqual("abc", "abd")
|
||||||
|
assert.Less("abc", "abd")
|
||||||
|
assert.Greater("abd", "abc")
|
||||||
|
|
||||||
|
assert.Equal([]int{1, 2, 3}, []int{1, 2, 3})
|
||||||
|
assert.NotEqual([]int{1, 2, 3}, []int{1, 2})
|
||||||
|
|
||||||
|
assert.IsNil(nil)
|
||||||
|
assert.IsNotNil("abc")
|
||||||
|
|
||||||
|
}
|
@ -1,15 +1,15 @@
|
|||||||
package plain
|
package plain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.noahlan.cn/noahlan/nnet/core"
|
"git.noahlan.cn/noahlan/nnet"
|
||||||
"git.noahlan.cn/noahlan/nnet/packet"
|
"git.noahlan.cn/noahlan/nnet/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WithPlainProtocol() []core.RunOption {
|
func WithPlainProtocol() []nnet.RunOption {
|
||||||
opts := []core.RunOption{
|
opts := []nnet.RunOption{
|
||||||
withPipeline(),
|
withPipeline(),
|
||||||
core.WithRouter(NewRouter()),
|
nnet.WithRouter(NewRouter()),
|
||||||
core.WithPacker(func() packet.Packer { return NewPacker() }),
|
nnet.WithPackerBuilder(func() packet.Packer { return NewPacker() }),
|
||||||
}
|
}
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
// ToMiddleware converts the given handler to a Middleware.
|
||||||
|
func ToMiddleware(handler func(next Handler) Handler) Middleware {
|
||||||
|
return func(next HandlerFunc) HandlerFunc {
|
||||||
|
return handler(next).Handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMiddlewares adds given middlewares to given routes.
|
||||||
|
func WithMiddlewares(ms []Middleware, rs ...Route) []Route {
|
||||||
|
for i := len(ms) - 1; i >= 0; i-- {
|
||||||
|
rs = WithMiddleware(ms[i], rs...)
|
||||||
|
}
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMiddleware adds given middleware to given route.
|
||||||
|
func WithMiddleware(middleware Middleware, rs ...Route) []Route {
|
||||||
|
routes := make([]Route, len(rs))
|
||||||
|
|
||||||
|
for i := range rs {
|
||||||
|
route := rs[i]
|
||||||
|
routes[i] = Route{
|
||||||
|
Matches: route.Matches,
|
||||||
|
Handler: middleware(route.Handler),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return routes
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
func ConvertMiddleware(ware Middleware) func(Handler) Handler {
|
||||||
|
return func(next Handler) Handler {
|
||||||
|
return ware(next.Handle)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package nnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"git.noahlan.cn/noahlan/nnet/config"
|
||||||
|
"git.noahlan.cn/noahlan/ntools-go/core/nlog"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ngin *Engine) ListenTCP(conf config.TCPServerConf) error {
|
||||||
|
err := ngin.setup()
|
||||||
|
if err != nil {
|
||||||
|
nlog.Errorf("%s failed to setup server, err:%v", ngin.LogPrefix(), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := net.Listen(conf.Protocol, conf.Addr)
|
||||||
|
if err != nil {
|
||||||
|
nlog.Errorf("%s failed to listening at [%s %s] %v", ngin.LogPrefix(), conf.Protocol, conf.Addr, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nlog.Infof("%s now listening %s at %s...", ngin.LogPrefix(), conf.Protocol, conf.Addr)
|
||||||
|
defer func() {
|
||||||
|
_ = listener.Close()
|
||||||
|
ngin.Stop()
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, net.ErrClosed) {
|
||||||
|
nlog.Errorf("%s connection closed, err:%v", ngin.LogPrefix(), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nlog.Errorf("%s accept connection failed, err:%v", ngin.LogPrefix(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ngin.goPool.Submit(func() {
|
||||||
|
ngin.handle(conn)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
nlog.Errorf("%s submit conn pool err: %ng", ngin.LogPrefix(), err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
package nnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.noahlan.cn/noahlan/nnet/config"
|
||||||
|
"git.noahlan.cn/noahlan/nnet/connection"
|
||||||
|
"git.noahlan.cn/noahlan/ntools-go/core/nlog"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListenWebsocket 开始监听Websocket
|
||||||
|
func (ngin *Engine) ListenWebsocket(conf config.WSServerConf) error {
|
||||||
|
err := ngin.setup()
|
||||||
|
if err != nil {
|
||||||
|
nlog.Errorf("%s failed to setup server, err:%v", ngin.LogPrefix(), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nlog.Infof("%s now listening websocket at %s.", ngin.LogPrefix(), conf.Addr)
|
||||||
|
ngin.upgradeWebsocket(conf)
|
||||||
|
if conf.IsTLS() {
|
||||||
|
if err := http.ListenAndServeTLS(conf.Addr, conf.TLSCertificate, conf.TLSKey, nil); err != nil {
|
||||||
|
nlog.Errorf("%s failed to listening websocket with TLS at %s %v", ngin.LogPrefix(), conf.Addr, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := http.ListenAndServe(conf.Addr, nil); err != nil {
|
||||||
|
nlog.Errorf("%s failed to listening websocket at %s %v", ngin.LogPrefix(), conf.Addr, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ngin *Engine) handleWS(conn *websocket.Conn) {
|
||||||
|
wsConn := connection.NewWSConn(conn)
|
||||||
|
ngin.handle(wsConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ngin *Engine) upgradeWebsocket(conf config.WSServerConf) {
|
||||||
|
upgrade := websocket.Upgrader{
|
||||||
|
HandshakeTimeout: conf.HandshakeTimeout,
|
||||||
|
ReadBufferSize: conf.ReadBufferSize,
|
||||||
|
WriteBufferSize: conf.WriteBufferSize,
|
||||||
|
CheckOrigin: conf.CheckOrigin,
|
||||||
|
EnableCompression: conf.Compression,
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/%s", strings.TrimPrefix(conf.Path, "/"))
|
||||||
|
http.HandleFunc(path, func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
conn, err := upgrade.Upgrade(writer, request, nil)
|
||||||
|
if err != nil {
|
||||||
|
nlog.Errorf("%s Upgrade failure, URI=%ng, Error=%ng", ngin.LogPrefix(), request.RequestURI, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = ngin.goPool.Submit(func() {
|
||||||
|
ngin.handleWS(conn)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
nlog.Errorf("%s submit conn pool err: %v", ngin.LogPrefix(), err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package session
|
||||||
|
|
||||||
|
import "sync/atomic"
|
||||||
|
|
||||||
|
type IDMgr struct {
|
||||||
|
count int64
|
||||||
|
sid int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSessionIDMgr() *IDMgr {
|
||||||
|
return &IDMgr{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment the connection count
|
||||||
|
func (c *IDMgr) Increment() {
|
||||||
|
atomic.AddInt64(&c.count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrement the connection count
|
||||||
|
func (c *IDMgr) Decrement() {
|
||||||
|
atomic.AddInt64(&c.count, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the connection numbers in current
|
||||||
|
func (c *IDMgr) Count() int64 {
|
||||||
|
return atomic.LoadInt64(&c.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the connection service status
|
||||||
|
func (c *IDMgr) Reset() {
|
||||||
|
atomic.StoreInt64(&c.count, 0)
|
||||||
|
atomic.StoreInt64(&c.sid, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionID returns the session id
|
||||||
|
func (c *IDMgr) SessionID() int64 {
|
||||||
|
return atomic.AddInt64(&c.sid, 1)
|
||||||
|
}
|
Loading…
Reference in New Issue