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

如何基于Go语言SCP协议实现远程文件拷贝并获取[]byte数据

SCP下载功能修正方案

核心逻辑说明

SCP下载采用的是源端发送-客户端确认的交互逻辑,和上传逻辑完全相反:

  1. 远程服务器执行scp -f <目标文件路径>进入文件发送模式
  2. 客户端需要分三次发送\x00作为确认信号,分别对应:允许发送元数据、允许发送文件内容、接收完成确认
  3. 远程返回数据分为三段:文件头信息、二进制文件内容、传输结束标记

现有代码问题

  • 协程内的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 01:54:02