client.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  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. "github.com/peterh/liner"
  17. "hnyfkj.com.cn/rtu/linux/baseapp"
  18. )
  19. const MODULE_NAME = "YFKJ_SSH_CLIENT"
  20. var (
  21. coupler *MQTTCoupler
  22. Version = "1.0.0.1"
  23. ErrBrokerAddressEmpty = errors.New("mqtt server address is empty")
  24. ErrIMEINotAvailable = errors.New("device imei is not available")
  25. )
  26. func main() {
  27. if baseapp.IsArgsParam("-h") {
  28. help()
  29. return
  30. }
  31. if baseapp.IsArgsParam("-v") {
  32. fmt.Println("程序版本:", Version, "\n构建时间:", baseapp.BuildTime)
  33. return
  34. }
  35. devIMEI := baseapp.GetArgsParamStr("-c", "")
  36. if devIMEI == "" {
  37. help()
  38. return
  39. }
  40. if err := loadAppConfig(); err != nil {
  41. fmt.Printf("[%s] 错误: %v!!\n", MODULE_NAME, err)
  42. return
  43. }
  44. if CfgServers.MQTTSrv.Address == "" {
  45. fmt.Printf("[%s] 错误: %v!!\n", MODULE_NAME, ErrBrokerAddressEmpty)
  46. return
  47. }
  48. ctx, cancel := context.WithCancel(context.Background())
  49. coupler = &MQTTCoupler{
  50. ctx: ctx,
  51. cancel: cancel,
  52. broker: CfgServers.MQTTSrv.Address,
  53. username: CfgServers.MQTTSrv.Username,
  54. password: CfgServers.MQTTSrv.Password,
  55. clientID: uuid.New().String(),
  56. imei: devIMEI,
  57. cwd: "/",
  58. }
  59. if err := coupler.init2(); err != nil {
  60. fmt.Printf("[%s] 错误: %v\n!!", MODULE_NAME, err)
  61. return
  62. }
  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. line := liner.NewLiner()
  80. defer line.Close()
  81. line.SetCtrlCAborts(false)
  82. line.SetTabCompletionStyle(liner.TabCircular)
  83. historyFile := "history.txt"
  84. var history []string
  85. if f, err := os.Open(historyFile); err == nil {
  86. scanner := bufio.NewScanner(f)
  87. for scanner.Scan() {
  88. cmd := strings.TrimSpace(scanner.Text())
  89. if cmd != "" {
  90. history = append(history, cmd)
  91. line.AppendHistory(cmd)
  92. }
  93. }
  94. f.Close()
  95. }
  96. defer func() {
  97. if f, err := os.Create(historyFile); err == nil {
  98. line.WriteHistory(f)
  99. f.Close()
  100. }
  101. }()
  102. line.SetCompleter(func(input string) []string { // 补全
  103. var matches []string
  104. for _, cmd := range history {
  105. if strings.HasPrefix(cmd, input) {
  106. matches = append(matches, cmd)
  107. }
  108. }
  109. return matches
  110. })
  111. for {
  112. if !pingState.Load() {
  113. fmt.Printf("[%s] 目标设备连接丢失!!\n", MODULE_NAME)
  114. break
  115. }
  116. if interrupted.Swap(false) {
  117. fmt.Println("^C")
  118. }
  119. prompt := fmt.Sprintf("root@%s:%s# ", coupler.imei, coupler.cwd)
  120. input, err := line.Prompt(prompt) /// 等待用户输入指令
  121. if err == nil {
  122. goto inputOK
  123. }
  124. if err == io.EOF { ////////////////// Ctrl+D 按键处理
  125. _, _ = coupler.quit()
  126. fmt.Println("bye")
  127. break
  128. }
  129. fmt.Println("读取用户输入失败:", err)
  130. continue
  131. inputOK:
  132. input = strings.TrimSpace(input)
  133. if input == "" {
  134. continue
  135. }
  136. line.AppendHistory(input) ///// 保存用户输入的历史命令
  137. history = append(history, input) ///// 本次也立刻生效
  138. if input == "exit" || input == "quit" {
  139. _, _ = coupler.quit()
  140. break
  141. }
  142. executing.Store(true)
  143. result, err := coupler.exec(input)
  144. executing.Store(false)
  145. if err != nil {
  146. fmt.Printf("执行错误: %v\n", err)
  147. continue
  148. }
  149. if result.Stdout != "" {
  150. fmt.Print(result.Stdout)
  151. }
  152. if result.Stderr != "" {
  153. fmt.Fprintln(os.Stderr, result.Stderr)
  154. }
  155. if result.ExitCode == 124 {
  156. fmt.Println("command timeout")
  157. }
  158. if result.Cwd != "" {
  159. coupler.cwd = result.Cwd
  160. }
  161. }
  162. }
  163. func help() {
  164. h := `
  165. -h 显示帮助提示
  166. -v 当前程序版本
  167. -c 连接目标设备(IMEI), 例如: -c 869523059113051
  168. `
  169. fmt.Println(h)
  170. }
  171. func interruptLoop(executing *atomic.Bool, interrupted *atomic.Bool) {
  172. sigCh := make(chan os.Signal, 1)
  173. signal.Notify(sigCh, syscall.SIGINT)
  174. go func() {
  175. for range sigCh {
  176. interrupted.Store(true)
  177. if executing.Load() {
  178. _, _ = coupler.stop()
  179. }
  180. }
  181. }()
  182. }
  183. func heartbeatLoop(pingState *atomic.Bool) {
  184. go func() {
  185. ticker := time.NewTicker(1 * time.Second)
  186. defer ticker.Stop()
  187. pingFailCount := 0
  188. pong := ""
  189. for range ticker.C {
  190. resp, err := coupler.ping()
  191. if err == nil && resp.Error == nil && resp.Result != nil &&
  192. json.Unmarshal(resp.Result, &pong) == nil && pong == "pong" {
  193. pingState.Store(true)
  194. pingFailCount = 0
  195. } else {
  196. pingFailCount++
  197. if pingFailCount >= 3 { ///// 连续3次ping失败, 可以认为设备离线
  198. pingState.Store(false)
  199. }
  200. } // end if
  201. } // end for
  202. }()
  203. }
  204. func printWelcome() {
  205. welcome := `
  206. _ _ _ _ _ _ _ _
  207. | \ | (_) | | | (_) | |
  208. | \| |_| |_ ___| |__ _| | | ___
  209. | . | | __/ __| '_ \| | | |/ _ \
  210. | |\ | | || (__| | | | | | | __/
  211. |_| \_|_|\__\___|_| |_|_|_|_|\___|
  212. ══════════════════════════════════
  213. 云飞科技RTU远程运维终端
  214. ══════════════════════════════════
  215. 提示: 输入'quit'命令, 退出终端
  216. `
  217. fmt.Println(welcome)
  218. }