Files
hotgo/server/internal/library/network/tcp/client.go

380 lines
9.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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)
})
}