Golang实现一个批量自动化执行树莓派指令的软件(2)指令

简介

基于上篇 Golang实现一个批量自动化执行树莓派指令的软件(1)文本加密&配置&命令行交互实现, 这篇实现的是指令, 即通过ssh执行linux指令的实现。

环境描述

运行环境: Windows, 基于Golang, 暂时没有使用什么不可跨平台接口, 理论上支持Linux/MacOS
目标终端:树莓派DebianOS(主要做用它测试)

实现

接口定义

go 复制代码
type ICommander interface {
	/*
		Command: 堵塞直到执行完毕
	*/
	Command(cmdStr string) (output string, err error)
	/*
		CommandWithCallback:
			background 为 true 时 异步函数, 执行完毕之后自动调用回调
			background 为 false时 堵塞直到执行完毕之后调用回调
	*/
	CommandWithCallback(cmdStr string, callback func(output string, err error), background bool) error

	/*
		Cancel : 任务取消, 执行到一半的任务断开场景
	*/
	Cancel() error

	/*
		Desroy : 无论如何直接关闭客户端
	*/
	Destroy() error
}

接口实现

go 复制代码
package sshutil

import (
	"fmt"
	"golang.org/x/crypto/ssh"
	"io"
	"time"
)

type Commander struct {
	sshClient *ssh.Client

	started  bool
	canceled chan struct{}
	finished chan struct{}
}

func NewCommander(cfg SSHConfig) (*Commander, error) {
	sshClient, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", cfg.IP, cfg.Port), cfg.sshClientConfig)
	if err != nil {
		return nil, err
	}
	return &Commander{
		sshClient: sshClient,
		canceled:  make(chan struct{}),
		finished:  make(chan struct{}),
	}, nil
}

func (c *Commander) Command(cmd string) (output string, err error) {
	c.doCommand(cmd, func(recvStr string, e error) {
		output = recvStr
		err = e
	})
	return
}

func (c *Commander) CommandWithCallback(cmd string, callback func(output string, e error), background bool) (err error) {
	if background {
		go c.doCommand(cmd, callback)
	} else {
		c.doCommand(cmd, callback)
	}
	return
}

func (c *Commander) doCommand(cmd string, callback func(output string, e error)) {
	var (
		session   *ssh.Session
		recvBytes []byte
		err       error
		canceled  bool
	)

	if session, err = c.sshClient.NewSession(); err != nil {
		callback("", err)
		return
	}
	c.started = true

	go func(session *ssh.Session) {
		select {
		case <-c.finished:
		case <-c.canceled:
			canceled = true
			if e := session.Signal(ssh.SIGINT); nil != err {
				fmt.Println("emit abort fail: ", e.Error())
			}
		}
	}(session)

	defer func() {
		c.started = false
		if !canceled {
			c.finished <- struct{}{}
		}
		if e := session.Close(); nil != e && e != io.EOF {
			fmt.Println("session close fail: ", e.Error())
		}
	}()

	// err = session.Start(command) // ssh库的异步方式
	if recvBytes, err = session.CombinedOutput(cmd); err != nil {
		if canceled {
			err = fmt.Errorf("user canceled")
		}
		callback(string(recvBytes), err)
		return
	}

	callback(string(recvBytes), err)
}

func (c *Commander) Cancel() error {
	if c.started {
		select {
		case c.canceled <- struct{}{}:
		case <-time.After(time.Second): // 取消时间过长,取消失败
			return fmt.Errorf("time out waiting for cancel")
		}
	}
	return nil
}

func (c *Commander) Destroy() error {
	var err = c.Cancel()
	close(c.finished)
	close(c.canceled)
	err = c.sshClient.Close()
	return err
}

测试用例

go 复制代码
package sshutil

import (
	"fmt"
	"golang.org/x/crypto/ssh"
	"sync"
	"testing"
	"time"
)

func newCommander() (*Commander, error) {
	config := &ssh.ClientConfig{
		User: "pi",
		Auth: []ssh.AuthMethod{
			ssh.Password("a123456"),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}
	sshCfg := SSHConfig{
		IP:              "192.168.3.2",
		Port:            22,
		User:            "pi",
		Password:        "a123456",
		sshClientConfig: config,
	}
	commander, err := NewCommander(sshCfg)

	return commander, err
}

func TestEasyCommandAndDestroy(t *testing.T) {
	commander, err := newCommander()
	if nil != err {
		fmt.Println("new command fail: ", err.Error())
		return
	}
	defer func() {
		e := commander.Destroy()
		if nil != err {
			fmt.Println("fail to destroy, ", e.Error())
		}
	}()
	var output string
	output, err = commander.Command("ls /")
	fmt.Println("command result: ", output)
}

func TestCommandWithCallbackWithoutAsync(t *testing.T) {
	commander, err := newCommander()
	if nil != err {
		fmt.Println("new command fail: ", err.Error())
		return
	}
	defer func() {
		e := commander.Destroy()
		if nil != err {
			fmt.Println("fail to destroy, ", e.Error())
		}
	}()

	err = commander.CommandWithCallback("sleep 4; ls /", func(output string, e error) {
		if nil != e {
			fmt.Println("fail, ", e.Error())
		} else {
			fmt.Println("result: ", output)
		}
	}, false)
	if nil != err {
		fmt.Println("do command fail: ", err.Error())
		return
	}
}

func TestCommandWithCallbackAsync(t *testing.T) {
	var waiter sync.WaitGroup
	commander, err := newCommander()
	if nil != err {
		fmt.Println("new command fail: ", err.Error())
		return
	}
	defer func() {
		e := commander.Destroy()
		if nil != err {
			fmt.Println("fail to destroy, ", e.Error())
		}
	}()
	waiter.Add(1)
	err = commander.CommandWithCallback("sleep 4; ls /", func(output string, e error) {
		if nil != e {
			fmt.Println("fail, ", e.Error())
		} else {
			fmt.Println("result: ", output)
		}
		waiter.Done()
	}, true)
	if nil != err {
		fmt.Println("do command fail: ", err.Error())
		return
	}
	fmt.Println("waiting finished<--------")
	waiter.Wait()
	fmt.Println("waiting finished------>")
}

func TestCommandWithCallbackAndCancel(t *testing.T) {
	var waiter sync.WaitGroup
	commander, err := newCommander()
	if nil != err {
		fmt.Println("new command fail: ", err.Error())
		return
	}
	defer func() {
		e := commander.Destroy()
		if nil != err {
			fmt.Println("fail to destroy, ", e.Error())
		}
	}()
	waiter.Add(1)
	err = commander.CommandWithCallback("sleep 10; ls /", func(output string, e error) {
		if nil != e {
			fmt.Println("fail, ", e.Error())
		} else {
			fmt.Println("result: ", output)
		}
		waiter.Done()
	}, true)
	if nil != err {
		fmt.Println("do command fail: ", err.Error())
		return
	}
	fmt.Println("waiting finished<--------")
	time.Sleep(time.Second * 2)
	fmt.Println("canceling...")
	fmt.Println("canceled, ", commander.Cancel())
	waiter.Wait()
	fmt.Println("waiting finished------>")
}

代码源

https://gitee.com/grayhsu/ssh_remote_access

其他

参考

相关推荐
moringlightyn21 分钟前
c++11可变模版参数 emplace接口 新的类功能 lambda 包装器
开发语言·c++·笔记·其他·c++11·lambda·包装器
Laplaces Demon22 分钟前
Spring 源码学习(十四)—— HandlerMethodArgumentResolver
java·开发语言·学习
郝学胜-神的一滴25 分钟前
使用Linux系统函数递归遍历指定目录
linux·运维·服务器·开发语言·c++·软件工程
guygg8826 分钟前
Java 无锁方式实现高性能线程
java·开发语言
资讯全球33 分钟前
2025机器人自动化打磨抛光设备及汽车零件打磨新技术10月应用解析
人工智能·机器人·自动化
青衫码上行1 小时前
【从0开始学习Java | 第22篇】反射
java·开发语言·学习
一念&1 小时前
每日一个C语言知识:C 字符串
c语言·开发语言
0110_10241 小时前
tauri + rust的环境搭建---初始化以及构建
开发语言·后端·rust
会开花的二叉树1 小时前
C++微服务 UserServer 设计与实现
开发语言·c++·微服务
像风一样自由20202 小时前
Rust Tokio vs Go net/http:云原生与嵌入式生态选型指南
开发语言·golang·rust