Przeglądaj źródła

优化修改sshd模块客户端代码,使其交互更加友好

niujiuru 2 tygodni temu
rodzic
commit
bdd5b44c1f
4 zmienionych plików z 261 dodań i 19 usunięć
  1. 2 0
      go.mod
  2. 5 0
      go.sum
  3. 40 19
      sshd/client/client.go
  4. 214 0
      sshd/client/client.go.old

+ 2 - 0
go.mod

@@ -9,6 +9,7 @@ require (
 	github.com/google/uuid v1.6.0
 	github.com/jlaffaye/ftp v0.2.0
 	github.com/mattn/go-shellwords v1.0.12
+	github.com/peterh/liner v1.2.2
 	github.com/sirupsen/logrus v1.9.3
 	github.com/vishvananda/netlink v1.3.1
 	gopkg.in/ini.v1 v1.67.0
@@ -19,6 +20,7 @@ require (
 	github.com/gorilla/websocket v1.5.3 // indirect
 	github.com/hashicorp/errwrap v1.0.0 // indirect
 	github.com/hashicorp/go-multierror v1.1.1 // indirect
+	github.com/mattn/go-runewidth v0.0.3 // indirect
 	github.com/vishvananda/netns v0.0.5 // indirect
 	golang.org/x/net v0.44.0 // indirect
 	golang.org/x/sync v0.17.0 // indirect

+ 5 - 0
go.sum

@@ -17,8 +17,12 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
 github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
 github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg=
 github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI=
+github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
+github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
 github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
+github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw=
+github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
@@ -36,6 +40,7 @@ golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
 golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
 golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
 golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

+ 40 - 19
sshd/client/client.go

@@ -1,7 +1,6 @@
 package main
 
 import (
-	"bufio"
 	"context"
 	"encoding/json"
 	"errors"
@@ -15,6 +14,8 @@ import (
 	"time"
 
 	"github.com/google/uuid"
+	"github.com/peterh/liner"
+
 	"hnyfkj.com.cn/rtu/linux/baseapp"
 )
 
@@ -76,7 +77,7 @@ func main() {
 		if pingState.Load() { //// 等待成功连接上目标设备卍
 			break
 		}
-		fmt.Printf("[%s] 正在尝试连接设备....\n", MODULE_NAME)
+		fmt.Printf("[%s] 正在尝试连接设备...\n", MODULE_NAME)
 		time.Sleep(1 * time.Second)
 	}
 
@@ -91,40 +92,61 @@ func term(pingState *atomic.Bool) {
 
 	printWelcome()
 
-	reader := bufio.NewReader(os.Stdin)
+	line := liner.NewLiner()
+	defer line.Close()
+	line.SetCtrlCAborts(false)
+	line.SetTabCompletionStyle(liner.TabCircular)
+
+	historyFile := "history.txt"
+	if f, err := os.Open(historyFile); err == nil {
+		line.ReadHistory(f)
+		f.Close()
+	}
+	defer func() {
+		if f, err := os.Create(historyFile); err == nil {
+			line.WriteHistory(f)
+			f.Close()
+		}
+	}()
+
 	for {
 		if !pingState.Load() {
 			fmt.Printf("[%s] 目标设备连接丢失!!\n", MODULE_NAME)
 			break
 		}
 
-		fmt.Print("\033[?25h") // 显示光标
 		if interrupted.Swap(false) {
 			fmt.Println("^C")
 		}
-		fmt.Printf("root@%s:%s# ", coupler.imei, coupler.cwd)
 
-		input, err := reader.ReadString('\n')
-		if err != nil {
-			if err == io.EOF {
-				os.Exit(0)
-			}
-			fmt.Println("读取用户输入失败: ", err)
-			continue
+		prompt := fmt.Sprintf("root@%s:%s# ", coupler.imei, coupler.cwd)
+		input, err := line.Prompt(prompt) /// 等待用户输入指令
+		if err == nil {
+			goto inputOK
 		}
 
+		if err == io.EOF { ////////////////// Ctrl+D 按键处理
+			_, _ = coupler.quit()
+			fmt.Println("bye")
+			break
+		}
+
+		fmt.Println("读取用户输入失败:", err)
+		continue
+
+	inputOK:
 		input = strings.TrimSpace(input)
 		if input == "" {
 			continue
 		}
 
-		switch input {
-		case "quit":
+		line.AppendHistory(input) ///// 保存用户输入的历史命令
+
+		if input == "exit" || input == "quit" {
 			_, _ = coupler.quit()
-			os.Exit(0)
+			break
 		}
 
-		fmt.Print("\033[?25l") // 隐藏光标
 		executing.Store(true)
 		result, err := coupler.exec(input)
 		executing.Store(false)
@@ -141,7 +163,7 @@ func term(pingState *atomic.Bool) {
 			fmt.Fprintln(os.Stderr, result.Stderr)
 		}
 		if result.ExitCode == 124 {
-			fmt.Println("command timed out")
+			fmt.Println("command timeout")
 		}
 
 		if result.Cwd != "" {
@@ -177,7 +199,6 @@ func heartbeatLoop(pingState *atomic.Bool) {
 	go func() {
 		ticker := time.NewTicker(1 * time.Second)
 		defer ticker.Stop()
-
 		pingFailCount := 0
 		pong := ""
 		for range ticker.C {
@@ -191,7 +212,7 @@ func heartbeatLoop(pingState *atomic.Bool) {
 				if pingFailCount >= 3 { ///// 连续3次ping失败, 可以认为设备离线
 					pingState.Store(false)
 				}
-			}
+			} // end if
 		} // end for
 	}()
 }

+ 214 - 0
sshd/client/client.go.old

@@ -0,0 +1,214 @@
+package main
+
+import (
+	"bufio"
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"os"
+	"os/signal"
+	"strings"
+	"sync/atomic"
+	"syscall"
+	"time"
+
+	"github.com/google/uuid"
+	"hnyfkj.com.cn/rtu/linux/baseapp"
+)
+
+const MODULE_NAME = "YFKJ_SSH_CLIENT"
+
+var (
+	coupler               *MQTTCoupler
+	Version               = "1.0.0.1"
+	ErrBrokerAddressEmpty = errors.New("mqtt server address is empty")
+	ErrIMEINotAvailable   = errors.New("device imei is not available")
+)
+
+func main() {
+	if baseapp.IsArgsParam("-h") {
+		help()
+		return
+	}
+
+	if baseapp.IsArgsParam("-v") {
+		fmt.Println("程序版本:", Version, "\n构建时间:", baseapp.BuildTime)
+		return
+	}
+
+	devIMEI := baseapp.GetArgsParamStr("-c", "")
+	if devIMEI == "" {
+		help()
+		return
+	}
+
+	if err := loadAppConfig(); err != nil {
+		fmt.Printf("[%s] 错误: %v!!\n", MODULE_NAME, err)
+		return
+	}
+	if CfgServers.MQTTSrv.Address == "" {
+		fmt.Printf("[%s] 错误: %v!!\n", MODULE_NAME, ErrBrokerAddressEmpty)
+		return
+	}
+
+	ctx, cancel := context.WithCancel(context.Background())
+	coupler = &MQTTCoupler{
+		ctx:      ctx,
+		cancel:   cancel,
+		broker:   CfgServers.MQTTSrv.Address,
+		username: CfgServers.MQTTSrv.Username,
+		password: CfgServers.MQTTSrv.Password,
+		clientID: uuid.New().String(),
+		imei:     devIMEI,
+		cwd:      "/",
+	}
+
+	if err := coupler.init2(); err != nil {
+		fmt.Printf("[%s] 错误: %v\n!!", MODULE_NAME, err)
+		return
+	}
+
+	var pingState atomic.Bool
+	heartbeatLoop(&pingState) // -启动-设备在线-心跳检测-
+	for {
+		if pingState.Load() { //// 等待成功连接上目标设备卍
+			break
+		}
+		fmt.Printf("[%s] 正在尝试连接设备....\n", MODULE_NAME)
+		time.Sleep(1 * time.Second)
+	}
+
+	term(&pingState) /////////////////// 启动终端模拟器卍
+}
+
+func term(pingState *atomic.Bool) {
+	var executing atomic.Bool   // 是否有正在执行中的命令
+	var interrupted atomic.Bool // 用户是否按键取消了命令
+
+	interruptLoop(&executing, &interrupted) // Ctrl+C卍
+
+	printWelcome()
+
+	reader := bufio.NewReader(os.Stdin)
+	for {
+		if !pingState.Load() {
+			fmt.Printf("[%s] 目标设备连接丢失!!\n", MODULE_NAME)
+			break
+		}
+
+		fmt.Print("\033[?25h") // 显示光标
+		if interrupted.Swap(false) {
+			fmt.Println("^C")
+		}
+		fmt.Printf("root@%s:%s# ", coupler.imei, coupler.cwd)
+
+		input, err := reader.ReadString('\n')
+		if err != nil {
+			if err == io.EOF {
+				os.Exit(0)
+			}
+			fmt.Println("读取用户输入失败: ", err)
+			continue
+		}
+
+		input = strings.TrimSpace(input)
+		if input == "" {
+			continue
+		}
+
+		switch input {
+		case "quit":
+			_, _ = coupler.quit()
+			os.Exit(0)
+		}
+
+		fmt.Print("\033[?25l") // 隐藏光标
+		executing.Store(true)
+		result, err := coupler.exec(input)
+		executing.Store(false)
+
+		if err != nil {
+			fmt.Printf("执行错误: %v\n", err)
+			continue
+		}
+
+		if result.Stdout != "" {
+			fmt.Print(result.Stdout)
+		}
+		if result.Stderr != "" {
+			fmt.Fprintln(os.Stderr, result.Stderr)
+		}
+		if result.ExitCode == 124 {
+			fmt.Println("command timed out")
+		}
+
+		if result.Cwd != "" {
+			coupler.cwd = result.Cwd
+		}
+	}
+}
+
+func help() {
+	h := `
+-h 显示帮助提示
+-v 当前程序版本
+-c 连接目标设备(IMEI), 例如: -c 869523059113051
+`
+
+	fmt.Println(h)
+}
+
+func interruptLoop(executing *atomic.Bool, interrupted *atomic.Bool) {
+	sigCh := make(chan os.Signal, 1)
+	signal.Notify(sigCh, syscall.SIGINT)
+	go func() {
+		for range sigCh {
+			interrupted.Store(true)
+			if executing.Load() {
+				_, _ = coupler.stop()
+			}
+		}
+	}()
+}
+
+func heartbeatLoop(pingState *atomic.Bool) {
+	go func() {
+		ticker := time.NewTicker(1 * time.Second)
+		defer ticker.Stop()
+
+		pingFailCount := 0
+		pong := ""
+		for range ticker.C {
+			resp, err := coupler.ping()
+			if err == nil && resp.Error == nil && resp.Result != nil &&
+				json.Unmarshal(resp.Result, &pong) == nil && pong == "pong" {
+				pingState.Store(true)
+				pingFailCount = 0
+			} else {
+				pingFailCount++
+				if pingFailCount >= 3 { ///// 连续3次ping失败, 可以认为设备离线
+					pingState.Store(false)
+				}
+			}
+		} // end for
+	}()
+}
+
+func printWelcome() {
+	welcome := `
+ _   _ _ _       _     _ _ _     
+| \ | (_) |     | |   (_) | |    
+|  \| |_| |_ ___| |__  _| | | ___
+| .   | | __/ __| '_ \| | | |/ _ \
+| |\  | | || (__| | | | | | |  __/
+|_| \_|_|\__\___|_| |_|_|_|_|\___|
+
+══════════════════════════════════
+       云飞科技RTU远程运维终端
+══════════════════════════════════
+提示: 输入'quit'命令, 退出终端
+`
+	fmt.Println(welcome)
+}