mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-11-09 18:53:46 +08:00
This commit is contained in:
322
server/internal/library/network/tcp/client.go
Normal file
322
server/internal/library/network/tcp/client.go
Normal file
@@ -0,0 +1,322 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"hotgo/utility/simple"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ClientConfig 客户端配置
|
||||
type ClientConfig struct {
|
||||
Addr string
|
||||
Auth *AuthMeta
|
||||
Timeout time.Duration
|
||||
ConnectInterval time.Duration
|
||||
MaxConnectCount uint
|
||||
ConnectCount uint
|
||||
AutoReconnect bool
|
||||
LoginEvent CallbackEvent
|
||||
CloseEvent CallbackEvent
|
||||
}
|
||||
|
||||
// Client 客户端
|
||||
type Client struct {
|
||||
Ctx context.Context
|
||||
Logger *glog.Logger
|
||||
IsLogin bool // 是否已登录
|
||||
addr string
|
||||
auth *AuthMeta
|
||||
timeout time.Duration
|
||||
connectInterval time.Duration
|
||||
maxConnectCount uint
|
||||
connectCount uint
|
||||
autoReconnect bool
|
||||
loginEvent CallbackEvent
|
||||
closeEvent CallbackEvent
|
||||
sync.Mutex
|
||||
heartbeat int64
|
||||
routers map[string]RouterHandler
|
||||
conn *gtcp.Conn
|
||||
wg sync.WaitGroup
|
||||
closeFlag bool // 关闭标签,关闭以后可以重连
|
||||
stopFlag bool // 停止标签,停止以后不能重连
|
||||
}
|
||||
|
||||
func NewClient(config *ClientConfig) (client *Client, err error) {
|
||||
client = new(Client)
|
||||
|
||||
if config == nil {
|
||||
err = gerror.New("config is nil")
|
||||
return
|
||||
}
|
||||
|
||||
if config.Addr == "" {
|
||||
err = gerror.New("client address is not set")
|
||||
return
|
||||
}
|
||||
|
||||
if config.Auth == nil {
|
||||
err = gerror.New("client auth cannot be empty")
|
||||
return
|
||||
}
|
||||
|
||||
if config.Auth.Group == "" || config.Auth.Name == "" {
|
||||
err = gerror.New("Auth.Group or Auth.Group is nil")
|
||||
return
|
||||
}
|
||||
|
||||
client.Ctx = gctx.New()
|
||||
client.autoReconnect = true
|
||||
client.addr = config.Addr
|
||||
client.auth = config.Auth
|
||||
client.loginEvent = config.LoginEvent
|
||||
client.closeEvent = config.CloseEvent
|
||||
|
||||
logger := glog.New()
|
||||
path := g.Cfg().MustGet(client.Ctx, "logger.path", "logs/logger").String()
|
||||
if err = logger.SetPath(fmt.Sprintf("%s/tcp.client/%s.%s", path, config.Auth.Group, config.Auth.Name)); err != nil {
|
||||
return
|
||||
}
|
||||
client.Logger = logger
|
||||
|
||||
if config.ConnectInterval <= 0 {
|
||||
client.connectInterval = 5 * time.Second
|
||||
client.Logger.Debugf(client.Ctx, "invalid connectInterval, reset to %v", client.connectInterval)
|
||||
} else {
|
||||
client.connectInterval = config.ConnectInterval
|
||||
}
|
||||
|
||||
if config.Timeout <= 0 {
|
||||
client.timeout = 10 * time.Second
|
||||
client.Logger.Debugf(client.Ctx, "invalid timeout, reset to %v", client.timeout)
|
||||
} else {
|
||||
client.timeout = config.Timeout
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Start 启动
|
||||
func (client *Client) Start() (err error) {
|
||||
client.Lock()
|
||||
defer client.Unlock()
|
||||
|
||||
if client.stopFlag {
|
||||
err = gerror.New("client is stop")
|
||||
return
|
||||
}
|
||||
|
||||
if client.conn != nil {
|
||||
return gerror.New("client is running")
|
||||
}
|
||||
|
||||
client.IsLogin = false
|
||||
client.connectCount = 0
|
||||
client.closeFlag = false
|
||||
client.stopFlag = false
|
||||
|
||||
client.wg.Add(1)
|
||||
simple.SafeGo(client.Ctx, func(ctx context.Context) {
|
||||
client.connect()
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RegisterRouter 注册路由
|
||||
func (client *Client) RegisterRouter(routers map[string]RouterHandler) (err error) {
|
||||
if client.conn != nil {
|
||||
return gerror.New("client is running")
|
||||
}
|
||||
|
||||
client.Lock()
|
||||
defer client.Unlock()
|
||||
|
||||
if client.routers == nil {
|
||||
client.routers = make(map[string]RouterHandler)
|
||||
// 默认路由
|
||||
client.routers = map[string]RouterHandler{
|
||||
"ResponseServerHeartbeat": client.onResponseServerHeartbeat,
|
||||
"ResponseServerLogin": client.onResponseServerLogin,
|
||||
}
|
||||
}
|
||||
|
||||
for i, router := range routers {
|
||||
_, ok := client.routers[i]
|
||||
if ok {
|
||||
return gerror.Newf("client route duplicate registration:%v", i)
|
||||
}
|
||||
client.routers[i] = router
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (client *Client) dial() *gtcp.Conn {
|
||||
for {
|
||||
conn, err := gtcp.NewConn(client.addr, client.timeout)
|
||||
if err == nil || client.closeFlag {
|
||||
return conn
|
||||
}
|
||||
|
||||
if client.maxConnectCount > 0 {
|
||||
if client.connectCount < client.maxConnectCount {
|
||||
client.connectCount += 1
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
client.Logger.Debugf(client.Ctx, "connect to %v error: %v", client.addr, err)
|
||||
time.Sleep(client.connectInterval)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) connect() {
|
||||
defer client.wg.Done()
|
||||
|
||||
goto reconnect
|
||||
reconnect:
|
||||
conn := client.dial()
|
||||
if conn == nil {
|
||||
client.Logger.Debugf(client.Ctx, "client dial failed")
|
||||
return
|
||||
}
|
||||
|
||||
client.Lock()
|
||||
if client.closeFlag {
|
||||
client.Unlock()
|
||||
conn.Close()
|
||||
client.Logger.Debugf(client.Ctx, "client connect but closeFlag is true")
|
||||
return
|
||||
}
|
||||
|
||||
client.conn = conn
|
||||
client.connectCount = 0
|
||||
client.heartbeat = gtime.Timestamp()
|
||||
|
||||
client.read()
|
||||
client.Unlock()
|
||||
|
||||
client.serverLogin()
|
||||
client.startCron()
|
||||
}
|
||||
|
||||
func (client *Client) read() {
|
||||
simple.SafeGo(client.Ctx, func(ctx context.Context) {
|
||||
defer func() {
|
||||
client.Close()
|
||||
client.Logger.Debugf(client.Ctx, "client are about to be reconnected..")
|
||||
time.Sleep(client.connectInterval)
|
||||
client.Start()
|
||||
}()
|
||||
|
||||
for {
|
||||
if client.conn == nil {
|
||||
client.Logger.Debugf(client.Ctx, "client client.conn is nil, server closed")
|
||||
break
|
||||
}
|
||||
|
||||
msg, err := RecvPkg(client.conn)
|
||||
if err != nil {
|
||||
client.Logger.Debugf(client.Ctx, "client RecvPkg err:%+v, server closed", err)
|
||||
break
|
||||
}
|
||||
|
||||
if client.routers == nil {
|
||||
client.Logger.Debugf(client.Ctx, "client RecvPkg routers is nil")
|
||||
break
|
||||
}
|
||||
|
||||
if msg == nil {
|
||||
client.Logger.Debugf(client.Ctx, "client RecvPkg msg is nil")
|
||||
break
|
||||
}
|
||||
|
||||
f, ok := client.routers[msg.Router]
|
||||
if !ok {
|
||||
client.Logger.Debugf(client.Ctx, "client RecvPkg invalid message: %+v", msg)
|
||||
continue
|
||||
}
|
||||
f(msg.Data, client.conn)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Close 关闭同服务器的链接
|
||||
func (client *Client) Close() {
|
||||
client.Lock()
|
||||
defer client.Unlock()
|
||||
|
||||
client.IsLogin = false
|
||||
client.closeFlag = true
|
||||
if client.conn != nil {
|
||||
client.conn.Close()
|
||||
client.conn = nil
|
||||
}
|
||||
|
||||
if client.closeEvent != nil {
|
||||
client.closeEvent()
|
||||
}
|
||||
client.wg.Wait()
|
||||
}
|
||||
|
||||
// Stop 停止服务
|
||||
func (client *Client) Stop() {
|
||||
if client.stopFlag {
|
||||
return
|
||||
}
|
||||
client.stopFlag = true
|
||||
client.stopCron()
|
||||
client.Close()
|
||||
}
|
||||
|
||||
// Destroy 销毁当前连接
|
||||
func (client *Client) Destroy() {
|
||||
client.stopCron()
|
||||
if client.conn != nil {
|
||||
client.conn.Close()
|
||||
client.conn = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Write
|
||||
func (client *Client) Write(data interface{}) error {
|
||||
client.Lock()
|
||||
defer client.Unlock()
|
||||
|
||||
if client.conn == nil {
|
||||
return gerror.New("client conn is nil")
|
||||
}
|
||||
|
||||
if client.closeFlag {
|
||||
return gerror.New("client conn is closed")
|
||||
}
|
||||
|
||||
if data == nil {
|
||||
return gerror.New("client Write message is nil")
|
||||
}
|
||||
|
||||
// 签名
|
||||
SetSign(data, gctx.CtxId(client.Ctx), client.auth.AppId, client.auth.SecretKey)
|
||||
|
||||
msgType := reflect.TypeOf(data)
|
||||
if msgType == nil || msgType.Kind() != reflect.Ptr {
|
||||
return gerror.Newf("client json message pointer required: %+v", data)
|
||||
}
|
||||
msg := &Message{Router: msgType.Elem().Name(), Data: data}
|
||||
|
||||
client.Logger.Debugf(client.Ctx, "client Write Router:%v, data:%+v", msg.Router, gjson.New(data).String())
|
||||
|
||||
return SendPkg(client.conn, msg)
|
||||
}
|
||||
37
server/internal/library/network/tcp/client_cron.go
Normal file
37
server/internal/library/network/tcp/client_cron.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/os/gcron"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
func (client *Client) getCronKey(s string) string {
|
||||
return fmt.Sprintf("tcp.client_%s_%s:%s", s, client.auth.Group, client.auth.Name)
|
||||
}
|
||||
|
||||
func (client *Client) stopCron() {
|
||||
for _, v := range gcron.Entries() {
|
||||
gcron.Remove(v.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) startCron() {
|
||||
// 心跳超时检查
|
||||
if gcron.Search(client.getCronKey(cronHeartbeatVerify)) == nil {
|
||||
gcron.AddSingleton(client.Ctx, "@every 600s", func(ctx context.Context) {
|
||||
if client.heartbeat < gtime.Timestamp()-600 {
|
||||
client.Logger.Debugf(client.Ctx, "client heartbeat timeout, about to reconnect..")
|
||||
client.Destroy()
|
||||
}
|
||||
}, client.getCronKey(cronHeartbeatVerify))
|
||||
}
|
||||
|
||||
// 心跳
|
||||
if gcron.Search(client.getCronKey(cronHeartbeat)) == nil {
|
||||
gcron.AddSingleton(client.Ctx, "@every 120s", func(ctx context.Context) {
|
||||
client.serverHeartbeat()
|
||||
}, client.getCronKey(cronHeartbeat))
|
||||
}
|
||||
}
|
||||
61
server/internal/library/network/tcp/client_handle.go
Normal file
61
server/internal/library/network/tcp/client_handle.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/internal/model/input/msgin"
|
||||
)
|
||||
|
||||
// serverLogin 心跳
|
||||
func (client *Client) serverHeartbeat() {
|
||||
if err := client.Write(&msgin.ServerHeartbeat{}); err != nil {
|
||||
client.Logger.Debugf(client.Ctx, "client WriteMsg ServerHeartbeat err:%+v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// serverLogin 服务登陆
|
||||
func (client *Client) serverLogin() {
|
||||
data := &msgin.ServerLogin{
|
||||
Group: client.auth.Group,
|
||||
Name: client.auth.Name,
|
||||
}
|
||||
|
||||
if err := client.Write(data); err != nil {
|
||||
client.Logger.Debugf(client.Ctx, "client WriteMsg ServerLogin err:%+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if client.loginEvent != nil {
|
||||
client.loginEvent()
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) onResponseServerLogin(args ...interface{}) {
|
||||
var in *msgin.ResponseServerLogin
|
||||
if err := gconv.Scan(args[0], &in); err != nil {
|
||||
client.Logger.Infof(client.Ctx, "onResponseServerLogin message Scan failed:%+v, args:%+v", err, args[0])
|
||||
return
|
||||
}
|
||||
client.Logger.Infof(client.Ctx, "onResponseServerLogin in:%+v", *in)
|
||||
|
||||
if in.Code != gcode.CodeOK.Code() {
|
||||
client.IsLogin = false
|
||||
client.Logger.Warningf(client.Ctx, "onResponseServerLogin quit err:%v", in.Message)
|
||||
client.Destroy()
|
||||
return
|
||||
}
|
||||
client.IsLogin = true
|
||||
}
|
||||
|
||||
func (client *Client) onResponseServerHeartbeat(args ...interface{}) {
|
||||
var in *msgin.ResponseServerHeartbeat
|
||||
if err := gconv.Scan(args[0], &in); err != nil {
|
||||
client.Logger.Infof(client.Ctx, "onResponseServerHeartbeat message Scan failed:%+v, args:%+v", err, args)
|
||||
return
|
||||
}
|
||||
|
||||
client.heartbeat = gtime.Timestamp()
|
||||
client.Logger.Infof(client.Ctx, "onResponseServerHeartbeat in:%+v", *in)
|
||||
}
|
||||
25
server/internal/library/network/tcp/model.go
Normal file
25
server/internal/library/network/tcp/model.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package tcp
|
||||
|
||||
// 定时任务
|
||||
const (
|
||||
cronHeartbeatVerify = "tcpHeartbeatVerify"
|
||||
cronHeartbeat = "tcpHeartbeat"
|
||||
)
|
||||
|
||||
// 认证分组
|
||||
const (
|
||||
ClientGroupCron = "cron" // 定时任务
|
||||
ClientGroupQueue = "queue" // 消息队列
|
||||
ClientGroupAuth = "auth" // 服务授权
|
||||
)
|
||||
|
||||
// AuthMeta 认证元数据
|
||||
type AuthMeta struct {
|
||||
Group string `json:"group"`
|
||||
Name string `json:"name"`
|
||||
AppId string `json:"appId"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
}
|
||||
|
||||
// CallbackEvent 回调事件
|
||||
type CallbackEvent func()
|
||||
39
server/internal/library/network/tcp/router.go
Normal file
39
server/internal/library/network/tcp/router.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type RouterHandler func(args ...interface{})
|
||||
|
||||
// Message 路由消息
|
||||
type Message struct {
|
||||
Router string `json:"router"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
func SendPkg(conn *gtcp.Conn, message *Message) error {
|
||||
b, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return conn.SendPkg(b)
|
||||
}
|
||||
|
||||
func RecvPkg(conn *gtcp.Conn) (*Message, error) {
|
||||
if data, err := conn.RecvPkg(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
var msg = new(Message)
|
||||
if err = gconv.Scan(data, &msg); err != nil {
|
||||
return nil, gerror.Newf("invalid package structure: %s", err.Error())
|
||||
}
|
||||
if msg.Router == "" {
|
||||
return nil, gerror.Newf("message is not routed: %+v", msg)
|
||||
}
|
||||
return msg, err
|
||||
}
|
||||
}
|
||||
278
server/internal/library/network/tcp/server.go
Normal file
278
server/internal/library/network/tcp/server.go
Normal file
@@ -0,0 +1,278 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ClientConn struct {
|
||||
Conn *gtcp.Conn
|
||||
Auth *AuthMeta
|
||||
heartbeat int64
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Name string // 服务名称
|
||||
Addr string // 监听地址
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Ctx context.Context
|
||||
Logger *glog.Logger
|
||||
addr string
|
||||
name string
|
||||
ln *gtcp.Server
|
||||
wgLn sync.WaitGroup
|
||||
mutex sync.Mutex
|
||||
closeFlag bool
|
||||
clients map[string]*ClientConn // 已登录的认证客户端
|
||||
mutexConns sync.Mutex
|
||||
wgConns sync.WaitGroup
|
||||
cronRouters map[string]RouterHandler // 路由
|
||||
queueRouters map[string]RouterHandler
|
||||
authRouters map[string]RouterHandler
|
||||
}
|
||||
|
||||
func NewServer(config *ServerConfig) (server *Server, err error) {
|
||||
if config == nil {
|
||||
err = gerror.New("config is nil")
|
||||
return
|
||||
}
|
||||
|
||||
if config.Addr == "" {
|
||||
err = gerror.New("server address is not set")
|
||||
return
|
||||
}
|
||||
|
||||
if config.Name == "" {
|
||||
config.Name = "hotgo"
|
||||
}
|
||||
|
||||
server = new(Server)
|
||||
server.Ctx = gctx.New()
|
||||
server.addr = config.Addr
|
||||
server.name = config.Name
|
||||
server.ln = gtcp.NewServer(server.addr, server.accept, config.Name)
|
||||
server.clients = make(map[string]*ClientConn)
|
||||
server.closeFlag = false
|
||||
|
||||
logger := glog.New()
|
||||
path := g.Cfg().MustGet(server.Ctx, "logger.path", "logs/logger").String()
|
||||
if err = logger.SetPath(fmt.Sprintf("%s/tcp.server/%s", path, config.Name)); err != nil {
|
||||
return
|
||||
}
|
||||
server.Logger = logger
|
||||
|
||||
server.startCron()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (server *Server) accept(conn *gtcp.Conn) {
|
||||
defer func() {
|
||||
server.mutexConns.Lock()
|
||||
conn.Close()
|
||||
// 从登录列表中移除
|
||||
if _, ok := server.clients[conn.RemoteAddr().String()]; ok {
|
||||
delete(server.clients, conn.RemoteAddr().String())
|
||||
}
|
||||
server.mutexConns.Unlock()
|
||||
}()
|
||||
|
||||
for {
|
||||
msg, err := RecvPkg(conn)
|
||||
if err != nil {
|
||||
server.Logger.Debugf(server.Ctx, "RecvPkg err:%+v, client closed.", err)
|
||||
break
|
||||
}
|
||||
|
||||
client := server.getLoginConn(conn)
|
||||
|
||||
switch msg.Router {
|
||||
case "ServerLogin": // 服务登录
|
||||
server.onServerLogin(msg.Data, conn)
|
||||
case "ServerHeartbeat": // 心跳
|
||||
if client == nil {
|
||||
server.Logger.Infof(server.Ctx, "conn not connected, ignore the heartbeat, msg:%+v", msg)
|
||||
continue
|
||||
}
|
||||
server.onServerHeartbeat(msg, client)
|
||||
default: // 通用路由消息处理
|
||||
if client == nil {
|
||||
server.Logger.Warningf(server.Ctx, "conn is not logged in but sends a routing message. actively conn disconnect, msg:%+v", msg)
|
||||
time.Sleep(time.Second)
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
server.handleRouterMsg(msg, client)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleRouterMsg 处理路由消息
|
||||
func (server *Server) handleRouterMsg(msg *Message, client *ClientConn) {
|
||||
|
||||
// 验证签名
|
||||
err := VerifySign(msg.Data, client.Auth.AppId, client.Auth.SecretKey)
|
||||
if err != nil {
|
||||
server.Logger.Warningf(server.Ctx, "handleRouterMsg VerifySign err:%+v message: %+v", err, msg)
|
||||
return
|
||||
}
|
||||
|
||||
handle := func(routers map[string]RouterHandler, group string) {
|
||||
if routers == nil {
|
||||
server.Logger.Debugf(server.Ctx, "handleRouterMsg route is not initialized %v message: %+v", group, msg)
|
||||
return
|
||||
}
|
||||
f, ok := routers[msg.Router]
|
||||
if !ok {
|
||||
server.Logger.Debugf(server.Ctx, "handleRouterMsg invalid %v message: %+v", group, msg)
|
||||
return
|
||||
}
|
||||
f(msg.Data, client)
|
||||
}
|
||||
|
||||
switch client.Auth.Group {
|
||||
case ClientGroupCron:
|
||||
handle(server.cronRouters, client.Auth.Group)
|
||||
case ClientGroupQueue:
|
||||
handle(server.queueRouters, client.Auth.Group)
|
||||
case ClientGroupAuth:
|
||||
handle(server.authRouters, client.Auth.Group)
|
||||
default:
|
||||
server.Logger.Warningf(server.Ctx, "group is not registered: %+v", client.Auth.Group)
|
||||
}
|
||||
}
|
||||
|
||||
// getLoginConn 获取指定已登录的连接
|
||||
func (server *Server) getLoginConn(conn *gtcp.Conn) *ClientConn {
|
||||
client, ok := server.clients[conn.RemoteAddr().String()]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// getLoginConn 获取指定appid的所有连接
|
||||
func (server *Server) getAppIdClients(appid string) (list []*ClientConn) {
|
||||
for _, v := range server.clients {
|
||||
if v.Auth.AppId == appid {
|
||||
list = append(list, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RegisterAuthRouter 注册授权路由
|
||||
func (server *Server) RegisterAuthRouter(routers map[string]RouterHandler) {
|
||||
server.mutex.Lock()
|
||||
defer server.mutex.Unlock()
|
||||
|
||||
if server.authRouters == nil {
|
||||
server.authRouters = make(map[string]RouterHandler)
|
||||
}
|
||||
|
||||
for i, router := range routers {
|
||||
_, ok := server.authRouters[i]
|
||||
if ok {
|
||||
server.Logger.Debugf(server.Ctx, "server authRouters duplicate registration:%v", i)
|
||||
continue
|
||||
}
|
||||
server.authRouters[i] = router
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterCronRouter 注册任务路由
|
||||
func (server *Server) RegisterCronRouter(routers map[string]RouterHandler) {
|
||||
server.mutex.Lock()
|
||||
defer server.mutex.Unlock()
|
||||
|
||||
if server.cronRouters == nil {
|
||||
server.cronRouters = make(map[string]RouterHandler)
|
||||
}
|
||||
|
||||
for i, router := range routers {
|
||||
_, ok := server.cronRouters[i]
|
||||
if ok {
|
||||
server.Logger.Debugf(server.Ctx, "server cronRouters duplicate registration:%v", i)
|
||||
continue
|
||||
}
|
||||
server.cronRouters[i] = router
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterQueueRouter 注册队列路由
|
||||
func (server *Server) RegisterQueueRouter(routers map[string]RouterHandler) {
|
||||
server.mutex.Lock()
|
||||
defer server.mutex.Unlock()
|
||||
|
||||
if server.queueRouters == nil {
|
||||
server.queueRouters = make(map[string]RouterHandler)
|
||||
}
|
||||
|
||||
for i, router := range routers {
|
||||
_, ok := server.queueRouters[i]
|
||||
if ok {
|
||||
server.Logger.Debugf(server.Ctx, "server queueRouters duplicate registration:%v", i)
|
||||
continue
|
||||
}
|
||||
server.queueRouters[i] = router
|
||||
}
|
||||
}
|
||||
|
||||
func (server *Server) Listen() (err error) {
|
||||
server.wgLn.Add(1)
|
||||
defer server.wgLn.Done()
|
||||
return server.ln.Run()
|
||||
}
|
||||
|
||||
// Close 关闭服务
|
||||
func (server *Server) Close() {
|
||||
if server.closeFlag {
|
||||
return
|
||||
}
|
||||
server.closeFlag = true
|
||||
|
||||
server.stopCron()
|
||||
|
||||
server.mutexConns.Lock()
|
||||
for _, client := range server.clients {
|
||||
client.Conn.Close()
|
||||
}
|
||||
server.clients = nil
|
||||
server.mutexConns.Unlock()
|
||||
server.wgConns.Wait()
|
||||
|
||||
if server.ln != nil {
|
||||
server.ln.Close()
|
||||
}
|
||||
server.wgLn.Wait()
|
||||
}
|
||||
|
||||
// Write 向指定客户端发送消息
|
||||
func (server *Server) Write(conn *gtcp.Conn, data interface{}) (err error) {
|
||||
if server.closeFlag {
|
||||
return gerror.New("service is down")
|
||||
}
|
||||
|
||||
msgType := reflect.TypeOf(data)
|
||||
if msgType == nil || msgType.Kind() != reflect.Ptr {
|
||||
return gerror.Newf("json message pointer required: %+v", data)
|
||||
}
|
||||
|
||||
msg := &Message{Router: msgType.Elem().Name(), Data: data}
|
||||
|
||||
server.Logger.Debugf(server.Ctx, "server Write Router:%v, data:%+v", msg.Router, gjson.New(data).String())
|
||||
|
||||
return SendPkg(conn, msg)
|
||||
}
|
||||
35
server/internal/library/network/tcp/server_cron.go
Normal file
35
server/internal/library/network/tcp/server_cron.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/os/gcron"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
func (server *Server) getCronKey(s string) string {
|
||||
return fmt.Sprintf("tcp.server_%s_%s", s, server.name)
|
||||
}
|
||||
|
||||
func (server *Server) stopCron() {
|
||||
for _, v := range gcron.Entries() {
|
||||
gcron.Remove(v.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func (server *Server) startCron() {
|
||||
// 心跳超时检查
|
||||
if gcron.Search(server.getCronKey(cronHeartbeatVerify)) == nil {
|
||||
gcron.AddSingleton(server.Ctx, "@every 300s", func(ctx context.Context) {
|
||||
if server.clients == nil {
|
||||
return
|
||||
}
|
||||
for _, client := range server.clients {
|
||||
if client.heartbeat < gtime.Timestamp()-300 {
|
||||
client.Conn.Close()
|
||||
server.Logger.Debugf(server.Ctx, "client heartbeat timeout, about to reconnect.. auth:%+v", client.Auth)
|
||||
}
|
||||
}
|
||||
}, server.getCronKey(cronHeartbeatVerify))
|
||||
}
|
||||
}
|
||||
150
server/internal/library/network/tcp/server_handle.go
Normal file
150
server/internal/library/network/tcp/server_handle.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/gtcp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/internal/model/input/msgin"
|
||||
"hotgo/utility/convert"
|
||||
)
|
||||
|
||||
func (server *Server) onServerLogin(args ...interface{}) {
|
||||
var (
|
||||
in = new(msgin.ServerLogin)
|
||||
conn = args[1].(*gtcp.Conn)
|
||||
res = new(msgin.ResponseServerLogin)
|
||||
models *entity.SysServeLicense
|
||||
)
|
||||
|
||||
if err := gconv.Scan(args[0], &in); err != nil {
|
||||
server.Logger.Infof(server.Ctx, "onServerLogin message Scan failed:%+v, args:%+v", err, args)
|
||||
return
|
||||
}
|
||||
server.Logger.Infof(server.Ctx, "onServerLogin in:%+v", *in)
|
||||
|
||||
err := g.Model("sys_serve_license").
|
||||
Ctx(server.Ctx).
|
||||
Where("appid = ?", in.AppId).
|
||||
Scan(&models)
|
||||
|
||||
if err != nil {
|
||||
res.Code = 1
|
||||
res.Message = err.Error()
|
||||
server.Write(conn, res)
|
||||
return
|
||||
}
|
||||
|
||||
if models == nil {
|
||||
res.Code = 2
|
||||
res.Message = "授权信息不存在"
|
||||
server.Write(conn, res)
|
||||
return
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
if err = VerifySign(in, models.Appid, models.SecretKey); err != nil {
|
||||
res.Code = 3
|
||||
res.Message = "签名错误,请联系管理员"
|
||||
server.Write(conn, res)
|
||||
return
|
||||
}
|
||||
|
||||
if models.Status != consts.StatusEnabled {
|
||||
res.Code = 4
|
||||
res.Message = "授权已禁用,请联系管理员"
|
||||
server.Write(conn, res)
|
||||
return
|
||||
}
|
||||
|
||||
if models.Group != in.Group {
|
||||
res.Code = 5
|
||||
res.Message = "你登录的授权分组未得到授权,请联系管理员"
|
||||
server.Write(conn, res)
|
||||
return
|
||||
}
|
||||
|
||||
if models.EndAt.Before(gtime.Now()) {
|
||||
res.Code = 6
|
||||
res.Message = "授权已过期,请联系管理员"
|
||||
server.Write(conn, res)
|
||||
return
|
||||
}
|
||||
|
||||
allowedIps := convert.IpFilterStrategy(models.AllowedIps)
|
||||
if _, ok := allowedIps["*"]; !ok {
|
||||
ip := gstr.StrTillEx(conn.RemoteAddr().String(), ":")
|
||||
if _, ok2 := allowedIps[ip]; !ok2 {
|
||||
res.Code = 7
|
||||
res.Message = "IP(" + ip + ")未授权,请联系管理员"
|
||||
server.Write(conn, res)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否存在多地登录,如果连接超出上限,直接将所有已连接断开,然后在吧新的连接加入进来
|
||||
clients := server.getAppIdClients(models.Appid)
|
||||
online := len(clients) + 1
|
||||
if online > models.OnlineLimit {
|
||||
online = 1
|
||||
res2 := new(msgin.ResponseServerLogin)
|
||||
res2.Code = 8
|
||||
res2.Message = "授权登录端超出上限,请联系管理员"
|
||||
for _, client := range clients {
|
||||
server.Write(client.Conn, res2)
|
||||
client.Conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
server.mutexConns.Lock()
|
||||
server.clients[conn.RemoteAddr().String()] = &ClientConn{
|
||||
Conn: conn,
|
||||
Auth: &AuthMeta{
|
||||
Group: in.Group,
|
||||
Name: in.Name,
|
||||
AppId: in.AppId,
|
||||
SecretKey: models.SecretKey,
|
||||
},
|
||||
heartbeat: gtime.Timestamp(),
|
||||
}
|
||||
server.mutexConns.Unlock()
|
||||
|
||||
server.Write(conn, res)
|
||||
|
||||
_, err = g.Model("sys_serve_license").
|
||||
Ctx(server.Ctx).
|
||||
Where("id = ?", models.Id).Data(g.Map{
|
||||
"online": online,
|
||||
"login_times": models.LoginTimes + 1,
|
||||
"last_login_at": gtime.Now(),
|
||||
"last_active_at": gtime.Now(),
|
||||
"remote_addr": conn.RemoteAddr().String(),
|
||||
}).Update()
|
||||
if err != nil {
|
||||
server.Logger.Warningf(server.Ctx, "onServerLogin Update err:%+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (server *Server) onServerHeartbeat(args ...interface{}) {
|
||||
var in *msgin.ServerHeartbeat
|
||||
if err := gconv.Scan(args, &in); err != nil {
|
||||
server.Logger.Infof(server.Ctx, "onServerHeartbeat message Scan failed:%+v, args:%+v", err, args)
|
||||
return
|
||||
}
|
||||
client := args[1].(*ClientConn)
|
||||
client.heartbeat = gtime.Timestamp()
|
||||
|
||||
server.Write(client.Conn, &msgin.ResponseServerHeartbeat{})
|
||||
|
||||
_, err := g.Model("sys_serve_license").
|
||||
Ctx(server.Ctx).
|
||||
Where("appid = ?", client.Auth.AppId).Data(g.Map{
|
||||
"last_active_at": gtime.Now(),
|
||||
}).Update()
|
||||
if err != nil {
|
||||
server.Logger.Warningf(server.Ctx, "onServerHeartbeat Update err:%+v", err)
|
||||
}
|
||||
}
|
||||
41
server/internal/library/network/tcp/sign.go
Normal file
41
server/internal/library/network/tcp/sign.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/internal/model/input/msgin"
|
||||
)
|
||||
|
||||
type Sign interface {
|
||||
SetSign(traceID, appId, secretKey string)
|
||||
}
|
||||
|
||||
// SetSign 设置签名
|
||||
func SetSign(data interface{}, traceID, appId, secretKey string) {
|
||||
if c, ok := data.(Sign); ok {
|
||||
c.SetSign(traceID, appId, secretKey)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// VerifySign 验证签名
|
||||
func VerifySign(data interface{}, appId, secretKey string) (err error) {
|
||||
// 无密钥,无需签名
|
||||
if secretKey == "" {
|
||||
return
|
||||
}
|
||||
|
||||
var in *msgin.Request
|
||||
if err = gconv.Scan(data, &in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if appId != in.AppId {
|
||||
return gerror.New("appId invalid")
|
||||
}
|
||||
|
||||
if in.Sign != in.GetSign(secretKey) {
|
||||
return gerror.New("sign invalid")
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user