我正在编写一个Go库来代表各种网络设备,例如交换机,路由器,无线控制器,接入点等,以便自动配置这些设备。到目前为止,我有一个Host
结构,它有一个公共Device
字段和各种私有字段,用于处理特定于SSH的操作,以及连接到设备的方法,发送一组配置命令它,并检索命令的输出。截至目前,这些方法都没有实现并发,主要是因为我不知道哪些方法(如果有的话)会从并发中受益。
我的问题整体而言,通过SSH配置设备列表似乎是使用并发的一个很好的例子(不是盲目地尝试使用并发来快速"),因为配置一个过程单个设备可能很昂贵,但我不确定在我的应用程序中实现并发性的位置以及如何同步所有内容(线程安全性?)。使用互斥体,等待组,通道和goroutine,像我这样的初学者知道从哪里开始有点令人困惑。我想至少让一个方法同时工作,以便更好地理解Go中的(惯用)并发性。
这是我的package device
import (
"golang.org/x/crypto/ssh"
"io"
)
// A Device represents a network device, such as a switch, router, controller, etc.
type Device struct {
Host string // Hostname or IP address
client *ssh.Client // the SSH client connection
session *ssh.Session // the connection to the remote shell
stdin io.WriteCloser // a pipe connected to the remote shell's standard input
stdout io.Reader // a pipe connected to the remote shell's standard output
stderr io.Reader // a pipe connected to the remote shell's standard error
}
// NewDevice constructs a new device with the given hostname or IP address.
func NewDevice(host string) *Device {
return &Device{Host: host}
}
// Connect starts a client connection to the device, starts a remote
// shell, and creates pipes connected to the remote shell's standard input,
// standard output, and standard error.
func (d *Device) Connect(config *ssh.ClientConfig) error {
// TODO: connect to client, start session, setup IO
// Use a goroutine to handle each step? One goroutine for all steps?
return nil
}
// setupIO connects pipes to the remote shell's standard input, output and error.
func (d *Device) setupIO() error {
sshIn, err := d.session.StdinPipe()
if err != nil {
return err
}
d.stdin = sshIn
sshOut, err := d.session.StdoutPipe()
if err != nil {
return err
}
d.stdout = sshOut
sshErr, err := d.session.StderrPipe()
if err != nil {
return err
}
d.stderr = sshErr
return nil
}
// SendConfigSet writes a set of configuration commands to the remote shell's
// standard input then waits for the remote commands to exit.
func (d *Device) SendConfigSet(cmds []string) error {
// TODO: send a set of configuration commands
// Make concurrent? Commands need to be sent in a specific order.
//
// This function will have different setup and cleanup commands
// that will need to be sent depending on a Device's vendor.
// For example, a Cisco device and an HPE device have
// different sets of setup commands needed before sending
// the `cmds` passed to this function, and have different sets of
// cleanup commands that must be sent before exiting.
return nil
}
// sendCmd writes a remote command to the remote shell's standard input
func (d *Device) sendCmd(cmd string) error {
if _, err := d.stdin.Write([]byte(cmd + "\n")); err != nil {
return err
}
return nil
}
// Output reads the remote shell's standard output line by line into a
// slice of strings.
func (d *Device) Output() ([]string, error) {
// TODO: read contents of session standard output
// Concurrently read from stdout and send to channel?
// If so, use a local channel or add an output channel to `Device`?
return nil, nil
}
// Output reads the remote shell's standard error line by line into a
// slice of strings.
func (d *Device) Err() ([]string, error) {
// TODO: read contents of session standard error
// Concurrently read from stderr and send to channel?
// If so, use a local channel or add an error channel to `Device`?
return nil, nil
}
func (d *Device) Close() error {
if err := d.stdin.Close(); err != nil {
return err
}
if err := d.session.Close(); err != nil {
return err
}
if err := d.client.Close(); err != nil {
return err
}
return nil
}
结构及其方法。为了清楚了解我想要完成的内容以及我对实现细节的想法,我们给予了大量评论。
device
以下是我的package main
import (
"fmt"
"github.com/mwalto7/concurrency/device"
"golang.org/x/crypto/ssh"
"strings"
"time"
)
func main() {
var hosts, cmds []string
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{ssh.Password("password")},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: time.Second * 5,
}
outputs := make(chan string)
for _, host := range hosts {
go configure(host, cmds, config, outputs)
}
for i := 0; i < len(hosts); i++ {
res := <-outputs
fmt.Println(res)
}
}
func configure(host string, cmds []string, config *ssh.ClientConfig, outputs <-chan string) {
// omitted error handling for brevity
netDev := device.NewDevice(host)
defer netDev.Close()
netDev.Connect(config)
netDev.SendConfigSet(cmds)
out, _ := netDev.Output()
outputs <- strings.Join(out, "\n")
}
包的示例用法:
require([
"dojo/parser",
"dojo/ready",
"dijit/layout/BorderContainer",
"dijit/layout/ContentPane",
"dijit/layout/TabContainer",
"dojo/dom",
"esri/map",
"esri/urlUtils",
"esri/arcgis/utils",
"esri/dijit/Legend",
"esri/dijit/Scalebar",
"dijit/registry",
"dojo/domReady!"
], function (
parser,
ready,
BorderContainer,
TabContainer,
ContentPane,
dom,
Map,
urlUtils,
arcgisUtils,
Legend,
Scalebar,
registry
) {
parser.parse()
ready(init)
const maps = {
'Chicago Youth': chikagoYouth,
'USA Median': usaMedian,
'Topo': topo
}
let legend
let scalebar
function createMap(response) {
//document.getElementById('map').innerHTML = ``;
if (legend) {
legend.destroy()
}
if (scalebar) {
scalebar.destroy()
}
dom.byId("title").innerHTML = response.itemInfo.item.title;
//dom.byId("subtitle").innerHTML = response.itemInfo.item.snippet; not found it
var map = response.map;
scalebar = new Scalebar({
map: map,
scalebarUnit: "english"
});
var legendLayers = arcgisUtils.getLegendLayers(response);
legend = new Legend({
map: map,
layerInfos: legendLayers
}, "legend");
legend.startup();
}
function chikagoYouth () {
console.log('Chicago Youth')
arcgisUtils.createMap("c63cdcbbba034b62a2f3becac021b0a8", "map").then(createMap);
}
function usaMedian () {
console.log('USA Median')
arcgisUtils.createMap("1e79439598494713b553f990a4040886", "map").then(createMap);
}
function topo () {
console.log('Topo')
// require(["esri/map", "dojo/domReady!"], function(topMap) {
// let map = new topMap("map", {
// basemap: "topo",
// center: [-95.71, 37.09],
// zoom: 5
// });
// });
}
function init() {
const mapSelect = registry.byId("maplink")
mapSelect.on('change', loadMap)
}
function loadMap(value) {
maps[value]()
}
});
</script>
我不是要求有人为我编写这段代码。如果你有一个代码示例,那很好,但我只是试图组织实现并发并一般地学习并发性。
答案 0 :(得分:0)
如果我正在编写这个程序,我首先要以序列化的方式编写程序。一旦你开始工作,就要考虑如何同时完成任务,以便更有效地利用资源。
通过查看代码,考虑不共享&amp; ssh.ClientConfig对象,为正在运行的每个goroutine创建一个新代码。
此外,您不需要使用通道来打印每个goroutine的输出(除非您想以某种方式序列化输出)。在goroutine中打印它并简化configure()
函数的签名。
您需要使用sync.WaitGroup等待所有配置完成,以防止程序在所有goroutine启动后退出。
答案 1 :(得分:0)
我编写了一个高度并发的应用程序,同时与多个设备通信。我的设备主要是串口(请求,响应,请求等),所以使用单个设备的并发性不是一种选择。如果那就是你想要的,那么这就适合你。
我认为您的设备无法同时处理多个请求。如果可以,您将需要答案,清楚地表明他们属于哪个请求。如果情况属实,请告诉我 - 我也有这方面的经验。
关于串口设备的最难的部分是请求与答案的同步。这里有一些示例代码我是如何解决这个问题的:playground, not runnable
免责声明:快速将其复制在一起。希望你能在混乱中看到这个想法。
上面的示例代码使用单个例程来同步对设备的调用,而每个调用在完成之前都等待答案。每次使用连接时都可以使用互斥锁来实现相同的功能。同时我更喜欢使用互斥锁来实现这一点,因为它可以节省goroutine的启动和停止 - 如果没有正确完成,可能会造成goroutine泄漏。
BTW:ConnAdapter结构是完全并发安全的,可以同时从多个例程中使用。
从您的代码看起来您启动应用程序,获取主机列表(从某个地方),获取cmd列表(可能是某个地方的每个主机),然后想要在其主机上执行所有命令并等到所有内容完了。
如果是这种情况,那么你在这里完全处理并发问题。
我要添加的一件事是超时:playground