You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 06:32:10