使用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; }
解决方案
- 移除自定义块大小限制
手动限制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; }
- 调整连接与超时参数
恢复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); // 允许低于阈值的最长时间(秒)
对比FileZilla参数对齐配置
开启FileZilla的调试日志,查看它的连接细节(如TLS会话复用、是否启用压缩、数据端口范围等),将libcurl的参数调整为与FileZilla一致,确保协议行为匹配。避免传输完成后的阻塞操作
回调中最后的日志输出可能延迟libcurl读取服务器响应的时机,可简化日志或异步处理日志输出,确保传输完成后libcurl能立刻处理服务器的226完成响应。
内容的提问来源于stack exchange,提问作者Vlad Didyk
相关产品推荐
相关产品推荐

