mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-08-26 16:46:14 +08:00
380 lines
9.2 KiB
Go
380 lines
9.2 KiB
Go
// Package tcp
|
||
// @Link https://github.com/bufanyun/hotgo
|
||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||
// @Author Ms <133814250@qq.com>
|
||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||
package tcp
|
||
|
||
import (
|
||
"context"
|
||
"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 // 最大重连次数,0不限次数
|
||
ConnectCount uint // 已重连次数
|
||
AutoReconnect bool // 是否开启自动重连
|
||
LoginEvent CallbackEvent // 登录成功事件
|
||
CloseEvent CallbackEvent // 连接关闭事件
|
||
}
|
||
|
||
// Client 客户端
|
||
type Client struct {
|
||
Ctx context.Context // 上下文
|
||
Logger *glog.Logger // 日志处理器
|
||
IsLogin bool // 是否已登录
|
||
addr string // 连接地址
|
||
auth *AuthMeta // 认证元数据
|
||
rpc *Rpc // rpc协议支持
|
||
timeout time.Duration // 连接超时时间
|
||
connectInterval time.Duration // 重连时间间隔
|
||
maxConnectCount uint // 最大重连次数,0不限次数
|
||
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 // 停止标签,停止以后不能重连
|
||
}
|
||
|
||
// NewClient 初始化一个tcp客户端
|
||
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
|
||
client.Logger = g.Log("tcpClient")
|
||
|
||
if config.ConnectInterval <= 0 {
|
||
client.connectInterval = 5 * time.Second
|
||
} else {
|
||
client.connectInterval = config.ConnectInterval
|
||
}
|
||
|
||
if config.Timeout <= 0 {
|
||
client.timeout = 10 * time.Second
|
||
} else {
|
||
client.timeout = config.Timeout
|
||
}
|
||
|
||
client.rpc = NewRpc(client.Ctx)
|
||
return
|
||
}
|
||
|
||
// Start 启动tcp连接
|
||
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
|
||
}
|
||
|
||
// dial
|
||
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 {
|
||
if !client.stopFlag {
|
||
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()
|
||
}
|
||
|
||
// read
|
||
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
|
||
}
|
||
|
||
switch msg.Router {
|
||
case "ResponseServerLogin", "ResponseServerHeartbeat": // 服务登录、心跳无需验证签名
|
||
ctx, cancel := initCtx(gctx.New(), &Context{})
|
||
doHandleRouterMsg(f, ctx, cancel, msg.Data)
|
||
default: // 通用路由消息处理
|
||
in, err := VerifySign(msg.Data, client.auth.AppId, client.auth.SecretKey)
|
||
if err != nil {
|
||
client.Logger.Warningf(client.Ctx, "client read VerifySign err:%+v message: %+v", err, msg)
|
||
continue
|
||
}
|
||
|
||
ctx, cancel := initCtx(gctx.New(), &Context{
|
||
Conn: client.conn,
|
||
Auth: client.auth,
|
||
TraceID: in.TraceID,
|
||
})
|
||
|
||
// 响应rpc消息
|
||
if client.rpc.HandleMsg(ctx, cancel, msg.Data) {
|
||
return
|
||
}
|
||
|
||
doHandleRouterMsg(f, ctx, cancel, msg.Data)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 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()
|
||
}
|
||
|
||
// IsStop 是否已停止
|
||
func (client *Client) IsStop() bool {
|
||
return client.stopFlag
|
||
}
|
||
|
||
// 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")
|
||
}
|
||
|
||
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}
|
||
return SendPkg(client.conn, msg)
|
||
}
|
||
|
||
// Send 发送消息
|
||
func (client *Client) Send(ctx context.Context, data interface{}) error {
|
||
MsgPkg(data, client.auth, gctx.CtxId(ctx))
|
||
return client.Write(data)
|
||
}
|
||
|
||
// Reply 回复消息
|
||
func (client *Client) Reply(ctx context.Context, data interface{}) (err error) {
|
||
user := GetCtx(ctx)
|
||
if user == nil {
|
||
err = gerror.New("获取回复用户信息失败")
|
||
return
|
||
}
|
||
MsgPkg(data, client.auth, user.TraceID)
|
||
return client.Write(data)
|
||
}
|
||
|
||
// RpcRequest 发送消息并等待响应结果
|
||
func (client *Client) RpcRequest(ctx context.Context, data interface{}) (res interface{}, err error) {
|
||
var (
|
||
traceID = MsgPkg(data, client.auth, gctx.CtxId(ctx))
|
||
key = client.rpc.GetCallId(client.conn, traceID)
|
||
)
|
||
|
||
if traceID == "" {
|
||
err = gerror.New("traceID is required")
|
||
return
|
||
}
|
||
return client.rpc.Request(key, func() {
|
||
_ = client.Write(data)
|
||
})
|
||
}
|