实现FTP断点续传
应用需求:
网盘开发工作逐步进入各部分的整合阶段,当用户在客户端修改或新增加一个文件时,该文件要同步上传到服务器端对应的用户目录下,因此针对数据传输(即:上传、下载)这一块现在既定了三种传输方式,即:Ftp传输、HTTP传输以及基于UDT的传输。且这三种数据传输方式是可配的,可以通过不同的接口调用。相比这三种方式,基于UDT的大量文件传输是比较值得研究与创新的地方,它在底层是基于UDP,在上层实现了可靠性的控制;同时它充分考虑到了基于在公网环境下基于Tcp进行传输时拥塞控制算法的缺点,实现了自己的拥塞控制算法,在实际测试中其性能也是明显高于基于Tcp的传输。关于UDT实现文件传输只进行了技术调研,还没有真正实现,这一部分内容将在后续文章中提及。这三天的时间只实现了基于FTP的支持断点续传的文件上传、下载。
实现原理:
离我们最近的断点续传的应用例子是:迅雷。当使用迅雷下载一个大文件时,它实现了下面的功能:1> 电脑突然断电或程序突然退出后,当我们重新启动迅雷时它还会从程序退出时已经下载的文件点继续向后下载,而不是文件又从头开始下载。2> 可以设置采用多个线程同时下载,每个线程只下载文件中的某一部分,例如:使用三个线程下载一个9000个字节的文件,则第一个线程下载第1—3000个字节,第二个线程下载第3001—6000个字节,第三个线程下载第6001—9000个字节。这三个线程是同时下载一个文件,只是下载不同的部分,它会把下载的文件片段暂存在某个位置,当三个线程全部下载完成时再拼成一个完整的文件。这里不用多说,其优点显而易见。
其实,断点续传实现的原理很简单,就是无论是上传还是下载时都可以实时记录下已经上传了或下载了多少字节,如果中间因为某种原因传输断开,下载启动时只需要再重新从已经下载的位置继续下载或上传就可以了。
利用Qftp实现断点续传:
QT中有一个实现Ftp的类:Qftp,它提供了基本的ftp的使用方式,连接ftp服务器:connectToHost;登录:login;上传:put;下载:get,使用这些方法可以实现与ftp服务器交互实现文件上传、下载。但是使用它原生提供的put与get方法,无法实现断点续传。因此,为了实现断点续传我们需要重新实现文件传输,并在其中添加断点续传的控制。其实Ftp文件传输的本质也是使用Tcp来实现底层的文件传输。大体思路就是:利用Qftp的connectToHost登录ftp服务器,使用login登录ftp服务器,使用rawCommand发送ftp原生态的命令,使用QTcpSocket实现文件数据的传输。
首先,使用QTcpSocket实现文件数据的传输,需要设置ftp服务器为“PASV”被动接收方式,即ftp服务器被动地接收来自客户端的连接请求。
Ftp服务器所有可以发送的原生命令有:http://www.nsftools.com/tips/RawFTP.htm。
实现断点上传的命令发送流程:
1、rawCommand("TYPE I");设置传输数据的类型:二进制数据或ASCII
2、rawCommand("PASV");设置服务器为被动接收方式。发送PASV命令后,服务器会返回自己开启的数据传输的端口,等待客户端连接进行数据传输。返回的数据格式为:“227 Entering Passive Mode (192, 168, 2, 18, 118, 32)”,然后从返回的信息里面或去相关的信息,ftp服务器的IP地址:192.168.2.18;ftp服务器开启的用于数据传输的端口:118*256 + 32 = 30240;获得该信息后就需要建立TcpSocket的通信链路,连接ftp服务器。
3、rawCommand("APPE remote-file-path");设置服务器端remote-file-path为追加的方式。如果此时改文件不存在,则服务器端会创建一个。
4、完成上述流程后,就可以打开本地文件进行读取,并通过tcpsocket链路发送出去(write)。
实现断点下载的命令发送流程:
1、rawCommand("TYPE I");设置传输数据的类型:二进制数据或ASCII
2、rawCommand("PASV");设置服务器为被动接收方式。发送PASV命令后,服务器会返回自己开启的数据传输的端口,等待客户端连接进行数据传输。返回的数据格式为:“227 Entering Passive Mode (192, 168, 2, 18, 118, 32)”,然后从返回的信息里面或去相关的信息,ftp服务器的IP地址:192.168.2.18;ftp服务器开启的用于数据传输的端口:118*256 + 32 = 30240;获得该信息后就需要建立TcpSocket的通信链路,连接ftp服务器。
3、rawCommand("REST size");该命令设置ftp服务器从本地文件的哪个地方开始进行数据传输。
4、rawCommand(“RETR remote-file-path”);开始从远程主机传输文件。
文件上传时在设置APPE返回之后,就可以打开本地文件进行上传;文件下载时,收到PASV的返回信息建立tcpsocket的连接后,需要建立readyRead()的信号槽,在该槽函数中实现数据的读取。
关键代码:
1. 处理rawCommand()发送原生命令返回后的槽函数:
- void LHTFileTransfer::ProcRawCommandReply(int nReplyCode, QString strDetail)
- {
- //! TYPE
- if (200 == nReplyCode)
- {
- m_ftpHandle->rawCommand("PASV");
- if (currentItem.task_type.compare("Upload") == 0)
- {
- op = QString("Put");
- }
- else if (currentItem.task_type.compare("DownLoad") == 0)
- {
- op = QString("Get");
- }
- }
- //! PASV
- else if(227 == nReplyCode)
- {
- const QString backResult = strDetail;
- if (NULL != m_sendDataSocket)
- {
- m_sendDataSocket->close();
- delete m_sendDataSocket;
- }
- m_sendDataSocket = new QTcpSocket();
- connect(m_sendDataSocket, SIGNAL(readyRead()), this, SLOT(ProcReadyRead()), Qt::UniqueConnection);
- connect(m_sendDataSocket, SIGNAL(readChannelFinished()), this, SLOT(ProcReadChannelFinished()), Qt::UniqueConnection);
- connect(m_sendDataSocket, SIGNAL(bytesWritten(qint64)), this, SLOT(ProcBytesWritten(qint64)), Qt::UniqueConnection);
- QStringList lstr = backResult.split("(").last().split(")").first().split(",");
- int nAddress = lstr.at(0).toInt()<<24 |
- lstr.at(1).toInt()<<16 |
- lstr.at(2).toInt()<<8 |
- lstr.at(3).toInt();
- QHostAddress hostAddress(nAddress);
- int nPort = lstr.at(lstr.length() - 2).toInt() * 256 + lstr.last().toInt();
- m_sendDataSocket->connectToHost(hostAddress, nPort);
- //! APPE , 需要接远程文件的绝对路径
- QString appeShell;
- if (op.compare("Put") == 0)
- {
- appeShell = QString("APPE %1").arg(currentItem.file_remote_path);
- }
- else if (op.compare("Get") == 0)
- {
- //! 这里的REST后面的大小应该为本地保存的问价的大小
- appeShell = QString("REST 0");
- }
- m_ftpHandle->rawCommand(appeShell);
- }
- //! 发送数据
- else if (150 == nReplyCode)
- {
- if (op.compare("Put") == 0)
- {
- m_fileHandle = new QFile(currentItemFilePath);
- if (!m_fileHandle->open(QIODevice::ReadOnly))
- {
- qDebug() << "file open error ...";
- return ;
- }
- const qint64 fileSize = m_fileHandle->size();
- m_fileHandle->seek(currentItem.uploaded_size);
- while(!m_fileHandle->atEnd())
- {
- const qint64 nBlockSize = 16 * 1024 ;
- char buf[16 * 1024];
- qint64 nowPos = m_fileHandle->pos();
- qint64 readLen = m_fileHandle->read(buf, nBlockSize);
- if (readLen !=0 && readLen != -1)
- {
- m_sendDataSocket->write(buf, readLen);
- m_sendDataSocket->flush();
- emit DataTransferProgress(nowPos, fileSize);
- }
- }
- m_sendDataSocket->flush();
- m_sendDataSocket->close();
- m_sendDataSocket = NULL ;
- emit DataTransferProgress(m_fileHandle->pos(), m_fileHandle->size());
- m_procTask.remove(currentItemFilePath);
- m_fileHandle->close();
- //emit StartNextTask();
- }
- else if(op.compare("Get") == 0)
- {
- m_fileHandle = new QFile(currentItem.file_remote_path);
- if (!m_fileHandle->open(QIODevice::WriteOnly))
- {
- qDebug() << "file open error ...";
- return ;
- }
- }
- }
- else if(350 == nReplyCode)
- {
- QString shell = QString("RETR %1").arg(currentItemFilePath);
- m_ftpHandle->rawCommand(shell);
- }
- }
2. 断点下载时实现buffer读取的函数:
- void LHTFileTransfer::ProcReadyRead()
- {
- qDebug() << "[DownLoad] ProcReadyReady ....";
- QByteArray buffer = m_sendDataSocket->readAll();
- m_fileHandle->write(buffer);
- m_fileHandle->flush();
- emit DataTransferProgress(m_fileHandle->size(), 0);
- }
面临的问题以及后续需要优化的地方:
1. 字符编码问题,即当需要上传的文件名是中文名称时,需要对其进行转码。
2. 现在实现的是单线程,尚未添加多线程断点下载以及队列的实现。
实现FTP断点续传的更多相关文章
- 【大话QT之十】实现FTP断点续传(需要设置ftp服务器为“PASV”被动接收方式)
应用需求: 网盘开发工作逐步进入各部分的整合阶段,当用户在客户端修改或新增加一个文件时,该文件要同步上传到服务器端对应的用户目录下,因此针对数据传输(即:上传.下载)这一块现在既定了三种传输方式,即: ...
- Java ftp断点续传
FtpTransFile类 import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundExcept ...
- 【大话QT之十】实现FTP断点续传
应用需求: 网盘开发工作逐步进入各部分的整合阶段,当用户在client改动或新添加一个文件时.该文件要同步上传到server端相应的用户文件夹下,因此针对传输数据(即:上传.下载)这一块如今既定了三种 ...
- ftp断点续传
有时候ftp的文件太大了 容易断掉 使用shell下载 1 #!/bin/bash 2 cd /data2/GATK2/refSeqDB/1000genomePhase3 3 ftp -v -n 19 ...
- edtftpj让Java上传FTP文件支持断点续传
在用Java实现FTP上传文件功能时,特别是上传大文件的时候,可以需要这样的功能:程序在上传的过程中意外终止了,文件传了一大半,想从断掉了地方继续传:或者想做类似迅雷下载类似的功能,文件太大,今天传一 ...
- 用windows自带的ftp.exe实现断点续传的方法
摘自http://www.jb51.net/article/10604.htm 动画下载地址: http://www.chinesehack.org/soft/book/goonftp-jc.rar ...
- TCP/IP协议原理与应用笔记02:断点续传
1.断点续传简介: FTP(文件传输协议的简称)(File Transfer Protocol. FTP)客户端软件断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人 ...
- 学习笔记之 curl 命令用法详解
[前言] 本文翻译和整理自 Linux-2.6.32 中和 curl 相关的 Manual Page 描述文档. 文档目的仅在提醒读者所遗忘的知识点,故在整理时削弱了阅读流畅性,适用于对 cu ...
- python基础----文件处理
一.文件处理流程 打开文件,得到文件句柄并赋值给一个变量 通过句柄对文件进行操作 关闭文件 正趣果上果 Interesting fruit fruit 词:郭婞 曲:陈粒 编曲/混音/和声:燕池 萧: ...
随机推荐
- 意犹未尽而来的第一篇Android 逆向
游戏:咕噜王国大冒险 平台:android 目标: 1. 去除乱七八糟提示(本篇目标) 2. 去除google弹窗 3. 破解“all stages” 破文开始: 1. 使用APKIDE反编译:搜索字 ...
- 听同事讲 Bayesian statistics: Part 2 - Bayesian inference
听同事讲 Bayesian statistics: Part 2 - Bayesian inference 摘要:每天坐地铁上班是一件很辛苦的事,需要早起不说,如果早上开会又赶上地铁晚点,更是让人火烧 ...
- uva 12097 - Pie
简单题,二分就行: #include<cstdio> #include<cmath> #define pi acos(-1.0) #define eps 0.000001 #d ...
- PowerDesigner从SqlServer 数据库中导入实体模型
此篇是之前写的,从我的CSDN博客挖过来的- 一.开启数据库服务并配置ODBC数据源 1.开启数据库服务 (1)通过SQL Server Configuration Manager配置工具启动SQL ...
- 加JENKINS的SLAVE节点(LINUX)要注意的事项
从昨天下午到现在,终于解决了所有的问题.作如下记录要点: 1,从节点只要建好目录,JENKINS的包,MASTER会推送过来. 2,ANT,MAVEN之类的,要配置好环境变量,PROFILE.D .B ...
- 【良心noip膜你赛】总结
一点都不良心!!!! AK 快乐爆零快乐!!! 1. A. value512mb 1s规定一个区间的价值为这个区间中所有数 and 起来的值与这个区间所有数 or 起来的值的乘积.例如 3 个数 2, ...
- 【网络流24题】No.11(航空路线问题 最长不相交路径 最大费用流)
[题意] 给定一张航空图, 图中顶点代表城市, 边代表 2 城市间的直通航线. 现要求找出一条满足下述限制条件的且途经城市最多的旅行路线.(1) 从最西端城市出发,单向从西向东途经若干城市到达最东端城 ...
- The APR based Apache Tomcat Native library
Tomcat启动的时候出现下面这样的提示: 2015-11-06 14:24:12 org.apache.catalina.core.AprLifecycleListener init 信息: The ...
- 当函数没有return时错误
error:control reaches end of non-void function 在对应函数+return : 即可
- 在服务 ObtainData 实现的协定列表中找不到协定名称 "IMetadataExchange"。将 ServiceMetadataBehavior 添加到配置文件或直接添加到 ServiceHost,以启用对该协定的支持。
第一种解决方法:最暴力的 配置去掉<endpoint address="mex" binding="mexHttpBinding" contract=&q ...