Go SSH连接调用Odroid板Go脚本无响应问题排查求助
问题描述
使用Go的ssh包建立SSH连接,通过stdin/stdout管道与Odroid板交互时,ls、cd等标准命令可正常执行,但调用板上的Go脚本时,服务端代码卡在Stdoutpipe.Read()处无响应。直接SSH登录Odroid板可正常运行该脚本(脚本使用fmt.Println输出内容),尝试过os.Stdout.Write、exec.Command("echo")等方式仍未解决。
服务端核心代码
var serverTerminalInfo ServerTerminalInfo err = json.Unmarshal(body, &serverTerminalInfo) if err != nil { log.Fatal(err) } fmt.Println("ServerTerminal CMD:", serverTerminalInfo.CMD) // Write a command to the remote shell shellCMD := serverTerminalInfo.CMD + "\n" fmt.Println(shellCMD) io.WriteString(s.Stdinpipe, shellCMD) buf := make([]byte, 8192) time.Sleep(2 * time.Second) fmt.Println("Hold up here??") n, err := s.Stdoutpipe.Read(buf) fmt.Println("Sucessfully Read into buffer") if err != nil && err != io.EOF { log.Fatalf("Failed to read command output: %s", err) } output := strings.TrimSpace(string(buf[:n])) fmt.Println("Outputs should be produced?") fmt.Println("Output Message:", output)
板端核心代码
func transmitCommand(command string) { uartPortName := "/dev/ttyS1" uartConfig := &serial.Config{ Name: uartPortName, Baud: 115200, StopBits: 1, Parity: serial.ParityNone, } p, err := serial.OpenPort(uartConfig) if err != nil { log.Fatalf("Failed to open UART port: %v", err) } defer p.Close() data := []byte(command) _, err = p.Write(data) _, err = p.Write([]byte("\r")) if err != nil { log.Fatalf("Failed to write data to UART port: %v", err) } buf := make([]byte, 8192) preVal := 0 currentVal := 1 time.Sleep(1 * time.Second) for preVal < currentVal { preVal = currentVal currentVal = len(buf) time.Sleep(100*time.Millisecond) } _, err = p.Read(buf) if err != nil { log.Fatal(err) } fmt.Printf(string(buf[:len(buf)])+ "\n") } func main() { cmd := exec.Command("echo The go program is working") cmd.Run() if len(os.Args) < 2 { transmitCommand("") }else{ command := strings.Join(os.Args[1:], " ") fmt.Println("echoing the command:", command) transmitCommand(command) } }
排查思路
- 输出缓冲未刷新:远程Go程序在非终端环境下(ssh管道),stdout默认是全缓冲模式,
fmt.Println的输出不会即时发送到管道。解决方法:在板端代码中手动刷新输出,比如在fmt.Printf后调用fmt.Flush();或者将os.Stdout设置为无缓冲:os.Stdout.(*os.File).SetFlags(os.O_SYNC);也可以改用log包输出(默认自动刷新)。 - UART读取逻辑阻塞:板端
transmitCommand函数中的UART读取逻辑存在错误,循环条件preVal < currentVal会直接退出(因为currentVal始终是buf的长度8192),随后调用的p.Read(buf)是阻塞式的,如果UART没有返回数据,板端程序会一直卡在这,导致服务端的Read永远等不到输出。需要修正UART读取逻辑:比如使用带超时的读取,或者通过serial.Port的SetReadDeadline设置超时,避免无限阻塞。 - 服务端读取方式问题:服务端仅调用一次
Stdoutpipe.Read(),无法处理分批次输出的内容,且time.Sleep是不可靠的同步方式。建议改用Session.Run()方法,它会自动执行命令并读取输出直到命令结束,无需手动管理stdin/stdout:output, err := s.Run(serverTerminalInfo.CMD) if err != nil { log.Fatal(err) } fmt.Println(string(output)) - 缺少终端(pty)环境:直接SSH登录时是终端模式,而ssh包的管道默认是非终端模式,部分程序的输出缓冲行为会不同。可以在ssh Session中请求pty模拟终端环境:
modes := ssh.TerminalModes{ ssh.ECHO: 0, } if err := s.RequestPty("xterm", 80, 40, modes); err != nil { log.Fatal(err) } - 板端程序未正常退出:检查板端程序是否因UART读取阻塞而未退出,确保所有代码路径都能走到程序结束,必要时在
main函数末尾添加os.Exit(0)强制退出。
内容的提问来源于stack exchange,提问作者Harmandeep Dubb
相关产品推荐
相关产品推荐

