应用需求:

网盘开发工作逐步进入各部分的整合阶段,当用户在客户端修改或新增加一个文件时,该文件要同步上传到服务器端对应的用户目录下,因此针对数据传输(即:上传、下载)这一块现在既定了三种传输方式,即: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()发送原生命令返回后的槽函数:

  1. void LHTFileTransfer::ProcRawCommandReply(int nReplyCode, QString strDetail)
  2. {
  3. //! TYPE
  4. if (200 == nReplyCode)
  5. {
  6. m_ftpHandle->rawCommand("PASV");
  7. if (currentItem.task_type.compare("Upload") == 0)
  8. {
  9. op = QString("Put");
  10. }
  11. else if (currentItem.task_type.compare("DownLoad") == 0)
  12. {
  13. op = QString("Get");
  14. }
  15. }
  16. //! PASV
  17. else if(227 == nReplyCode)
  18. {
  19. const QString backResult = strDetail;
  20. if (NULL != m_sendDataSocket)
  21. {
  22. m_sendDataSocket->close();
  23. delete m_sendDataSocket;
  24. }
  25. m_sendDataSocket = new QTcpSocket();
  26. connect(m_sendDataSocket, SIGNAL(readyRead()), this, SLOT(ProcReadyRead()), Qt::UniqueConnection);
  27. connect(m_sendDataSocket, SIGNAL(readChannelFinished()), this, SLOT(ProcReadChannelFinished()), Qt::UniqueConnection);
  28. connect(m_sendDataSocket, SIGNAL(bytesWritten(qint64)), this, SLOT(ProcBytesWritten(qint64)), Qt::UniqueConnection);
  29. QStringList lstr = backResult.split("(").last().split(")").first().split(",");
  30. int nAddress = lstr.at(0).toInt()<<24 |
  31. lstr.at(1).toInt()<<16 |
  32. lstr.at(2).toInt()<<8 |
  33. lstr.at(3).toInt();
  34. QHostAddress hostAddress(nAddress);
  35. int nPort = lstr.at(lstr.length() - 2).toInt() * 256 + lstr.last().toInt();
  36. m_sendDataSocket->connectToHost(hostAddress, nPort);
  37. //! APPE , 需要接远程文件的绝对路径
  38. QString appeShell;
  39. if (op.compare("Put") == 0)
  40. {
  41. appeShell = QString("APPE %1").arg(currentItem.file_remote_path);
  42. }
  43. else if (op.compare("Get") == 0)
  44. {
  45. //! 这里的REST后面的大小应该为本地保存的问价的大小
  46. appeShell = QString("REST 0");
  47. }
  48. m_ftpHandle->rawCommand(appeShell);
  49. }
  50. //! 发送数据
  51. else if (150 == nReplyCode)
  52. {
  53. if (op.compare("Put") == 0)
  54. {
  55. m_fileHandle = new QFile(currentItemFilePath);
  56. if (!m_fileHandle->open(QIODevice::ReadOnly))
  57. {
  58. qDebug() << "file open error ...";
  59. return ;
  60. }
  61. const qint64 fileSize = m_fileHandle->size();
  62. m_fileHandle->seek(currentItem.uploaded_size);
  63. while(!m_fileHandle->atEnd())
  64. {
  65. const qint64 nBlockSize = 16 * 1024 ;
  66. char buf[16 * 1024];
  67. qint64 nowPos = m_fileHandle->pos();
  68. qint64 readLen = m_fileHandle->read(buf, nBlockSize);
  69. if (readLen !=0 && readLen != -1)
  70. {
  71. m_sendDataSocket->write(buf, readLen);
  72. m_sendDataSocket->flush();
  73. emit DataTransferProgress(nowPos, fileSize);
  74. }
  75. }
  76. m_sendDataSocket->flush();
  77. m_sendDataSocket->close();
  78. m_sendDataSocket = NULL ;
  79. emit DataTransferProgress(m_fileHandle->pos(), m_fileHandle->size());
  80. m_procTask.remove(currentItemFilePath);
  81. m_fileHandle->close();
  82. //emit StartNextTask();
  83. }
  84. else if(op.compare("Get") == 0)
  85. {
  86. m_fileHandle = new QFile(currentItem.file_remote_path);
  87. if (!m_fileHandle->open(QIODevice::WriteOnly))
  88. {
  89. qDebug() << "file open error ...";
  90. return ;
  91. }
  92. }
  93. }
  94. else if(350 == nReplyCode)
  95. {
  96. QString shell = QString("RETR %1").arg(currentItemFilePath);
  97. m_ftpHandle->rawCommand(shell);
  98. }
  99. }

2. 断点下载时实现buffer读取的函数:

  1. void LHTFileTransfer::ProcReadyRead()
  2. {
  3. qDebug() << "[DownLoad] ProcReadyReady ....";
  4. QByteArray buffer = m_sendDataSocket->readAll();
  5. m_fileHandle->write(buffer);
  6. m_fileHandle->flush();
  7. emit DataTransferProgress(m_fileHandle->size(), 0);
  8. }

面临的问题以及后续需要优化的地方:

1. 字符编码问题,即当需要上传的文件名是中文名称时,需要对其进行转码。

2. 现在实现的是单线程,尚未添加多线程断点下载以及队列的实现。

http://blog.csdn.net/houqd2012/article/details/31798087

【大话QT之十】实现FTP断点续传(需要设置ftp服务器为“PASV”被动接收方式)的更多相关文章

  1. 【大话QT之十】实现FTP断点续传

    应用需求: 网盘开发工作逐步进入各部分的整合阶段,当用户在client改动或新添加一个文件时.该文件要同步上传到server端相应的用户文件夹下,因此针对传输数据(即:上传.下载)这一块如今既定了三种 ...

  2. 【大话QT之十二】基于CTK Plugin Framework的插件版本号动态升级

    应用需求: 某些场景下我们可能面临这种问题,在执行着的应用程序不能终止的情况下,升级某个功能(或添,或减.或改动).在不採用CTK Plugin Framework插件系统架构的情况下这将是非常困难的 ...

  3. 【大话QT之十六】使用ctkPluginFramework插件系统构建项目实战

    "使用ctkPluginFramework插件系统构建项目实战",这篇文章是写博客以来最纠结的一篇文章. 倒不是由于技术都多么困难,而是想去描写叙述一个项目架构採用ctkPlugi ...

  4. 【大话QT之十四】QT实现多语言切换

    功能需求: 网盘client要可以实现多国语言的切换,第一版要支持中.英文的切换. 在实现过程中感觉QT对多国语言的支持还是非常不错的.制作多语言包非常方便.切换的逻辑也非常easy. 以下就来看一下 ...

  5. Linux centos 安装 ftp(Vsftp) 与 设置ftp(Vsftp)

    本文章只是简单搭建,因为公司只须要简单使用,虽然简单但是之前也走了一些弯路,所以决定把过程记录下来. 一.Vsftp安装与卸载 安装:yum install vsftpd 卸载:yum remove ...

  6. 实现FTP断点续传

    应用需求: 网盘开发工作逐步进入各部分的整合阶段,当用户在客户端修改或新增加一个文件时,该文件要同步上传到服务器端对应的用户目录下,因此针对数据传输(即:上传.下载)这一块现在既定了三种传输方式,即: ...

  7. 【大话QT之十三】系统软件自己主动部署实现方案

    本篇文章是对[大话QT之十二]基于CTK Plugin Framework的插件版本号动态升级文章的补充,在上篇文章中我们阐述的重点是新版本号的插件已经下载到plugins文件夹后应该怎样更新本地正在 ...

  8. vscode 通过ftp发布vue到azure服务器

    参考资料:vs code配置ftp连接远程服务器实现代码文自动上传 1.在vscode应用商店中搜索拓展sftp插件,然后进行安装.2.安装完成后重启窗口,按快捷键Ctrl+shift+p,输入sft ...

  9. C#编程总结(十二)断点续传

    C#编程总结(十二)断点续传 我们经常使用下载工具,如bit精灵.迅雷.FlashGet,这些软件都支持断点续传. 断点续传即下载任务暂停后可以继续,而无需重新下载,即下载时需要通知服务器的起始位置. ...

随机推荐

  1. 在Ubuntu Linux下安装Code::Blocks和Eclipse CDT

           最近小白由于有工作学习的需要,要尝试在Linux下进行C++编程.所以特地花了一点时间研究一下Linux下的C++的IDE.最后我尝试了使用Code::Blocks和Eclipse两个著 ...

  2. java--简单排序算法

    1.冒泡排序 排序原理: 过程简单,首先将第一个记录的关键字和第二个记录的关键字进行比较,若为逆序,则将两个记录交换,然后比较第二个记录与第三个记录得关键字.以此类推,直至第n-1个记录和第n个记录的 ...

  3. Checkbox 全选、反选

    1.全选.反选 <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></t ...

  4. VS2010 VS2012 如何连接Oracle 11g数据库

    oracle是开发者常用的数据库,在做.NET开发是,由于Vs自带的驱动只能连接oracle 10g及以下版本,那么如何连接oracle 11g呢? 工具/原料   事先安装VS2010或者VS201 ...

  5. C# ACM poj1008

    玛雅历 public static void Acm1008(int day, string mon, int year) { ; switch (mon) { case "pop" ...

  6. CSS FIXED porn javhd

    CSS position property - W3Schools W3Schools › cssref › pr_class_position Definition and Usage. The p ...

  7. 04_SSM框架整合(Spring+SpringMVC+MyBatis)

    [SSM的系统架构] [整合概述] 第一步: MyBatis和Spring整合,通过Spring管理mapper接口. 使用mapper的扫描器自动扫描mapper接口在Spring中进行注册. 第二 ...

  8. 392. Is Subsequence

    392. Is Subsequence 水题,先是判断长度,长度t比s小,返回false,然后从左到右扫描t,然后同时扫描s,如果相同,s的index就往后拉一个,如果s的index等于s长度,返回t ...

  9. CAF(C++ actor framework)使用随笔(send sync_send)(二)

    a). 发完就忘, 就像上面anon_send 以及send #include <iostream> #include "caf/all.hpp" #include & ...

  10. java中instanceof和getClass()的作用

    初学者难免有点混淆java中instanceof和getClass()的作用,  下面就来一一讲解. 父类A: class A { } 子类B: class B extends A { }  构造对象 ...