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

使用C++ libcurl实现FTPS上传完成后卡顿(错误425)

FTPS上传文件字节发送完成但触发425带宽超时错误(libcurl/C++)

我在C++中使用libcurl通过FTPS上传文件,所有字节已成功发送,但连接出现卡顿,服务器返回如下错误:

> 425 Data channel timed out due to not meeting the minimum bandwidth requirement.
> Recv failure: Connection reset by peer
Logs:
> Connected 2nd connection to 50.226.39.172 port 50096
> 125 Data connection already open; Transfer starting.
> [UPLOAD CALLBACK] sent 7992 bytes, pos=753464/753464
> [UPLOAD CALLBACK] Upload complete: all 753464 bytes sent.
> Recv failure: Connection reset by peer
> 425 Data channel timed out due to not meeting the minimum bandwidth requirement.
> server did not report OK, got 425

已尝试的操作:

  • 使用FileZilla测试,上传正常
  • 调整CURLOPT_BUFFERSIZE、CURLOPT_FTP_USE_EPSV等libcurl选项,限制块大小为8KB
  • 通过CURLOPT_POSTQUOTE添加QUIT命令,无效果

上传代码如下:

struct UploadBuffer {
  const char *data;
  size_t len;
  size_t pos;
  bool complete;
};

size_t upload_read_callback(void *ptr, size_t size, size_t nmemb, void *stream) {
  UploadBuffer *buf = static_cast<UploadBuffer *>(stream);
  size_t max_read = size * nmemb;
  size_t remaining = buf->len - buf->pos;
  size_t chunk_limit = 8192;
  if (max_read > chunk_limit) max_read = chunk_limit;
  size_t to_read = (remaining < max_read) ? remaining : max_read;

  if (to_read > 0) {
    std::memcpy(ptr, buf->data + buf->pos, to_read);
    buf->pos += to_read;
    std::cerr << "[UPLOAD CALLBACK] sent " << to_read << " bytes, pos=" << buf->pos << "/" << buf->len << std::endl;
    if (buf->pos == buf->len) {
      buf->complete = true;
      std::cerr << "[UPLOAD CALLBACK] Upload complete: all " << buf->len << " bytes sent." << std::endl;
    }
  } else {
    buf->complete = true;
  }
  return to_read;
}

bool upload_bytes(const char *ftp_url, const char *username, const char *password, const char *data, size_t length) {
  CURL *curl = curl_easy_init();
  if (!curl) return false;

  UploadBuffer upload_ctx = {data, length, 0, false};
  curl_easy_setopt(curl, CURLOPT_URL, ftp_url);
  curl_easy_setopt(curl, CURLOPT_USERNAME, username);
  curl_easy_setopt(curl, CURLOPT_PASSWORD, password);
  curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
  curl_easy_setopt(curl, CURLOPT_READFUNCTION, upload_read_callback);
  curl_easy_setopt(curl, CURLOPT_READDATA, &upload_ctx);
  curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)length);
  curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
  curl_easy_setopt(curl, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_TLS);
  curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
  curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
  curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 8192L);
  curl_easy_setopt(curl, CURLOPT_FTP_USE_EPSV, 0L);

  CURLcode res = curl_easy_perform(curl);
  curl_easy_cleanup(curl);
  return res == CURLE_OK && upload_ctx.complete;
}

解决方案

  1. 移除自定义块大小限制
    手动限制8KB块大小会降低传输节奏,触发服务器最小带宽检测。libcurl默认的缓冲区处理更高效,删除回调中的chunk_limit相关逻辑:
size_t upload_read_callback(void *ptr, size_t size, size_t nmemb, void *stream) {
  UploadBuffer *buf = static_cast<UploadBuffer *>(stream);
  size_t max_read = size * nmemb;
  size_t remaining = buf->len - buf->pos;
  size_t to_read = (remaining < max_read) ? remaining : max_read;

  if (to_read > 0) {
    std::memcpy(ptr, buf->data + buf->pos, to_read);
    buf->pos += to_read;
    std::cerr << "[UPLOAD CALLBACK] sent " << to_read << " bytes, pos=" << buf->pos << "/" << buf->len << std::endl;
    if (buf->pos == buf->len) {
      buf->complete = true;
      std::cerr << "[UPLOAD CALLBACK] Upload complete: all " << buf->len << " bytes sent." << std::endl;
    }
  } else {
    buf->complete = true;
  }
  return to_read;
}
  1. 调整连接与超时参数
    恢复EPSV被动模式(多数FTPS服务器推荐),并延长响应超时、设置合理的低速度阈值:
// 在upload_bytes函数中添加以下选项
curl_easy_setopt(curl, CURLOPT_FTP_USE_EPSV, 1L);
curl_easy_setopt(curl, CURLOPT_FTP_RESPONSE_TIMEOUT, 30L); // 延长响应超时至30秒
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 100L); // 设置最低速度阈值(字节/秒)
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 60L); // 允许低于阈值的最长时间(秒)
  1. 对比FileZilla参数对齐配置
    开启FileZilla的调试日志,查看它的连接细节(如TLS会话复用、是否启用压缩、数据端口范围等),将libcurl的参数调整为与FileZilla一致,确保协议行为匹配。

  2. 避免传输完成后的阻塞操作
    回调中最后的日志输出可能延迟libcurl读取服务器响应的时机,可简化日志或异步处理日志输出,确保传输完成后libcurl能立刻处理服务器的226完成响应。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 18:54:51