初始化/管理并发SSH连接

时间:2018-01-26 04:37:45

标签: go struct ssh connection

我已经搜索过高低,我不确定我是否使用了错误的关键字,但我无法理解这一点。我正在构建一个应用程序,它接收主机名列表并通过SSH连接到这些主机名。它旨在维护这些连接(并在断开连接时重新连接)。我的程序会定期对一些/所有这些主机执行指令和执行命令。

我现在的问题是,知道你不能初始化变量而不使用它,我必须为这些SSH连接动态创建变量,这样我就可以独立监控/管理它们(读/写,必要时重新连接等) 。由于我对go的了解有限,并且意外地使事情过于复杂,我到目前为止所提出的最好的方法是使用结构并附加每个连接及其参数(主机名,用户名,密码,SSH配置详细信息) ,日志文件位置等。)

目前我的狡猾代码看起来像这样:

package main

import (
    "os"
    "fmt"
    "flag"
    "golang.org/x/crypto/ssh"
    "io"
    "log"
    "strings"
    "time"
    "encoding/xml"
    "bufio"
    "net"
)

// SSH connection struct
type SSHConnections struct {
    Host string
    User string
    Password string
    CLILogfile string
    SSHConn ssh.Client
    SSHConfig ssh.ClientConfig
}

func main() {
    //parse list of addresses
    var ipaddr = []string{"a.b.c.d","e.f.g.h"} //hard-coded for now

    //build out SSHConnections struct

    for i := 0; i < len(ipaddr); i++ {
        tempsshConfig := &ssh.ClientConfig{
            User:   "administrator",
            Auth: []ssh.AuthMethod{
                ssh.Password("Password!"),
            },
            HostKeyCallback: ssh.InsecureIgnoreHostKey(), 
        }
        tempsshConfig.Config.Ciphers = append(tempsshConfig.Config.Ciphers, "aes128-cbc")
        var newitem = SSHConnections{
            Host: ipaddr[i],
            User: "administrator",
            Password: "Password!",
            CLILogfile: ipaddr[1],
            SSHConn: ssh.Client,
            SSHConfig: *tempsshConfig,
        }

        SSHConnections = append(SSHConnections, newitem)
    }

使用上面的代码我得到编译错误:

type ssh.Client is not an expression
type SSHConnections is not an expression

此后还有我需要管理的特定于连接的参数(日志文件声明,其他SSH参数,实际的SSH连接过程),可能与上面的结构相同。由于以上是我目前的绊脚石,我对如何解决问题毫无头绪,更不用说将以下单连接代码整合到上面了。

//extra SSH parameters required before connecting
sshConfig.Config.Ciphers = append(sshConfig.Config.Ciphers, "aes128-cbc")
modes := ssh.TerminalModes{
    ssh.ECHO:          0,     // disable echoing
    ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
    ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}

//prepare logfiles
f, ferr := os.OpenFile("outputfile.txt", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if ferr != nil {
    panic(ferr)
}
defer f.Close() 

//SSH connection procedure
connection, err := ssh.Dial("tcp", hostname+":22", sshConfig)
if err != nil {
    log.Fatalf("Failed to dial: %s", err)
}
session, err := connection.NewSession()
handleError(err, true, "Failed to create session: %s")
sshOut, err := session.StdoutPipe()
handleError(err, true, "Unable to setup stdin for session: %v")
sshIn, err := session.StdinPipe()
handleError(err, true, "Unable to setup stdout for session: %v")
if err := session.RequestPty("xterm", 0, 200, modes); err != nil {
    session.Close()
    handleError(err, true, "request for pseudo terminal failed: %s")
}
if err := session.Shell(); err != nil {
    session.Close()
    handleError(err, true, "request for shell failed: %s")
}

我已经删除了大量不相关的代码,以使我的帖子更清晰。我希望我能在这方面取得成功。

我甚至不确定结构是否是我想要的,但接收可变数量的主机名的动力以及单独控制它们的要求是关键。之后,我可能还需要单独拆卸连接,因此可以操作它们的结构是我正在寻找的。

非常感谢任何指导我正确方向的帮助。谢谢!

-------------在此行下方更新---------------

在下面的帖子和一些谷歌搜索后,我的代码现在看起来像这样(删除了导入):

var connections []SSHConnections

// SSH connection struct
type SSHConnections struct {
    Host string
    User string
    Password string
    CLILogfilepath string
    CLILogfile *os.File
    SSHConn *ssh.Client
    Session *ssh.Session
    SSHOut  io.Reader
    SSHIn   io.WriteCloser
    SSHConfig *ssh.ClientConfig
    CLIReady string
    SSHConnErr error
    SessionErr  error
    SSHOutErr   error
    SSHInErr    error
    CLILogfileErr   error
    CommandQueue    chan string
}

func handleError(e error, fatal bool, customMessage ...string) {
    var errorMessage string
    if e != nil {
        if len(customMessage) > 0 {
            errorMessage = strings.Join(customMessage, " ")
        } else {
            errorMessage = "%s"
        }
        if fatal == true {
            log.Fatalf(errorMessage, e)
        } else {
            log.Print(errorMessage, e)
        }
    }
}
func writeToFile(f *os.File, err error, inputString string) {
    if _, err = f.WriteString(inputString); err != nil {
        panic(err)
    }   
}

func connectionWorker(i int) {
    //this function is to monitor SSH connections and reconnect if/when they are terminated
    fmt.Println("goroutine for: "+connections[i].Host)
    for { //not sure if this is even necessary? yet to test
        select {
            case command := <-connections[i].CommandQueue:
                fmt.Printf("received %v from queue\n", command)
            default:
                time.Sleep(1000 * time.Millisecond)
        }

    }
}

func main() {

    //parse list of addresses
    var ipaddr = []string{"host1","host2"}

    //build out SSHConnections struct
    for i := 0; i < len(ipaddr); i++ {
        tempsshConfig := &ssh.ClientConfig{
            User:   "administrator",
            Auth: []ssh.AuthMethod{
                ssh.Password("Password!"),
            },
            HostKeyCallback: ssh.InsecureIgnoreHostKey(),
        }
        tempsshConfig.Config.Ciphers = append(tempsshConfig.Config.Ciphers, "aes128-cbc")
        var newitem = SSHConnections{
            Host: ipaddr[i],
            User: "administrator",
            Password: "Password!",
            CLILogfilepath: ipaddr[i],
            SSHConfig: tempsshConfig,
        }

        connections = append(connections, newitem)
    }

    modes := ssh.TerminalModes{
        ssh.ECHO:          0,     // disable echoing
        ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
        ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
    }

    //establish connections, check each for relevant 'admin:' readiness
    for i := 0; i < len(connections); i++ {
        connections[i].SSHConn, connections[i].SSHConnErr = ssh.Dial("tcp", connections[i].Host+":22", connections[i].SSHConfig)
        if connections[i].SSHConnErr != nil {
            fmt.Println("Failed to dial: %s")
        } else {
            connections[i].Session, connections[i].SessionErr = connections[i].SSHConn.NewSession()
            handleError(connections[i].SessionErr, true, "Failed to create session: %s")
            connections[i].SSHOut, connections[i].SSHOutErr = connections[i].Session.StdoutPipe()
            handleError(connections[i].SSHOutErr, true, "Unable to setup stdin for session: %v")
            connections[i].SSHIn, connections[i].SSHInErr = connections[i].Session.StdinPipe()
            handleError(connections[i].SSHInErr, true, "Unable to setup stdout for session: %v")
            if err := connections[i].Session.RequestPty("xterm", 0, 200, modes); err != nil {
                connections[i].Session.Close()
                handleError(connections[i].SSHInErr, true, "request for pseudo terminal failed: %s")
            }
        }
        if err := connections[i].Session.Shell(); err != nil {
            connections[i].Session.Close()
            handleError(err, true, "request for shell failed: %s")
        }
        //initialise the buffered CommandQueue channel
        connections[i].CommandQueue = make(chan string, 1000)

        //prepare logfiles
        connections[i].CLILogfile, connections[i].CLILogfileErr = os.OpenFile(connections[i].CLILogfilepath+".txt", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
        if connections[i].CLILogfileErr != nil {
            panic(connections[i].CLILogfileErr)
        }
        defer connections[i].CLILogfile.Close()
        writeToFile(connections[i].CLILogfile, connections[i].CLILogfileErr, "testing output to file\r\n")

        //monitor/maintain connections, monitor work queue and execute
        go connectionWorker(i)
    }
}

以上不是很漂亮而且不完美,如果有人要复制/粘贴,也可能需要进行一些调整,所以请记住这一点。我确定有很多错误可以通过适当的测试偶然发现,但现在它可以按预期运行。我的原始查询现在全部回答了,谢谢!

1 个答案:

答案 0 :(得分:2)

你的循环如下:

    var bikearray = [];
    $('#searchbtn').on('click', function() {
     $.ajax({
        url:'http://metrobikes.in/api/a2b-bike-movement-on-map',
        method:"GET",
        data : {
            start_Date : "2017-12-11",
            end_date : "2018-01-24",
            bike_number : "KA-51-D-6109"
       },
    }).done(function(data){
        bikearray = data.result.data;
        initMap();
     });
});



function initMap() {
    var map = new google.maps.Map(document.getElementById('map'), {
        zoom: 13,
        center: new google.maps.LatLng(12.98966, 77.653637),
        mapTypeId: google.maps.MapTypeId.ROADMAP
    });

    var lineSymbol = {
        path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW
    };
    for(i = 0; i < bikearray.length;  i++){
        var from_lat = parseFloat(bikearray[i].from_lat);
        var from_long = parseFloat(bikearray[i].from_long);
        var to_lat = parseFloat(bikearray[i].to_lat);
        var to_long =parseFloat(bikearray[i].to_long);
        var linecolor = bikearray[i].colour;
        console.log(bikearray[i].from_lat);
        var bikePath = new google.maps.Polyline({
            path:  [

                {lat: from_lat, lng: from_long},
                {lat: to_lat, lng: to_long}
            ],
            icons: [{
                icon: lineSymbol,
                repeat:'35px',
                offset: '100%'
            }],
            geodesic: true,
            strokeColor: linecolor,
            strokeOpacity: 1.0,
            strokeWeight: 2,
            map: map
        });
        bikePath.setMap(map);


    }


}

当您构建for i := 0; i < len(ipaddr); i++ { // ... var newitem = SSHConnections{ // ... SSHConn: ssh.Client, // ... } SSHConnections = append(SSHConnections, newitem) } 时,newitem没有ssh.Client因此您应该将其遗漏(因此使用其零值)而不是试图使用SSHConn类型作为表达式;那是你的第一个错误。

第二个错误类似:您正在使用类型(在这种情况下为ssh.Client),您真正想要使用切片:

SSHConnections

应该是:

SSHConnections = append(SSHConnections, newitem)

那个循环看起来应该更像这样:

someSlice = append(someSlice, newitem)

或者,因为你知道var connections []SSHConnections for i := 0; i < len(ipaddr); i++ { // ... var newitem = SSHConnections{ // Everything that's there now except the SSHConn ... } connections = append(connections, newitem) } 需要多大:

connections

稍后,您拨打connections := make([]SSHConnections, len(ipaddr)) for i := 0; i < len(ipaddr); i++ { // ... var newitem = SSHConnections{ // Everything that's there now except the SSHConn ... } connections[i] = newitem } ssh.Dial,以获取可能最终出现在connection.NewClient中的ssh.Client

此外,您可能最好使用指向connectionsSSHConnections的{​​{1}}指针,因为这是指包的界面想要工作的内容用。