Golang中SSH连接stdoutPipe读取阻塞问题求助
解决SSH stdoutPipe读取挂起的问题
你的问题很典型——当用SSH交互式shell时,stdout.Read()会因为管道一直处于打开状态而阻塞。我来帮你拆解原因和解决办法:
为什么会挂起?
你调用了sess.Shell()启动了一个交互式SSH shell,执行ls之后,这个shell并没有退出,它会一直等待你输入下一条命令。此时stdout管道是持续打开的,而Read()方法是阻塞式的:只要管道没关闭,它就会一直等着新数据进来,所以你的程序就卡在那里不动了。
另外提一句,你的代码忽略了所有错误处理,这在实际开发中很容易隐藏问题,建议后续加上错误检查。
三种解决方案,按需选择
1. 执行单命令而非交互式shell(最推荐)
如果只是要运行ls这类单次命令,完全不需要启动交互式shell。用sess.Run("命令")代替sess.Shell(),命令执行完毕后会话会自动关闭,stdout.Read()读完数据后会返回EOF(numBytes=0),循环自然退出。
修改后的代码:
package main import ( "fmt" "strconv" "golang.org/x/crypto/ssh" ) func main() { user := "<user>" passwd := "<password>" ip := "<ip>" sshConfig := &ssh.ClientConfig{ User: user, HostKeyCallback: ssh.InsecureIgnoreHostKey(), Auth: []ssh.AuthMethod{ ssh.Password(passwd), }, } // 处理Dial错误 conn, err := ssh.Dial("tcp", ip+":22", sshConfig) if err != nil { fmt.Printf("连接SSH服务器失败: %v\n", err) return } defer conn.Close() // 处理Session创建错误 sess, err := conn.NewSession() if err != nil { fmt.Printf("创建会话失败: %v\n", err) return } defer sess.Close() stdout, err := sess.StdoutPipe() if err != nil { fmt.Printf("获取标准输出管道失败: %v\n", err) return } // 直接运行ls命令,替代交互式shell if err := sess.Run("ls"); err != nil { fmt.Printf("执行命令失败: %v\n", err) return } numBytes := 1 readCount := 0 for numBytes > 0 { fmt.Println("Read count: " + strconv.Itoa(readCount)) recvData := make([]byte, 1024) numBytes, err = stdout.Read(recvData) if err != nil { fmt.Printf("读取数据出错: %v\n", err) break } fmt.Println(string(recvData[:numBytes])) readCount++ } }
2. 在交互式shell中发送退出命令
如果你必须使用交互式shell(比如需要执行多条命令),可以在ls之后发送exit\n让shell主动退出,这样stdout管道会关闭,Read()就会返回EOF。
修改你的stdin写入部分:
stdin.Write([]byte("ls\n")) // 发送exit命令关闭shell stdin.Write([]byte("exit\n")) // 关闭stdin避免后续错误 stdin.Close()
3. 设置读取超时(保持会话活跃时用)
如果需要保持shell会话一直打开,同时不想让Read()无限阻塞,可以给stdout设置读取超时。你可以把stdout类型断言为net.Conn,然后调用SetReadDeadline:
import "net" import "time" // ... 其他代码 ... stdout, _ := sess.StdoutPipe() // 类型断言为net.Conn,设置5秒超时 if connStdout, ok := stdout.(net.Conn); ok { connStdout.SetReadDeadline(time.Now().Add(5 * time.Second)) }
之后Read()在5秒内没读到数据就会返回超时错误,你可以捕获这个错误并决定是退出循环还是重新设置超时继续等待。
内容的提问来源于stack exchange,提问作者jhall1990
相关产品推荐
相关产品推荐

