借助libcurl实现ftp文件上传,断点续传demo梳理。

公司业务,需要实现一个ftp大文件上传的功能,简单搭建一个ftp服务器,首先研究demo,以及断点上传的功能。

1:首先了解文件上传相关协议,ftp,sftp或者基于http,其他自己实现等。

2:确定基于ftp实现,搭建简单的ftp服务器并用工具确定服务器正常。

3:基于现有的服务器,了解相关ftp开源库,使用代码实现文件上传最基本的功能。

4:考虑到大文件的传输,首先考虑断点续传功能,后期用线程池/多线程方案进行适配优化。

5:考虑使用场景,适配在windows上进行测试,基于qt。

1:在linux上安装ftp服务器 vsftpd 了解sftp和ftp的区别。

​ sftp基于ssh进行远程传输,基于ssh通道进行数据传输,默认使用22端口。
​ ftp需要单独配置ftp服务器,比如linux上安装vsftpd,windows上安装FileZilla Server,默认使用20/21端口传输
​ 21端口是控制信息端口,20端口一般数据传输端口,可以协商。
​ 主动方式 ftp客户端开放端口给服务器,连接上21端口后,由服务器主动连接开放端口,可能被防火墙阻塞。
​ 被动方式 服务器开放另外的数据端口给客户端,客户端直接连接进行数据传输。

2:ubuntu环境安装ftp服务器后,测试使用。

​ 这里我ubuntu环境使用vsftpd进行安装,安装后正常启动,需要关注配置文件中,/etc/vsftpd.conf,下面的配置,否则一直上传有报错。
​ local_enable=YES
​ write_enable=YES
​ 这里我使用xftp连接我的ftp服务器,21端口,使用ftp进行上传(sftp基于ssh,原理不一样),这次测试成功,即基于工具测试ftp服务器成功。

#ftp服务器上  服务进程已经正常启动
ubuntu@ubuntu:~/test/test1$ ps afx|grep vsftpd84952 pts/4    S+     0:00  |           \_ grep --color=auto vsftpd83278 ?        Ss     0:00 /usr/sbin/vsftpd /etc/vsftpd.conf

3:分析ftp上传时的端口交互。

​ 要使用代码实现ftp的相关功能,需要 借助相关开源代码 libcurl 或者libftp libssh2 (sftp)
​ 需要根据业务场景,结合上传的文件个数,大小,选择适应的协议进行上传 ftp还是sftp

4:代码实现ftp上传功能测试

因为我比较熟悉linux,首先参考网络,或者libcurl开源库下的example中测试用例,实现最基本的上传文件成功。

这里遇到的问题是,参考网络时,url都是域名,使用自己搭建的ftp时,需要注意url的设置正确,以及这里登陆用户是我自己的账号,根目录也就是/home/用户名,否则会报错。

#include <iostream>
#include <curl/curl.h>int main() {CURL* curl;CURLcode res;// 初始化CURL对象curl_global_init(CURL_GLOBAL_ALL);curl = curl_easy_init();if (curl) {// 设置远程服务器地址、用户名和密码std::string url = "ftp://192.168.189.132/";std::string username = "ubuntu";std::string password = "123456";// 设置本地文件路径std::string localFile = "/home/ubuntu/ftp_test/1.cpp";// 配置CURL选项curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);curl_easy_setopt(curl, CURLOPT_URL, url.c_str());curl_easy_setopt(curl, CURLOPT_USERNAME, username.c_str());curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str());FILE* file = fopen(localFile.c_str(), "rb");if (file) {// 上传文件curl_easy_setopt(curl, CURLOPT_READDATA, file);res = curl_easy_perform(curl);if (res == CURLE_OK) {std::cout << "文件上传成功" << std::endl;} else {std::cout << "文件上传失败: " << curl_easy_strerror(res) << std::endl;}fclose(file);}// 清理CURL资源curl_easy_cleanup(curl);}// 全局清理curl_global_cleanup();
}

5:基于libcurl实现文件上传功能

经过了解,libcurl是最好实现该方案的开源库,暂时确定以该库入手实现功能。

5.1 首先获取libcurl开源库源码,编译完成后,了解相关目录架构,参考example目录下相关demo,了解相关基本接口。
5.2 获取libcurl的windows版本,这里直接从官网获取,基于qt进行测试
5.2.1 首先,获取到release版本后,直接解压在对应目录,需要在qt项目中,链接对应的头文件和lib库,使代码编译能通过。
#在测试的qt项目中 pro文件中增加libcurl库的路径,这里我的路径如下
INCLUDEPATH += E:/curl-8.5.0_3-win32-mingw/include
LIBS += -LE:/curl-8.5.0_3-win32-mingw/lib -lcurl

同时: 代码实现时能正常调用到libcurl的库,但是运行无反应。

需要把libcurl对应的链接库拷贝到qt 项目debug/release运行目录下,取libcurl release版本目录下bin目录下libcurl.dll。

5.2.2 实现简单的界面,测试服务器连接正常,

在这里插入图片描述

服务器连接测试demo:

void MainWindow::on_pb_check_connect_clicked()
{QString ftp_server_addr = ftp_addr + ftp_dir;CURL *curl;CURLcode res;curl_global_init(CURL_GLOBAL_DEFAULT);curl = curl_easy_init();if (curl) {// 设置FTP服务器地址和端口curl_easy_setopt(curl, CURLOPT_URL, ftp_addr.toStdString().c_str());// 设置用户名和密码curl_easy_setopt(curl, CURLOPT_USERNAME, ftp_username.toStdString().c_str());curl_easy_setopt(curl, CURLOPT_PASSWORD, ftp_passwd.toStdString().c_str());// 发起连接请求res = curl_easy_perform(curl);// 检查连接状态if (res == CURLE_OK) {ui->Display_Edit->appendPlainText("ftp 测试连接成功 : "+QString(curl_easy_strerror(res)));} else {ui->Display_Edit->appendPlainText("ftp 测试连接失败 : "+QString(curl_easy_strerror(res)));}curl_easy_cleanup(curl);}curl_global_cleanup();
}
5.2.3 测试单个文件上传正常
//用户自定义数据指针  下载总估计值 已下载   上传总值  已上传
static int progressCallback(void *p, double dltotal, double dlnow, double ult,double uln)
{Q_UNUSED(p);Q_UNUSED(dltotal);Q_UNUSED(dlnow);double process = (double)uln / ult * 100;qDebug()<<"progressCallback :"<<process;return 0;
}//开始一个文件的上传测试  注意进度的打印
void MainWindow::on_pb_start_one_clicked()
{ui->Display_Edit->appendPlainText("开始上传一个文件:"+ ftp_file);//这里实际是基于上面测试连接的基础上  加上真正的上传。CURL *curl;curl_global_init(CURL_GLOBAL_DEFAULT);curl = curl_easy_init();if(curl == nullptr){ui->Display_Edit->appendPlainText("创建句柄失败,请检查!");curl_global_cleanup();return;}FILE* hd_src = fopen(ftp_file.toStdString().c_str(), "rb");if (!hd_src) {ui->Display_Edit->appendPlainText("打开文件失败:"+ftp_file);curl_global_cleanup();return;}//这里是真正的上传目的 注意文件名的拼接QString ftp_server_addr = ftp_addr + ftp_dir +"/test";curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);curl_easy_setopt(curl, CURLOPT_URL, ftp_server_addr.toStdString().c_str());curl_easy_setopt(curl, CURLOPT_USERPWD, QString(ftp_username+":"+ftp_passwd).toStdString().c_str());fseek(hd_src, 0L, SEEK_END);long fileSize = ftell(hd_src);fseek(hd_src, 0L, SEEK_SET);curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);  //读文件的回调curl_easy_setopt(curl, CURLOPT_READDATA, hd_src); //设置要上传的文件的指针curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)fileSize);// 设置CURLOPT_NOPROGRESS为0,以启用进度回调函数// 设置CURLOPT_PROGRESSFUNCTION为progressCallback函数指针,用于获取上传进度curl_easy_setopt(curl,CURLOPT_NOPROGRESS , 0L);curl_easy_setopt(curl,CURLOPT_PROGRESSFUNCTION , progressCallback);CURLcode res;res = curl_easy_perform(curl);// 检查连接状态if (res == CURLE_OK) {ui->Display_Edit->appendPlainText("ftp 上传文件成功 : "+ftp_file);} else {ui->Display_Edit->appendPlainText("ftp 上传文件失败 : "+ftp_file);}curl_easy_cleanup(curl);fclose(hd_src);curl_global_cleanup();
}
5.2.4 如果服务器上已经有该文件,并且上传一半,测试断点续传功能正常。

===》这里我阻塞一段时间,参考example下的resume代码,一直无法实现,最后发现获取服务器上文件名称定义的CURL 实例,需要和真正上传文件时CURL实例

static size_t getcontentlengthfunc(void *ptr, size_t size, size_t nmemb,  void *stream)
{int r;long len = 0;char *pptr = (char*)ptr;r = sscanf(pptr, "Content-Length: %ld\n", &len);if(r)*((long *) stream) = len;return size * nmemb;
}static size_t discardfunc(void *ptr, size_t size, size_t nmemb, void *stream)
{char * cptr = (char*) ptr;qDebug()<<QString::fromUtf8(cptr);(void)ptr;(void)stream;return size * nmemb;
}static size_t readfunc(char *ptr, size_t size, size_t nmemb, void *stream)
{FILE *f = static_cast<FILE *>(stream);size_t n;if(ferror(f))return CURL_READFUNC_ABORT;n = fread(ptr, size, nmemb, f) * size;return n;
}//单个文件的断点续传测试 构造断点续传场景
//发现断点续传,这种方案并不可靠,分开构造curl分别获取服务端文件大小,进行续传处理
void MainWindow::on_pb_restart_one_clicked()
{CURL *curl = nullptr;curl_global_init(CURL_GLOBAL_ALL);curl = curl_easy_init();if(curl == nullptr){ui->Display_Edit->appendPlainText("创建句柄失败,请检查!");curl_global_cleanup();return;}FILE* hd_src = fopen(ftp_file.toStdString().c_str(), "rb");if (!hd_src) {ui->Display_Edit->appendPlainText("打开文件失败:"+ftp_file);curl_global_cleanup();return;}//设置上传   url  用户名和密码 默认端口QString ftp_server_addr = ftp_addr + ftp_dir +"/test";curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);curl_easy_setopt(curl, CURLOPT_URL, ftp_server_addr.toStdString().c_str());curl_easy_setopt(curl, CURLOPT_USERPWD, QString(ftp_username+":"+ftp_passwd).toStdString().c_str());long uploaded_len = 0;curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, getcontentlengthfunc);  //相应头部的回调函数curl_easy_setopt(curl, CURLOPT_HEADERDATA, &uploaded_len);             //从头部获取到目标远程文件的大小CURLcode res = CURLE_GOT_NOTHING;for(int i=0; (i<3)  && (res!= CURLE_OK); ++i){curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);  //只获取响应头信息  而不实际下载响应体curl_easy_setopt(curl, CURLOPT_HEADER, 1L);  //响应头信息包含在返回的数据中 和上面的读数据一致res = curl_easy_perform(curl); //这里获取远程服务器文件的大小 if(res != CURLE_OK)continue;curl_easy_cleanup(curl); //获取后,先清理,再重新进行必要的设置,上传成功了。curl = curl_easy_init();curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);curl_easy_setopt(curl, CURLOPT_URL, ftp_server_addr.toStdString().c_str());curl_easy_setopt(curl, CURLOPT_USERPWD, QString(ftp_username+":"+ftp_passwd).toStdString().c_str());curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, discardfunc);            //这是获取下载的数据?curl_easy_setopt(curl, CURLOPT_READFUNCTION, readfunc);curl_easy_setopt(curl, CURLOPT_READDATA, hd_src);             //发送请求中回调函数的指针curl_easy_setopt(curl, CURLOPT_FTPPORT, "-");   //默认端口curl_easy_setopt(curl, CURLOPT_ACCEPTTIMEOUT_MS, 7000L);curl_easy_setopt(curl, CURLOPT_FTP_CREATE_MISSING_DIRS, 1L);  //自动创建缺失目录curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);ui->Display_Edit->appendPlainText("获取到服务器文件大小为:"+QString::number(uploaded_len));curl_easy_setopt(curl, CURLOPT_NOBODY, 0L);  //重新设置curl_easy_setopt(curl, CURLOPT_HEADER, 0L);fseek(hd_src, uploaded_len, SEEK_SET); //把hd_src从开始位置偏移uploaded_len长度curl_easy_setopt(curl, CURLOPT_APPEND, 1L); //远程文件存在  则追加}if(res != CURLE_OK){curl_easy_setopt(curl, CURLOPT_APPEND, 0L);}res = curl_easy_perform(curl); //真正的数据上传if(res == CURLE_OK)ui->Display_Edit->appendPlainText("断点续传文件成功 !");elseui->Display_Edit->appendPlainText("断点续传文件失败 ! "+QString(curl_easy_strerror(res)));fclose(hd_src);curl_easy_cleanup(curl);curl_global_cleanup();
}

6:阻塞问题

1:断点续传一直不生效,发现是设置CURLOPT_NOBODY 和CURLOPT_HEADER 后,就不会触发上传。

===》解决方案是获取服务器上对应文件大小后,先清理对应的CURL * 再进行设置就好

2:模拟断点续传时,手动在linux上把目标文件进行部分内容的删除。

===》断点续传再次触发后,发现和源文件相比,两次上传之间有个换行。

===》解决方案,发现是linux环境手动删除时,自带一个换行符号,把该文件传到window环境上删除换行符后正常。

只是第一版初步的探索demo,技术点已经攻克,下一步优化代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/317921.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springCloud之Stream

1、简介 Spring Cloud Stream是一个用来为微服务应用构建 消息驱动 能力的框架。通过使用 Spring Cloud Strea m &#xff0c;可以有效简化开发人员对消息中间件的使用复杂度&#xff0c;降低代码与消息中间件间的耦合度&#xff0c;屏蔽消息中间件 之 间的差异性&#xff0c;…

Paddle3D 2 雷达点云CenterPoint模型训练

2 Paddle3D 雷达点云CenterPoint模型训练–包含KITTI格式数据地址 2.0 数据集 百度DAIR-V2X开源路侧数据转kitti格式。 2.0.1 DAIR-V2X-I\velodyne中pcd格式的数据转为bin格式 参考源码&#xff1a;雷达点云数据.pcd格式转.bin格式 def pcd2bin():import numpy as npimport…

基于头脑风暴算法优化的Elman神经网络数据预测 - 附代码

基于头脑风暴算法优化的Elman神经网络数据预测 - 附代码 文章目录 基于头脑风暴算法优化的Elman神经网络数据预测 - 附代码1.Elman 神经网络结构2.Elman 神经用络学习过程3.电力负荷预测概述3.1 模型建立 4.基于头脑风暴优化的Elman网络5.测试结果6.参考文献7.Matlab代码 摘要&…

EDI 项目推进流程

EDI 需求确认 交易伙伴发来EDI对接邀请&#xff0c;企业应该如何应对&#xff1f; 首先需要确认EDI需求&#xff0c;通常包括传输协议和报文标准以及传输的业务单据类型。可以向交易伙伴发送以下内容&#xff1a; &#xff08;中文版&#xff09; 与贵司建立EDI连接需要使用…

中国计算机学会推荐国际学术会议及时间(计算机体系结构/高性能计算/存储系统)

中国计算机学会推荐国际学术会议及时间 (计算机体系结构/高性能计算/存储系统) 参考资料 参考链接: call4papers

网工内推 | 网络工程师,NP认证优先,上市公司,包吃,最高15薪

01 无锡先导智能装备股份有限公司 招聘岗位&#xff1a;高级网络工程师 职责描述&#xff1a; 1.依据项目规划方案提供硬件及网络方案设计&#xff1b; 2.面向客户提供网络技术支持&#xff0c;包括故障的解决、性能的优化、日常维护等&#xff1b; 3.和合作伙伴、供应商的技术…

(七)独立按键

文章目录 独立按键原理图三行代码法简单概述代码书写键码推算如何使用短按键长按键 状态机法简单概述代码书写键码推算如何使用短按键长按键 现象 独立按键原理图 三行代码法 简单概述 代码书写 u8 Trg 0x00;//短按键 u8 Cont 0x00;//长按键 void BtnThree(void) {u8 reada…

打造专业开发者指南:针对ShardingProxy分库分表解决策略的深度剖析 – 详解部署、使用、服务治理与优化技巧

一、 ShardingProxy快速使用 ShardingProxy的功能同样是分库分表&#xff0c;但是他是一个独立部署的服务端&#xff0c;提供 统一的数据库代理服务。注意&#xff0c;ShardingProxy目前只支持MySQL和PostgreSQL。并且&#xff0c;客户端连接ShardingProxy时&#xff0c;最好使…

Java反射机制和动态代理

反射和动态代理 反射前言获取class对象的方式反射获取构造方法反射获取成员变量反射获取成员方法实例 动态代理 反射 前言 什么是反射&#xff1f; 反射允许对成员变量&#xff0c;成员方法和构造方法的信息进行编程访问。 为什么用反射 / 反射的作用&#xff1f; 可以轻易地获…

图像分割-漫水填充法 floodFill (C#)

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 本文的VB版本请访问&#xff1a;图像分割-漫水填充法 floodFill-CSDN博客 FloodFill方法是一种图像处理算法&#xff0c;它的目的是…

【SpringBoot框架篇】34.使用Spring Retry完成任务的重试

文章目录 简要1.为什么需要重试&#xff1f;2.添加maven依赖3.使用Retryable注解实现重试4.基于RetryTemplate模板实现重试 简要 Spring实现了一套重试机制&#xff0c;功能简单实用。Spring Retry是从Spring Batch独立出来的一个功能&#xff0c;已经广泛应用于Spring Batch,…

哪些洗地机比较好?洗地机选购指南

随着社会生活水平的提高&#xff0c;人们对居家环境的卫生和清洁要求不断提升。家用洗地机作为一种先进的清洁工具&#xff0c;带来了许多便利和优势&#xff0c;特别是在解决一些特殊需求的家庭环境方面。 以下是一些家用洗地机的优势和适用场景&#xff1a; 1.高效清洁&…