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

其他

参考

相关推荐
yangpipi-几秒前
数据结构(C语言版)-2.栈和队列
c语言·开发语言·数据结构
还在学习进步3 分钟前
C语言第九周课——经典算法
c语言·开发语言·算法
阿七想学习5 分钟前
数据结构《链表》
java·开发语言·数据结构·学习·链表
极客代码7 分钟前
【Python TensorFlow】进阶指南(续篇二)
开发语言·人工智能·python·深度学习·tensorflow
计算机学姐21 分钟前
基于Python的高校成绩分析管理系统
开发语言·vue.js·后端·python·mysql·pycharm·django
VertexGeek22 分钟前
Rust学习(三):rust基础Ⅱ
开发语言·学习·rust
迈威通信23 分钟前
迈威通信助力海上探测,守护蓝色疆域
网络·自动化·能源
一个数据小开发28 分钟前
业务开发问题之ConcurrentHashMap
java·开发语言·高并发·map
三小尛1 小时前
插入排序(C语言)
c语言·开发语言
NK.MainJay1 小时前
Go语言 HTTP 服务模糊测试教程
python·http·golang