// 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/container/gtype" "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/grpool" "hotgo/utility/simple" "sync" "time" ) // Client tcp客户端 type Client struct { ctx context.Context // 上下文 conn *Conn // 连接对象 config *ClientConfig // 配置 msgParser *MsgParser // 消息处理器 logger *glog.Logger // 日志处理器 isLogin *gtype.Bool // 是否已登录 taskGo *grpool.Pool // 任务协程池 closeFlag *gtype.Bool // 关闭标签,关闭以后可以重连 stopFlag *gtype.Bool // 停止标签,停止以后不能重连 wg sync.WaitGroup // 流程控制 mutex sync.Mutex // 状态锁 } // 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 // 连接关闭事件 } // CallbackEvent 回调事件 type CallbackEvent func() // NewClient 初始化一个tcp客户端 func NewClient(config *ClientConfig) (client *Client) { client = new(Client) client.ctx = gctx.New() client.logger = g.Log("tcpClient") baseErr := gerror.New("NewClient fail") if config == nil { client.logger.Fatal(client.ctx, gerror.Wrap(baseErr, "config is nil")) return } if config.Addr == "" { client.logger.Fatal(client.ctx, gerror.Wrap(baseErr, "client address is not set")) return } client.config = config if config.ConnectInterval <= 0 { client.config.ConnectInterval = 5 * time.Second } else { client.config.ConnectInterval = config.ConnectInterval } if config.Timeout <= 0 { client.config.Timeout = 10 * time.Second } else { client.config.Timeout = config.Timeout } client.isLogin = gtype.NewBool(false) client.closeFlag = gtype.NewBool(false) client.stopFlag = gtype.NewBool(false) client.taskGo = grpool.New(5) client.msgParser = NewMsgParser(client.handleRoutineTask) client.registerDefaultRouter() return } // Start 启动tcp连接 func (client *Client) Start() (err error) { client.mutex.Lock() defer client.mutex.Unlock() if client.stopFlag.Val() { err = gerror.New("client is stop") return } if client.conn != nil { return gerror.New("client is running") } client.isLogin.Set(false) client.config.ConnectCount = 0 client.closeFlag.Set(false) client.stopFlag.Set(false) client.wg.Add(1) simple.SafeGo(client.ctx, func(ctx context.Context) { client.connect() }) return } // registerDefaultRouter 注册默认路由 func (client *Client) registerDefaultRouter() { var routers = []interface{}{ client.onResponseServerLogin, // 服务登录 client.onResponseServerHeartbeat, // 心跳 } client.RegisterRouter(routers...) } // RegisterRouter 注册路由 func (client *Client) RegisterRouter(routers ...interface{}) { err := client.msgParser.RegisterRouter(routers...) if err != nil { client.logger.Fatal(client.ctx, err) } } // RegisterRPCRouter 注册RPC路由 func (client *Client) RegisterRPCRouter(routers ...interface{}) { err := client.msgParser.RegisterRPCRouter(routers...) if err != nil { client.logger.Fatal(client.ctx, err) } } // RegisterInterceptor 注册拦截器 func (client *Client) RegisterInterceptor(interceptors ...Interceptor) { client.msgParser.RegisterInterceptor(interceptors...) } // dial func (client *Client) dial() *gtcp.Conn { for { conn, err := gtcp.NewConn(client.config.Addr, client.config.Timeout) if err == nil || client.closeFlag.Val() { return conn } if client.config.MaxConnectCount > 0 { if client.config.ConnectCount < client.config.MaxConnectCount { client.config.ConnectCount += 1 } else { return nil } } client.logger.Debugf(client.ctx, "connect to %v error: %v", client.config.Addr, err) time.Sleep(client.config.ConnectInterval) continue } } func (client *Client) connect() { defer client.wg.Done() goto reconnect reconnect: conn := client.dial() if conn == nil { if !client.stopFlag.Val() { client.logger.Debugf(client.ctx, "client dial failed") } return } client.mutex.Lock() if client.closeFlag.Val() { client.mutex.Unlock() conn.Close() client.logger.Debugf(client.ctx, "client connect but closeFlag is true") return } client.conn = NewConn(conn, client.logger, client.msgParser) client.config.ConnectCount = 0 client.read() client.mutex.Unlock() client.serverLogin() client.startCron() } // read func (client *Client) read() { go func() { defer func() { client.Close() client.logger.Debugf(client.ctx, "client are about to be reconnected..") time.Sleep(client.config.ConnectInterval) _ = client.Start() }() if err := client.conn.Run(); err != nil { client.logger.Debug(client.ctx, err) } }() } // Close 关闭同服务器的链接 func (client *Client) Close() { client.mutex.Lock() defer client.mutex.Unlock() client.isLogin.Set(false) client.closeFlag.Set(true) if client.conn != nil { client.conn.Close() client.conn = nil } if client.config.CloseEvent != nil { client.config.CloseEvent() } client.wg.Wait() } // Destroy 销毁当前连接 func (client *Client) Destroy() { client.stopCron() if client.conn != nil { client.conn.Close() client.conn = nil } } // Stop 停止服务 func (client *Client) Stop() { if client.stopFlag.Val() { return } client.stopFlag.Set(true) client.stopCron() client.Close() } // IsStop 是否已停止 func (client *Client) IsStop() bool { return client.stopFlag.Val() } // IsLogin 是否已登录成功 func (client *Client) IsLogin() bool { return client.isLogin.Val() } func (client *Client) handleRoutineTask(ctx context.Context, task func()) { ctx, cancel := context.WithCancel(ctx) err := client.taskGo.AddWithRecover(ctx, func(ctx context.Context) { task() cancel() }, func(ctx context.Context, err error) { client.logger.Warningf(ctx, "routineTask exec err:%+v", err) cancel() }, ) if err != nil { client.logger.Warningf(ctx, "routineTask add err:%+v", err) } } // Conn 获取当前连接 func (client *Client) Conn() *Conn { return client.conn } // Send 发送消息 func (client *Client) Send(ctx context.Context, data interface{}) error { if client.conn == nil { return gerror.New("conn is nil") } return client.conn.Send(ctx, data) } // Request 发送消息并等待响应结果 func (client *Client) Request(ctx context.Context, data interface{}) (interface{}, error) { return client.conn.Request(ctx, data) } // RequestScan 发送消息并等待响应结果,将结果保存在response中 func (client *Client) RequestScan(ctx context.Context, data, response interface{}) error { return client.conn.RequestScan(ctx, data, response) }