如何基于Go语言SCP协议实现远程文件拷贝并获取[]byte数据
SCP下载功能修正方案
核心逻辑说明
SCP下载采用的是源端发送-客户端确认的交互逻辑,和上传逻辑完全相反:
- 远程服务器执行
scp -f <目标文件路径>进入文件发送模式 - 客户端需要分三次发送
\x00作为确认信号,分别对应:允许发送元数据、允许发送文件内容、接收完成确认 - 远程返回数据分为三段:文件头信息、二进制文件内容、传输结束标记
现有代码问题
- 协程内的
C0660开头的逻辑属于SCP上传场景的文件头发送逻辑,完全不匹配下载需求 - 同时调用
StdinPipe和CombinedOutput会产生流冲突,CombinedOutput会自动接管session的输入输出,手动创建的StdinPipe不会生效 - 重复创建Session属于冗余逻辑,单次会话即可完成整个下载流程
修正后可运行代码
import ( "fmt" "io" "strconv" "strings" "golang.org/x/crypto/ssh" ) func download(con *ssh.Client, path string) ([]byte, error) { session, err := con.NewSession() if err != nil { return nil, err } defer session.Close() stdout, err := session.StdoutPipe() if err != nil { return nil, err } stdin, err := session.StdinPipe() if err != nil { return nil, err } defer stdin.Close() // 启动远程SCP源模式,准备发送指定路径的文件 err = session.Start("sudo scp -f " + path) if err != nil { return nil, err } ackBuf := []byte{0} // 第一次确认:告知远程可以发送文件元数据 if _, err := stdin.Write(ackBuf); err != nil { return nil, err } // 读取文件头 格式为 C<权限> <文件大小> <文件名>\n headerBuf := make([]byte, 1024) headerLen, err := stdout.Read(headerBuf) if err != nil { return nil, err } headerStr := string(headerBuf[:headerLen]) if !strings.HasPrefix(headerStr, "C") { return nil, fmt.Errorf("非法SCP响应: %s", headerStr) } // 解析文件大小 headerParts := strings.Fields(headerStr) if len(headerParts) < 3 { return nil, fmt.Errorf("非法SCP头格式") } fileSize, err := strconv.Atoi(headerParts[1]) if err != nil { return nil, err } // 第二次确认:告知远程可以发送文件内容 if _, err := stdin.Write(ackBuf); err != nil { return nil, err } // 读取完整文件内容 fileContent := make([]byte, fileSize) _, err = io.ReadFull(stdout, fileContent) if err != nil { return nil, err } // 读取传输结束标记 _, err = stdout.Read(ackBuf) if err != nil { return nil, err } // 第三次确认:告知远程接收完成 if _, err := stdin.Write(ackBuf); err != nil { return nil, err } // 等待会话正常退出 if err := session.Wait(); err != nil { return nil, err } return fileContent, nil }
注意事项
- 上述代码仅适用于单文件下载场景,若需要下载目录需要额外处理
D开头的目录头标记 - 若
sudo需要验证密码,可在第一次发送确认信号前,往stdin写入密码加\n即可 - 如果需要记录日志,可以在相关节点把读写的内容写入你传入的
bytes.Buffer即可
内容的提问来源于stack exchange,提问作者OmerMichleviz
相关产品推荐
相关产品推荐

