【大话QT之十】实现FTP断点续传
应用需求:
网盘开发工作逐步进入各部分的整合阶段,当用户在client改动或新添加一个文件时。该文件要同步上传到server端相应的用户文件夹下,因此针对传输数据(即:上传、下载)这一块如今既定了三种传输方式,即:Ftp传输、HTTP传输以及基于UDT的传输。
且这三种传输数据方式是可配的,能够通过不同的接口调用。相比这三种方式。基于UDT的大量文件传输是比較值得研究与创新的地方,它在底层是基于UDP,在上层实现了可靠性的控制;同一时候它充分考虑到了基于在公网环境下基于Tcp进行传输时拥塞控制算法的缺点,实现了自己的拥塞控制算法,在实际測试中其性能也是明显高于基于Tcp的传输。
关于UDT实现文件传输仅仅进行了技术调研,还没有真正实现。这一部分内容将在兴许文章中提及。这三天的时间仅仅实现了基于FTP的支持断点续传的文件上传、下载。
实现原理:
离我们近期的断点续传的应用样例是:迅雷。当使用迅雷下载一个大文件时,它实现了以下的功能:1> 电脑突然断电或程序突然退出后,当我们又一次启动迅雷时它还会从程序退出时已经下载的文件点继续向后下载,而不是文件又从头開始下载。2> 能够设置採用多个线程同一时候下载,每一个线程仅仅下载文件里的某一部分,比如:使用三个线程下载一个9000个字节的文件。则第一个线程下载第1—3000个字节,第二个线程下载第3001—6000个字节,第三个线程下载第6001—9000个字节。这三个线程是同一时候下载一个文件,仅仅是下载不同的部分。它会把下载的文件片段暂存在某个位置,当三个线程所有下载完毕时再拼成一个完整的文件。
这里不用多说,其长处显而易见。
事实上,断点续传实现的原理非常easy,就是不管是上传还是下载时都能够实时记录下已经上传了或下载了多少字节,假设中间由于某种原因传输断开,下载启动时仅仅须要再又一次从已经下载的位置继续下载或上传就能够了。
利用Qftp实现断点续传:
QT中有一个实现Ftp的类:Qftp,它提供了主要的ftp的使用方式,连接ftpserver:connectToHost;登录:login。上传:put;下载:get。使用这些方法能够实现与ftpserver交互实现文件上传、下载。
可是使用它原生提供的put与get方法,无法实现断点续传。因此。为了实现断点续传我们须要又一次实现文件传输。并在当中加入断点续传的控制。事实上Ftp文件传输的本质也是使用Tcp来实现底层的文件传输。大体思路就是:利用Qftp的connectToHost登录ftpserver,使用login登录ftpserver,使用rawCommand发送ftp原生态的命令,使用QTcpSocket实现文件数据的传输。
首先,使用QTcpSocket实现文件数据的传输,须要设置ftpserver为“PASV”被动接收方式。即ftpserver被动地接收来自client的连接请求。
Ftpserver全部能够发送的原生命令有:http://www.nsftools.com/tips/RawFTP.htm。
实现断点上传的命令发送流程:
1、rawCommand("TYPE I");设置数据传输的类型:二进制数据或ASCII
2、rawCommand("PASV")。设置server为被动接收方式。发送PASV命令后。server会返回自己开启的传输数据的port。等待client连接进行传输数据。
返回的数据格式为:“227 Entering Passive Mode (192, 168, 2, 18, 118, 32)”,然后从返回的信息里面或去相关的信息,ftpserver的IP地址:192.168.2.18;ftpserver开启的用于传输数据的port:118*256 + 32 = 30240。获得该信息后就须要建立TcpSocket的通信链路。连接ftpserver。
3、rawCommand("APPE remote-file-path");设置server端remote-file-path为追加的方式。
假设此时改文件不存在,则server端会创建一个。
4、完毕上述流程后,就能够打开本地文件进行读取,并通过tcpsocket链路发送出去(write)。
实现断点下载的命令发送流程:
1、rawCommand("TYPE I");设置数据传输的类型:二进制数据或ASCII
2、rawCommand("PASV");设置server为被动接收方式。发送PASV命令后,server会返回自己开启的传输数据的port,等待client连接进行传输数据。
返回的数据格式为:“227 Entering Passive Mode (192, 168, 2, 18, 118, 32)”。然后从返回的信息里面或去相关的信息,ftpserver的IP地址:192.168.2.18;ftpserver开启的用于传输数据的port:118*256 + 32 = 30240;获得该信息后就须要建立TcpSocket的通信链路。连接ftpserver。
3、rawCommand("REST size");该命令设置ftpserver从本地文件的哪个地方開始进行传输数据。
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. 如今实现的是单线程,尚未加入多线程断点下载以及队列的实现。
【大话QT之十】实现FTP断点续传的更多相关文章
- 【大话QT之十】实现FTP断点续传(需要设置ftp服务器为“PASV”被动接收方式)
应用需求: 网盘开发工作逐步进入各部分的整合阶段,当用户在客户端修改或新增加一个文件时,该文件要同步上传到服务器端对应的用户目录下,因此针对数据传输(即:上传.下载)这一块现在既定了三种传输方式,即: ...
- 【大话QT之十二】基于CTK Plugin Framework的插件版本号动态升级
应用需求: 某些场景下我们可能面临这种问题,在执行着的应用程序不能终止的情况下,升级某个功能(或添,或减.或改动).在不採用CTK Plugin Framework插件系统架构的情况下这将是非常困难的 ...
- 【大话QT之十六】使用ctkPluginFramework插件系统构建项目实战
"使用ctkPluginFramework插件系统构建项目实战",这篇文章是写博客以来最纠结的一篇文章. 倒不是由于技术都多么困难,而是想去描写叙述一个项目架构採用ctkPlugi ...
- 【大话QT之十四】QT实现多语言切换
功能需求: 网盘client要可以实现多国语言的切换,第一版要支持中.英文的切换. 在实现过程中感觉QT对多国语言的支持还是非常不错的.制作多语言包非常方便.切换的逻辑也非常easy. 以下就来看一下 ...
- 【大话QT之十三】系统软件自己主动部署实现方案
本篇文章是对[大话QT之十二]基于CTK Plugin Framework的插件版本号动态升级文章的补充,在上篇文章中我们阐述的重点是新版本号的插件已经下载到plugins文件夹后应该怎样更新本地正在 ...
- 实现FTP断点续传
应用需求: 网盘开发工作逐步进入各部分的整合阶段,当用户在客户端修改或新增加一个文件时,该文件要同步上传到服务器端对应的用户目录下,因此针对数据传输(即:上传.下载)这一块现在既定了三种传输方式,即: ...
- C#编程总结(十二)断点续传
C#编程总结(十二)断点续传 我们经常使用下载工具,如bit精灵.迅雷.FlashGet,这些软件都支持断点续传. 断点续传即下载任务暂停后可以继续,而无需重新下载,即下载时需要通知服务器的起始位置. ...
- Qt使用QNetworkAccessManager实现Ftp操作
版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:Qt使用QNetworkAccessManager实现Ftp操作 本文地址:http: ...
- Java ftp断点续传
FtpTransFile类 import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundExcept ...
随机推荐
- iOS开发一行代码系列:一行搞定输入框
近期总结了下开发过程中经常使用的功能,发现有时候我在做反复性的劳动.于是决定把经常使用的功能抽出来,方便下次使用. 我的想法是:用最少的代码来解决这个问题.于是写了一些经常使用的工具类,名字就叫一行代 ...
- pandas深入理解
Pandas是一个Python库,旨在通过“标记”和“关系”数据以完成数据整理工作,库中有两个主要的数据结构Series和DataFrame In [1]: import numpy as np In ...
- 均匀分布(uniform distribution)期望的最大似然估计(maximum likelihood estimation)
maximum estimator method more known as MLE of a uniform distribution [0,θ] 区间上的均匀分布为例,独立同分布地采样样本 x1, ...
- Hadoop 三剑客之 —— 分布式文件存储系统 HDFS
一.介绍 二.HDFS 设计原理 2.1 HDFS 架构 2.2 文件系统命名空间 2.3 数据复制 2.4 数据复制的实现原理 2.5 副本的选择 2 ...
- Centos上JDK的安装搭建
一.下载 yum search java|grep jdk //查找所有jdk版本 二.选择安装1.8 yum install java-1.8.0-openjdk-src-debug.x86_64 ...
- 判断是否是Ajax请求
Request.IsAjaxRequest()判断是否是ajax请求原理:Http协议上有个X-Requested-With:XML HttpRequest属性判断的 mvc后台通过Request可以 ...
- 迁移oracle数据库至新分区
本文地址: http://www.cnblogs.com/blackmanba/p/move-oracle-database.html或者http://forkme.info/move-oracle- ...
- 高德SDK获取到的坐标转换为GPS真实坐标方法,Java版
发现高德SDK不提供高德的坐标转GPS坐标(GCJ_02转WGS_84),下面是一份Java版的 /**************************** 文件名:GCJ2WGS.java 创建时间 ...
- iOS11访问相册权限变更问题
手机升到iOS 11后 发现之前正常的图片保存功能无法正常使用 会闪退 经测试发现应该是权限没有开启的原因 但是NSPhotoLibraryUsageDescription已经写入plist ...
- web开发必看:你的网站支持https吗?
如果有一项技术可以让网站的访问速度更快.更安全.并且seo权重提升(百度除外),而且程序员不需要改代码就可以全站使用,最重要的是,不需要额外花钱,那有这么好的事情吗? HTTP通信协议是全球万维网ww ...