【写在前面】

本篇为传输篇。

接上篇。


 【正文开始】

在上一篇中,我们已经扫描到了整个局域网中运行了本工具的用户,以 <name, ip> 的形式存储在 DiscoverConnection 中,并且,它是一个单例对象。

在 qml 中,这些 name 还会存储在一个 ListModel 中:

    ...
ListView
{id: listViewclip: trueanchors.top: apLabel.bottomanchors.topMargin: 10anchors.bottom: parent.bottomanchors.left: parent.leftanchors.leftMargin: 5anchors.right: parent.rightspacing: 4ScrollBar.vertical: ScrollBar{policy: ScrollBar.AsNeeded}displaced: Transition{NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad }}model: ListModel { id: accessPoints }delegate: Component{Rectangle{width: listView.width - 20height: 32radius: 2border.color: "#777"color: hovered ? "#559EF2FA" : "#55556677"property bool hovered: falseMouseArea{anchors.fill: parenthoverEnabled: trueonEntered: parent.hovered = true;onExited: parent.hovered = false;onClicked:{scanner.stop();discoverCon.connectToName(name);fileTransfer.setAccessPoint(name);root.connected = true;root.connectName = name;accessPoints.clear();}}Text{anchors.centerIn: parenttext: qsTr(name)}}}
}...

在 MouseArea.onClicked 中( 即用户点击了某一个 name 时 ),将会进行:

1、scanner.stop() 停止扫描动画。

2、discoverCon.connectToName(name),这将会发送一个 UDP 数据报 [CONNECT]## + name

3、fileTransfer.setAccessPoint(name),其实现如下:

void FileTransfer::setAccessPoint(const QString &name)
{DiscoverConnection *dc = DiscoverConnection::instance();QHostAddress address = dc->getAddress(name);QThread *thread = new QThread;connect(thread, &QThread::finished, thread, &QThread::deleteLater);TransferSocket *socket = new TransferSocket;socket->moveToThread(thread);thread->start();m_socket = socket;QMetaObject::invokeMethod(m_socket, "setDestAddress", Q_ARG(QHostAddress, address));
}

这将会创建一个传输用的 TCP 连接,并运行在另一个独立的线程中,因此,最后一行使用 invokeMethod() 调用它的 setDestAddress() 来设置目的地址。

4、accessPoints.clear(),每次连接后将会清空上一次扫描到的用户。

接下来就是传输用的 TransferSocket。

transfersocket.h:

#ifndef TRANSFERSOCKET_H
#define TRANSFERSOCKET_H#include <QTcpSocket>
#include <QHostAddress>class QFile;
class TransferSocket : public QTcpSocket
{Q_OBJECTpublic:TransferSocket();~TransferSocket();void requestNewConnection();Q_INVOKABLE void setDestAddress(const QHostAddress &address);Q_INVOKABLE void sendFile(const QUrl &url);Q_INVOKABLE void writeToSocket(const QByteArray &data) { QTcpSocket::write(data); }signals:void hasError(const QString &error);public slots:void processRecvBlock();private:int m_maxRecvNum = 8;QString m_cachePath;QByteArray m_recvData;//可以用一个struct File { QFile *file; qint32 size; }QMap<QString, QFile *> m_recvFiles;QMap<QString, qint32> m_recvFileSize;QHostAddress m_destAddress;
};#endif // TRANSFERSOCKET_H

这里我用了两个 QMap 来存储接收文件的 QFile 指针和其大小,实际上用一个 QMap<QString, File*> 存储即可,File 在注释中提到,我就懒得改了( •́ὤ•̀)~

m_maxRecvNum 是用来设置同时接收文件数上限的( 即最多同时接收8个文件 ),然鹅只是我设想中的,实际并没有实现,有兴趣的可以自己实现它。

然后是 transfersocket.cpp:

#include "fileblock.h"
#include "filemanager.h"
#include "transfersocket.h"#include <QtConcurrent>
#include <QFile>
#include <QFileInfo>
#include <QQmlFile>const int maxBlockSize = 1024;TransferSocket::TransferSocket()
{m_cachePath = qApp->applicationDirPath() + "/FileRecv/";QDir dir;if (!dir.exists(m_cachePath)){dir.mkpath(m_cachePath);}connect(this, &QTcpSocket::readyRead, this, [this](){m_recvData += readAll();processRecvBlock();});
}TransferSocket::~TransferSocket()
{}void TransferSocket::requestNewConnection()
{abort();connectToHost(m_destAddress, 43800);waitForConnected(5000);
}void TransferSocket::setDestAddress(const QHostAddress &address)
{if (m_destAddress != address)m_destAddress = address;requestNewConnection();
}void TransferSocket::sendFile(const QUrl &url)
{if (state() != SocketState::ConnectedState)requestNewConnection();QtConcurrent::run([this, url](){QTime time;time.start();QFile file(QQmlFile::urlToLocalFileOrQrc(url));file.open(QIODevice::ReadOnly);qint32 offset = 0;qint32 totalSize = qint32(file.size());QString fileName = QFileInfo(QQmlFile::urlToLocalFileOrQrc(url)).fileName();while (offset < totalSize){file.seek(offset);QByteArray dataBlock = file.read(maxBlockSize);FileBlock block = { qint16(dataBlock.size()), offset, totalSize,fileName.toLocal8Bit(), dataBlock};QByteArray data;QDataStream out(&data, QIODevice::WriteOnly);out.setVersion(QDataStream::Qt_5_12);out << block;QMetaObject::invokeMethod(this, "writeToSocket", Q_ARG(QByteArray, data));offset += dataBlock.size();if (time.elapsed() >= 1000 || offset >= totalSize){time.restart();QMetaObject::invokeMethod(FileManager::instance(), "updateWriteFile",Q_ARG(QString, fileName), Q_ARG(int, offset));}}file.close();});
}void TransferSocket::processRecvBlock()
{static QTime time = QTime::currentTime();if (m_recvData.size() > 0){FileBlock block;QDataStream in(&m_recvData, QIODevice::ReadOnly);in.setVersion(QDataStream::Qt_5_12);in >> block;if (block.isEmpty())return;QString fileName = QString::fromLocal8Bit(block.fileName);if (!m_recvFiles[fileName]){QFile *file = new QFile(m_cachePath + fileName);file->open(QIODevice::WriteOnly);m_recvFiles[fileName] = file;m_recvFileSize[fileName] = 0;QMetaObject::invokeMethod(FileManager::instance(), "addReadFile",Q_ARG(QString, fileName), Q_ARG(int, block.fileSize));QThread::msleep(100);}if (m_recvFileSize[fileName] < block.fileSize){m_recvFileSize[fileName] += block.blockSize;m_recvFiles[fileName]->write(block.dataBlock);qDebug() << block;}if (m_recvFileSize[fileName] == block.fileSize){m_recvFiles[fileName]->close();m_recvFiles[fileName]->deleteLater();m_recvFiles.remove(fileName);m_recvFileSize.remove(fileName);QMetaObject::invokeMethod(FileManager::instance(), "updateReadFile",Q_ARG(QString, fileName), Q_ARG(int, block.fileSize));}if (time.elapsed() >= 1000){time.restart();QMetaObject::invokeMethod(FileManager::instance(), "updateReadFile",Q_ARG(QString, fileName), Q_ARG(int, m_recvFileSize[fileName]));}m_recvData.remove(0, block.size());if (m_recvData.size() > 0)  //如果还有则继续处理processRecvBlock();}
}

★ 构造函数中,初始化了接收文件的路径,并且,连接 QTcpSocket::readyRead 信号,将接收到的数据进行缓存并调用文件块处理程序(函数)。

这里,我用的文件块来传输某个文件的某一部分,它定义在 fileblock.h 中:

#ifndef FILEBLOCK_H
#define FILEBLOCK_H#include <QDebug>
#include <QtGlobal>
#include <QDataStream>struct FileBlock
{qint16 blockSize;qint32 offset;qint32 fileSize;QByteArray fileName;QByteArray dataBlock;bool isEmpty() const{return fileName.isEmpty() || dataBlock.isEmpty();}int size() const{return int(sizeof(blockSize)) +int(sizeof(offset)) +int(sizeof(fileSize)) +fileName.size() +dataBlock.size() +2 * 4;       //有两个QByteArray,每个会在前面加4字节大小}
};QDataStream& operator>>(QDataStream &in, FileBlock &block);
QDataStream& operator<<(QDataStream &out, FileBlock &block);
QDebug operator<<(QDebug debug, const FileBlock &block);#endif // FILEBLOCK_H

具体每个成员应该不用我说明了吧,见名知意,接着我们回到 TransferSocket:

★ requestNewConnection() 和 setDestAddress() 比较简单,就不说明了。

★ sendFile(),发送文件的函数,它接受一个本地文件的 URL,这个 URL 由 qml 传进来的,因此我比较喜欢使用 QQmlFile::urlToLocalFileOrQrc() 来进行解析。

1、QtConcurrent::run() 是 Qt 提供的高级并发 API,使用起来非常方便,这样,每个文件的发送将会使用一个单独的线程进行。

2、QTime 用来记录流逝的时间,如果过去了一秒,就更新文件进度,文件进度由文件管理器 FileManager 控制,它是一个单例。

3、QFile 保存了发送的文件,我们使用 seek() 设置文件偏移,然后读取 maxBlockSize ( 任意,我这里是1024 )字节的数据。

4、需要注意的是,lambda 函数运行在不同于 TransferSocket 的线程中,在 Qt Socket 中,不同的线程不能直接 Read / Write 的,因此,这里使用一个 Q_INVOKABLE 修饰的 writeToSocket() 函数来进行 socket write,记住使用 invokeMethod() 调用(或者加锁)。

★ processRecvBlock() 文件块处理函数。每当有数据到来时被调用。

1、首先我们尝试读取一个文件块,如果不为空( 即为完整的块 )。

2、然后我们将这个块中的数据写入对应文件中。

3、清空缓存,并且,如果流逝的时间超过一秒,就更新文件进度。

至此,文件传输部分结束。


 【结语】

好了,其实整个工具最关键的部分就是传输部分,然鹅我并没有花多少时间在这上面,反而在界面上花了不少时间( 有些本末倒置了呢 ),不过界面有些地方确实麻烦( qml 也没想象那么好用啊〒▽〒 )。

然后还有一些 ConnectionManager、FileManager 之类的很简单而且没多少关系就没有讲了,建议自己看代码了。

最后还有一些功能比如暂停/继续等等没有实现,不过都很容易,有兴趣可以自己玩玩~

最后,资源地址:QtQuick制作的文件传输器-C++文档类资源-CSDN下载

也可以访问项目地址:https://github.com/mengps/FileTransfer

Qt Quick实现的文件传输工具(TCP传输篇)相关推荐

  1. 基于QT开发PC端文件加密工具

    基于QT开发PC端文件加密工具 1.客户需求 某案子.该方案,通过外挂TF卡实现,本地教育资源的调取使用.按客户需求,把TF卡内的内容进行加密,并在软件中写好解密,确保资源文件只能在教育机中才能正常播 ...

  2. 杰理之文件打包工具使用【篇】

    打包工具位于 cpu\wl82\tools\packres, 打开packrec.bat文件, 按照说明添加需要打包的文件,如: REM packres.exe -n $(dir) -o $(outp ...

  3. Qt Quick 3D介绍:Qt Quick的高级3D API

    目录 我们的目标是什么?为什么提出另一个3D解决方案? 统一图形技术 直观易用的API Qt Quick的统一工具 一流的素材优化管道 跨平台性能和兼容性 Qt Quick 3D到底是什么? 您能用Q ...

  4. QT Quick 简介

    Qt Quick是一个用于帮助开发者设计直观,现代,流畅的用户界面的技术集,近年来被广泛应用于手机,媒体播放器,机顶盒和其他手提设备.Qt Quick中包含了大量的用户界面元素,及描述这些用户界面的陈 ...

  5. Qt Quick QML

    Qt Quick简介 Qt Quick是一个用于帮助开发者设计直观,现代,流畅的用户界面的技术集,近年来被广泛应用于手机,媒体播放器,机顶盒和其他手提设备.Qt Quick中包含了大量的用户界面元素, ...

  6. Qt Widgets、QML、Qt Quick的概念与区别

    1 QML 和 Qt Quick 是什么关系? 从概念上区分 QML 是一种用户界面规范和标记语言,它允许开发/设计人员创建高性能.流畅的动画和具有视觉吸引力的应用程序. 这里,主要涉及两点: 用户界 ...

  7. Qt Quick Controls 配置文件

    Qt Quick Controls 配置文件 Qt Quick Controls支持一个特殊的配置文件 qtquickcontrols2.conf,该文件内置于应用程序的资源中. 配置文件可以指定首选 ...

  8. 【Qt Quick】开启cmd控制台查看打印

    系统: win10 IDE: QT(Qt Quick) 在.pro文件中添加以下代码: CONFIG += console 再次运行程序时会同步开启一个cmd的控制台

  9. QML和Qt Quick

    什么是 QML? Qt Meta-Object Language,Qt元对象语言,是一种用于描述应用程序用户界面的声明式编程语言,使用一些可视组件以及这些组件之间的交互来描述用户界面.QML是一种高可 ...

最新文章

  1. mbed列--基于飞思卡尔FRDM KL25Z鼠标设计的高速实现
  2. 互联网思维-NO.1思维(1)
  3. 104. Maximum Depth of Binary Tree
  4. 软工网络15团队作业4——Alpha阶段敏捷冲刺之Scrum 冲刺博客(Day5)
  5. docker删除本地已下载的镜像
  6. 关于css 的AST 语法树的理解
  7. exc_bad_instruction(code=EXC_I386_INVOP,subcode=0x0) 错误
  8. 生成Oracle的AWR报告
  9. 陪跑 Android 十年,这家操作系统创业公司终于实现盈利!
  10. HDOJ 1106 排序
  11. Parallels Desktop 安装Win 10提示“安全启动功能防止操作系统启动”该如何操作?
  12. lockdir.exe(文件夹加密软件)重装系统后打不开的解决办法
  13. java编程选游戏本还是轻薄本,编程用什么笔记本,超薄本还是游戏本
  14. 迅雷总提示版权什么的。。
  15. mac brew安装/卸载
  16. 使用Jeb动态调试安卓的时候显示Could not attach to target
  17. 怎样玩魔方,魔方口诀,魔方玩法
  18. Struts 1与Struts 2区别
  19. opencv-python频繁报错尝试解决措施
  20. 六轴机器人控制系统软件设计

热门文章

  1. 需要知道的华为、锐捷、H3C交换机命令
  2. 中国石油大学(北京)-《中国近现代史纲要》第二阶段在线作业
  3. 使用latex画好看的深度学习模型图(基于Windows平台)
  4. 对前端工程化、模块化、组件化开发的理解
  5. 大白话教你认识 Kafka 背后优秀的架构设计
  6. 第十五周翻译-《Pro SQL Server Internals, 2nd edition》
  7. 「Adobe国际认证」关于Adobe Photoshop,创建和修改画笔教程?
  8. ASCII 与 UNICODE 字符映射表
  9. 计算机网络学习笔记(详尽版)
  10. CF赛后总结——一个自闭症患者的心路历程