client.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  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. fmt.Printf("[%s] 正在连接目标设备...\n", MODULE_NAME)
  63. var pingState atomic.Bool
  64. heartbeatLoop(&pingState) // -启动-设备在线-心跳检测-
  65. for {
  66. if pingState.Load() { //// 等待成功连接上目标设备卍
  67. break
  68. }
  69. fmt.Printf("[%s] 无法连接目标设备!!\n", MODULE_NAME)
  70. time.Sleep(1 * time.Second)
  71. }
  72. term(&pingState) /////////////////// 启动终端模拟器卍
  73. }
  74. func term(pingState *atomic.Bool) {
  75. var executing atomic.Bool // 是否有正在执行中的命令
  76. var interrupted atomic.Bool // 用户是否按键取消了命令
  77. interruptLoop(&executing, &interrupted) // Ctrl+C卍
  78. printWelcome()
  79. reader := bufio.NewReader(os.Stdin)
  80. for {
  81. if !pingState.Load() {
  82. fmt.Printf("[%s] 目标设备连接丢失!!\n", MODULE_NAME)
  83. break
  84. }
  85. fmt.Print("\033[?25h") // 显示光标
  86. if interrupted.Swap(false) {
  87. fmt.Println("^C")
  88. }
  89. fmt.Printf("root@%s:%s# ", coupler.imei, coupler.cwd)
  90. input, err := reader.ReadString('\n')
  91. if err != nil {
  92. if err == io.EOF {
  93. os.Exit(0)
  94. }
  95. fmt.Println("读取用户输入失败:", err)
  96. continue
  97. }
  98. input = strings.TrimSpace(input)
  99. if input == "" {
  100. continue
  101. }
  102. switch input {
  103. case "quit":
  104. _, _ = coupler.quit()
  105. os.Exit(0)
  106. }
  107. fmt.Print("\033[?25l") // 隐藏光标
  108. executing.Store(true)
  109. result, err := coupler.exec(input)
  110. executing.Store(false)
  111. if err != nil {
  112. fmt.Printf("执行错误: %v\n", err)
  113. continue
  114. }
  115. if result.Stdout != "" {
  116. fmt.Println(result.Stdout)
  117. }
  118. if result.Stderr != "" {
  119. fmt.Fprintln(os.Stderr, result.Stderr)
  120. }
  121. if result.Cwd != "" {
  122. coupler.cwd = result.Cwd
  123. }
  124. }
  125. }
  126. func help() {
  127. h := `
  128. -h 显示帮助提示
  129. -v 当前程序版本
  130. -c 连接目标设备(IMEI), 例如: -c 869523059113051
  131. `
  132. fmt.Println(h)
  133. }
  134. func interruptLoop(executing *atomic.Bool, interrupted *atomic.Bool) {
  135. sigCh := make(chan os.Signal, 1)
  136. signal.Notify(sigCh, syscall.SIGINT)
  137. go func() {
  138. for range sigCh {
  139. interrupted.Store(true)
  140. if executing.Load() {
  141. _, _ = coupler.stop()
  142. }
  143. }
  144. }()
  145. }
  146. func heartbeatLoop(pingState *atomic.Bool) {
  147. go func() {
  148. ticker := time.NewTicker(1 * time.Second)
  149. defer ticker.Stop()
  150. pingFailCount := 0
  151. pong := ""
  152. for range ticker.C {
  153. resp, err := coupler.ping()
  154. if err == nil && resp.Error == nil && resp.Result != nil &&
  155. json.Unmarshal(resp.Result, &pong) == nil && pong == "pong" {
  156. pingState.Store(true)
  157. pingFailCount = 0
  158. } else {
  159. pingFailCount++
  160. if pingFailCount >= 3 { ///// 连续3次ping失败, 可以认为设备离线
  161. pingState.Store(false)
  162. }
  163. }
  164. } // end for
  165. }()
  166. }
  167. func printWelcome() {
  168. welcome := `
  169. _ _ _ _ _ _ _ _
  170. | \ | (_) | | | (_) | |
  171. | \| |_| |_ ___| |__ _| | | ___
  172. | . | | __/ __| '_ \| | | |/ _ \
  173. | |\ | | || (__| | | | | | | __/
  174. |_| \_|_|\__\___|_| |_|_|_|_|\___|
  175. ═══════════════════════════════════
  176. 云飞科技 RTU远程运维终端
  177. ═══════════════════════════════════
  178. 提示: 输入'quit'命令, 可退出终端模拟器
  179. `
  180. fmt.Println(welcome)
  181. }