Golang编写的SSH客户端执行部分命令时挂起如何解决?
问题描述
我用Golang编写的SSH客户端远程连接虚拟路由器执行命令时,部分命令可正常运行,但执行另一些命令会出现客户端挂起、路由器会话未关闭且Host字段不显示地址的情况。使用Putty或Windows自带SSH客户端则无此问题,请问该如何解决?
客户端代码
package main import ( "fmt" "log" "os" "strings" "golang.org/x/crypto/ssh" ) func main() { if len(os.Args) < 4 { log.Fatalf("Usage: %s <user> <host:port> <command>", os.Args[0]) } client, session, err := connectToHost(os.Args[1], os.Args[2]) if err != nil { panic(err) } command := strings.Join(os.Args[3:], " ") log.Printf("Exec command '%s'", command) out, err := session.CombinedOutput(command) if err != nil { panic(err) } fmt.Println(string(out)) client.Close() } func connectToHost(user, host string) (*ssh.Client, *ssh.Session, error) { var pass string fmt.Print("Password: ") fmt.Scanf("%s\n", &pass) sshConfig := &ssh.ClientConfig{ User: user, Auth: []ssh.AuthMethod{ssh.Password(pass)}, } sshConfig.HostKeyCallback = ssh.InsecureIgnoreHostKey() client, err := ssh.Dial("tcp", host, sshConfig) if err != nil { return nil, nil, err } session, err := client.NewSession() if err != nil { client.Close() return nil, nil, err } return client, session, nil }
解决方向
1. 分配PTY终端
部分路由器命令依赖交互式终端(PTY)才能正常执行,默认的CombinedOutput不会分配PTY,可在创建Session后添加PTY请求:
session, err := client.NewSession() if err != nil { client.Close() return nil, nil, err } // 新增PTY分配逻辑 modes := ssh.TerminalModes{ ssh.ECHO: 0, // 关闭回显 ssh.TTY_OP_ISPEED: 14400, // 输入速率 ssh.TTY_OP_OSPEED: 14400, // 输出速率 } if err := session.RequestPty("xterm", 80, 40, modes); err != nil { session.Close() client.Close() return nil, nil, fmt.Errorf("请求PTY失败: %v", err) }
2. 替换CombinedOutput,手动处理流并设置超时
CombinedOutput会等待命令完全结束才返回,若命令进入交互式状态或未正确关闭输出流,就会导致挂起。可手动处理输出流并添加超时控制:
// 替换原CombinedOutput的代码段 import ( "context" "io" "time" ) // ... stdout, err := session.StdoutPipe() if err != nil { panic(err) } stderr, err := session.StderrPipe() if err != nil { panic(err) } if err := session.Start(command); err != nil { panic(err) } // 设置10秒超时 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() var output strings.Builder var errOutput strings.Builder // 异步读取输出 go func() { io.Copy(&output, stdout) }() go func() { io.Copy(&errOutput, stderr) }() // 等待命令结束或超时 select { case <-ctx.Done(): session.Signal(ssh.SIGINT) session.Wait() fmt.Println("命令执行超时") fmt.Println("标准输出:", output.String()) fmt.Println("错误输出:", errOutput.String()) case <-func() chan struct{} { ch := make(chan struct{}) go func() { session.Wait() close(ch) }() return ch }(): fmt.Println("标准输出:", output.String()) if errOutput.Len() > 0 { fmt.Println("错误输出:", errOutput.String()) } }
3. 显式关闭Session
当前代码仅关闭Client,未显式关闭Session,可能导致路由器会话残留。建议在获取Session后立即注册关闭操作:
client, session, err := connectToHost(os.Args[1], os.Args[2]) if err != nil { panic(err) } defer func() { session.Close() client.Close() }()
4. 指定命令执行的Shell环境
部分命令需要特定Shell环境才能运行,可尝试在命令前指定路由器支持的Shell(如sh -c):
command := fmt.Sprintf("sh -c '%s'", strings.Join(os.Args[3:], " "))
内容的提问来源于stack exchange,提问作者vostbur
相关产品推荐
相关产品推荐

