赞
踩
本篇为传输篇。
接上篇。
在上一篇中,我们已经扫描到了整个局域网中运行了本工具的用户,以 <name, ip> 的形式存储在 DiscoverConnection 中,并且,它是一个单例对象。
在 qml 中,这些 name 还会存储在一个 ListModel 中:
- .
- .
- .
- ListView
- {
- id: listView
- clip: true
- anchors.top: apLabel.bottom
- anchors.topMargin: 10
- anchors.bottom: parent.bottom
- anchors.left: parent.left
- anchors.leftMargin: 5
- anchors.right: parent.right
- spacing: 4
- ScrollBar.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 - 20
- height: 32
- radius: 2
- border.color: "#777"
- color: hovered ? "#559EF2FA" : "#55556677"
- property bool hovered: false
-
- MouseArea
- {
- anchors.fill: parent
- hoverEnabled: true
- onEntered: 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: parent
- text: 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_OBJECT
-
- public:
- 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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。