client.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. package main
  2. import (
  3. "bufio"
  4. "context"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "os"
  10. "os/signal"
  11. "strings"
  12. "sync/atomic"
  13. "syscall"
  14. "time"
  15. "github.com/google/uuid"
  16. "hnyfkj.com.cn/rtu/linux/baseapp"
  17. )
  18. const MODULE_NAME = "YFKJ_SSH_CLIENT"
  19. var (
  20. coupler *MQTTCoupler
  21. Version = "1.0.0.1"
  22. ErrBrokerAddressEmpty = errors.New("mqtt server address is empty")
  23. ErrIMEINotAvailable = errors.New("device imei is not available")
  24. )
  25. func main() {
  26. if baseapp.IsArgsParam("-h") {
  27. help()
  28. return
  29. }
  30. if baseapp.IsArgsParam("-v") {
  31. fmt.Println("程序版本:", Version, "\n构建时间:", baseapp.BuildTime)
  32. return
  33. }
  34. devIMEI := baseapp.GetArgsParamStr("-c", "")
  35. if devIMEI == "" {
  36. help()
  37. return
  38. }
  39. if err := loadAppConfig(); err != nil {
  40. fmt.Printf("[%s] 错误: %v!!\n", MODULE_NAME, err)
  41. return
  42. }
  43. if CfgServers.MQTTSrv.Address == "" {
  44. fmt.Printf("[%s] 错误: %v!!\n", MODULE_NAME, ErrBrokerAddressEmpty)
  45. return
  46. }
  47. ctx, cancel := context.WithCancel(context.Background())
  48. coupler = &MQTTCoupler{
  49. ctx: ctx,
  50. cancel: cancel,
  51. broker: CfgServers.MQTTSrv.Address,
  52. username: CfgServers.MQTTSrv.Username,
  53. password: CfgServers.MQTTSrv.Password,
  54. clientID: uuid.New().String(),
  55. imei: devIMEI,
  56. cwd: "/",
  57. }
  58. if err := coupler.init2(); err != nil {
  59. fmt.Printf("[%s] 错误: %v\n!!", MODULE_NAME, err)
  60. return
  61. }
  62. var pingState atomic.Bool
  63. heartbeatLoop(&pingState) // -启动-设备在线-心跳检测-
  64. for {
  65. if pingState.Load() { //// 等待成功连接上目标设备卍
  66. break
  67. }
  68. fmt.Printf("[%s] 尝试连接目标设备....\n", MODULE_NAME)
  69. time.Sleep(1 * time.Second)
  70. }
  71. term(&pingState) /////////////////// 启动终端模拟器卍
  72. }
  73. func term(pingState *atomic.Bool) {
  74. var executing atomic.Bool // 是否有正在执行中的命令
  75. var interrupted atomic.Bool // 用户是否按键取消了命令
  76. interruptLoop(&executing, &interrupted) // Ctrl+C卍
  77. printWelcome()
  78. reader := bufio.NewReader(os.Stdin)
  79. for {
  80. if !pingState.Load() {
  81. fmt.Printf("[%s] 目标设备连接丢失!!\n", MODULE_NAME)
  82. break
  83. }
  84. fmt.Print("\033[?25h") // 显示光标
  85. if interrupted.Swap(false) {
  86. fmt.Println("^C")
  87. }
  88. fmt.Printf("root@%s:%s# ", coupler.imei, coupler.cwd)
  89. input, err := reader.ReadString('\n')
  90. if err != nil {
  91. if err == io.EOF {
  92. os.Exit(0)
  93. }
  94. fmt.Println("读取用户输入失败: ", err)
  95. continue
  96. }
  97. input = strings.TrimSpace(input)
  98. if input == "" {
  99. continue
  100. }
  101. switch input {
  102. case "quit":
  103. _, _ = coupler.quit()
  104. os.Exit(0)
  105. }
  106. fmt.Print("\033[?25l") // 隐藏光标
  107. executing.Store(true)
  108. result, err := coupler.exec(input)
  109. executing.Store(false)
  110. if err != nil {
  111. fmt.Printf("执行错误: %v\n", err)
  112. continue
  113. }
  114. if result.Stdout != "" {
  115. fmt.Print(result.Stdout)
  116. }
  117. if result.Stderr != "" {
  118. fmt.Fprint(os.Stderr, result.Stderr)
  119. }
  120. if result.Cwd != "" {
  121. coupler.cwd = result.Cwd
  122. }
  123. }
  124. }
  125. func help() {
  126. h := `
  127. -h 显示帮助提示
  128. -v 当前程序版本
  129. -c 连接目标设备(IMEI), 例如: -c 869523059113051
  130. `
  131. fmt.Println(h)
  132. }
  133. func interruptLoop(executing *atomic.Bool, interrupted *atomic.Bool) {
  134. sigCh := make(chan os.Signal, 1)
  135. signal.Notify(sigCh, syscall.SIGINT)
  136. go func() {
  137. for range sigCh {
  138. interrupted.Store(true)
  139. if executing.Load() {
  140. _, _ = coupler.stop()
  141. }
  142. }
  143. }()
  144. }
  145. func heartbeatLoop(pingState *atomic.Bool) {
  146. go func() {
  147. ticker := time.NewTicker(1 * time.Second)
  148. defer ticker.Stop()
  149. pingFailCount := 0
  150. pong := ""
  151. for range ticker.C {
  152. resp, err := coupler.ping()
  153. if err == nil && resp.Error == nil && resp.Result != nil &&
  154. json.Unmarshal(resp.Result, &pong) == nil && pong == "pong" {
  155. pingState.Store(true)
  156. pingFailCount = 0
  157. } else {
  158. pingFailCount++
  159. if pingFailCount >= 3 { ///// 连续3次ping失败, 可以认为设备离线
  160. pingState.Store(false)
  161. }
  162. }
  163. } // end for
  164. }()
  165. }
  166. func printWelcome() {
  167. welcome := `
  168. _ _ _ _ _ _ _ _
  169. | \ | (_) | | | (_) | |
  170. | \| |_| |_ ___| |__ _| | | ___
  171. | . | | __/ __| '_ \| | | |/ _ \
  172. | |\ | | || (__| | | | | | | __/
  173. |_| \_|_|\__\___|_| |_|_|_|_|\___|
  174. ══════════════════════════════════
  175. 云飞科技RTU远程运维终端
  176. ══════════════════════════════════
  177. 提示: 输入'quit'命令, 退出终端
  178. `
  179. fmt.Println(welcome)
  180. }