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

NMSSH上传文件出现16384字节倍数截断问题求助

SFTP上传文件偶尔被截断为16384字节倍数的问题排查与解决

我使用NMSSH实现服务器文件上传,代码如下:

let localURL = ...
let sshSession = NMSSHSession.connect(toHost: "MYHOST", withUsername: "MYUSERNAME")
if sshSession.isConnected {
    sshSession.authenticate(byPassword: "MYPASSWORD")
    if sshSession.isAuthorized {
        let sftpSession = NMSFTP(session: sshSession)
        sftpSession.connect()
        if sftpSession.isConnected {
            let data = try? Data(contentsOf: localURL)
            sftpSession.writeContents(data, toFileAtPath: "./uploads/MYFILE")

// 能否刷新缓冲区?

let command = script.sh // 处理上传的数据并生成待下载文件
let stderr: NSErrorPointer = nil
sshSession.channel.execute(command, error: stderr)
...
let jsonData = sftpSession.contents(atPath: path)
...

            sftpSession.disconnect()
        } else {
            // sftpSession未连接
        }
    } else {
        // sshSession未授权
    }
    sshSession.disconnect()
} else {
    // sshSession未连接
}

应用上线数月后,发现约1%的上传文件被截断为16384字节的倍数(如16384、32768等)。该问题无法稳定复现:多次上传同一30000字节左右的测试文件均正常,但偶尔服务器上的文件仅保留前16384字节。我猜测可能是sftpSession断开前有缓冲数据未完全刷新,但不确定解决办法,求助问题原因。


更新(Kenster提醒后)

我使用标准Swift代码从Web服务获取数据再上传,代码如下:

let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url: remoteURL)
let _ = session.downloadTask(with: request) { (data, response, error) in
    if let data = data, error == nil {
        do {
            try? FileManager.default.removeItem(at: localURL)
            try FileManager.default.copyItem(at: data, to: localURL)
            ...

未发现这段代码存在问题,且用户数据来自不同Web服务,因此Web服务并非问题根源。


更新2

我注意到默认的sshSession.channel.bufferSize和sftpSession.bufferSize均为16384,与截断文件的大小或其倍数一致。

补充上传后的操作流程:上传文件后通过script.sh处理,再下载处理生成的.json文件。现提出两个疑问:

  1. 在同一sshSession内完成上传、处理、下载的流程是否允许?
  2. 调用sftpSession.writeContents()上传后,是否可以刷新缓冲区?

问题原因与解决方案

核心原因

缓冲区未完全刷新是问题的关键:NMSSH的sftpSession.writeContents()依赖SFTP协议的批量写入机制,默认使用16384字节的缓冲区。如果写入完成后立刻执行处理脚本或断开连接,缓冲区中剩余的未发送数据可能还未推送到服务器,导致文件被截断。同时,直接执行处理脚本会读取未完整落地的文件,进一步引发后续流程错误。

解决方案

  1. 强制刷新缓冲区:
    • 若writeContents未自动关闭文件句柄,调用sftpSession.closeFile(atPath: "./uploads/MYFILE"),关闭文件会触发缓冲区数据全部写入服务器。
    • 直接调用底层libssh2的sftp_flush函数(需导入libssh2桥接文件):
      if let sftpPtr = sftpSession.sftpSession {
          libssh2_sftp_flush(sftpPtr)
      }
      
  2. 校验文件完整性:执行处理脚本前,通过SFTP获取服务器上文件的大小,与本地文件大小对比,确认一致后再执行脚本。
  3. 调整缓冲区大小(可选):增大sftpSession.bufferSize可降低截断概率,但无法从根本上解决问题,核心仍需确保数据完全写入。

疑问解答

  1. 同一sshSession内完成上传、处理、下载的流程完全允许:SSH协议支持多路复用,多个通道(SFTP通道、命令执行通道)可在同一个会话上串行或并行工作,无技术限制。
  2. 调用writeContents()后可以刷新缓冲区:通过关闭文件句柄或调用底层sftp_flush函数,均可强制推送缓冲数据到服务器。

内容的提问来源于stack exchange,提问作者geohei

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 02:53:14