• Qt实战2.老生常谈的文件传输


    1 需求描述

    1. 实现点对点的文件传输功能;
    2. 可以批量传输文件。

    2 设计思路

    说到文件的传输当然使用QTcpSocket,思路还是蛮简单的,发送端维护一个文件队列,然后再将队列中的文件逐个传输到服务端,服务端使用QTcpServer进行监听,并逐个接收文件。
    为了实现文件名的统一,客户端每次发送新文件时需要先发送文件名以及文件的大小,这样服务端才能做好后续处理。

    3 代码实现

    3.1 服务端(接收端)

    服务端处理过程:打开监听->处理连接->接收数据->文件落盘

    1. 服务端首先打开端口监听,以便处理客户端的连接请求,相关代码如下:
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow),
        m_pSocket(nullptr),
        m_fileSize(0),
        m_fileBytesReceived(0)
    {
        ui->setupUi(this);
    
        setWindowTitle(QApplication::applicationName() + QStringLiteral(" Qt小罗"));
        qApp->setStyle(QStyleFactory::create("fusion"));
    
        if (m_server.listen()) {
            ui->statusBar->showMessage(QStringLiteral("状态:正在监听!"));
        } else {
            ui->statusBar->showMessage(QStringLiteral("状态:监听失败!"));
        }
        ui->labelListenPort->setText(QString::number(m_server.serverPort()));
    
        connect(&m_server, &QTcpServer::newConnection, this, &MainWindow::onNewConnection);
        connect(ui->pushButtonCancel, &QPushButton::clicked, this, &MainWindow::close);
    }
    
    void MainWindow::onNewConnection()
    {
        m_pSocket = m_server.nextPendingConnection();
    
        connect(m_pSocket, &QTcpSocket::disconnected, m_pSocket, &QObject::deleteLater);
        connect(m_pSocket, &QIODevice::readyRead, this, &MainWindow::onReadyRead);
        connect(m_pSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError)));
    
        m_inStream.setDevice(m_pSocket);
        m_inStream.setVersion(QDataStream::Qt_5_0);
    }
    
    1. 然后接收客户端的数据,先接收文件名和文件大小信息,然后接收文件的二进制数据,接收代码如下:
    void MainWindow::onReadyRead()
    {
        if (0 == m_fileSize && m_pSocket->bytesAvailable() > sizeof(qint64)) {
            m_inStream >> m_fileSize >> m_fileName;
            m_file.setFileName(m_fileName);
            if (!m_file.open(QIODevice::WriteOnly)) {
                qCritical() << m_file.errorString();
                return;
            }
            ui->plainTextEditLog->appendPlainText(QStringLiteral("正在接收【%1】 ...").arg(m_fileName));
        }
    
        qint64 size = qMin(m_pSocket->bytesAvailable(), m_fileSize - m_fileBytesReceived);
    
        QByteArray arry(size, 0);
        m_inStream.readRawData(arry.data(), size);
        m_file.write(arry);
    
        m_fileBytesReceived += size;
    
        if (m_fileBytesReceived == m_fileSize) {
            m_file.close();
            QFileInfo info(m_fileName);
            ui->plainTextEditLog->appendPlainText(QStringLiteral("成功接收【%1】 -> %2").arg(m_fileName).arg(info.absoluteFilePath()));
            reset();
        }
    }
    

    到这里,服务端已准备就绪,随时准备接收客户端的连接请求。

    3.2 客户端(发送端)

    客户端处理过程:选择文件列表->连接服务端->连接建立后自动逐个打开队列中的文件并发送

    1. 文件选择后,点击发送按钮,连接服务端,相关代码如下:
    void MainWindow::sendFile()
    {
        QString address = ui->lineEditIpAddress->text();
        int port = ui->spinBoxPort->text().toInt();
    
        QHostAddress hostAddress;
        if (!hostAddress.setAddress(address)) {
            QMessageBox::critical(this, QStringLiteral("错误"), QStringLiteral("目标网络地址错误!"));
            return;
        }
    
        if (0 == ui->listWidget->count()) {
            QMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("请选择需要发送的文件!"));
            addFile();
            return;
        }
    
        m_fileQueue.clear();
        int count = ui->listWidget->count();
        for (int i = 0; i < count; ++i) {
            QString file = ui->listWidget->item(i)->text();
            m_fileQueue.append(file);
    
            QFileInfo info(file);
            m_totalFileSize += info.size();
        }
    
        m_socket.connectToHost(address, port);
    }
    
    1. 与服务端的连接建立后,客户端socket状态改变发出信号,对应的槽函数内调用send自动发送文件,相关代码如下:
    void MainWindow::onSocketStateChanged(QAbstractSocket::SocketState state)
    {
        switch (state) {
        case QAbstractSocket::UnconnectedState:
            qDebug() << m_totalFileSize << " " << m_totalFileBytesWritten;
            qDebug() << __FUNCTION__ << "QAbstractSocket::UnconnectedState";
            break;
        case QAbstractSocket::HostLookupState:
            qDebug() << __FUNCTION__ << "QAbstractSocket::HostLookupState";
            break;
        case QAbstractSocket::ConnectingState:
            qDebug() << __FUNCTION__ << "QAbstractSocket::ConnectingState";
            break;
        case QAbstractSocket::ConnectedState:
            qDebug() << __FUNCTION__ << "QAbstractSocket::ConnectedState";
            m_timer.restart();
            send();
            break;
        case QAbstractSocket::BoundState:
            break;
        case QAbstractSocket::ClosingState:
            qDebug() << __FUNCTION__ << "QAbstractSocket::ClosingState";
            break;
        case QAbstractSocket::ListeningState:
            break;
        default:
            break;
        }
    }
    
    void MainWindow::send()
    {
        m_file.setFileName(m_fileQueue.dequeue());
    
        if (!m_file.open(QIODevice::ReadOnly)) {
            qCritical() << m_file.errorString();
            QMessageBox::critical(this, QStringLiteral("错误"), m_file.errorString());
            return;
        }
    
        m_currentFileSize = m_file.size();
    
        //设置当前文件进度显示格式
        ui->currentProgressBar->setFormat(QStringLiteral("%1 : %p%").arg(m_file.fileName()));
    
        m_outStream.setDevice(&m_socket);
        m_outStream.setVersion(QDataStream::Qt_5_0);
    
        QFileInfo info(m_file.fileName());
        QString fileName = info.fileName();
    
        //发送文件大小及文件名
        m_outStream << m_currentFileSize << fileName;
    
        //开始传输文件
        QByteArray arry = m_file.read(m_blockSize);
        int size = arry.size();
        m_outStream.writeRawData(arry.constData(), size);
    
        ui->pushButtonSend->setEnabled(false);
        updateProgress(size);
    }
    
    1. 客户端每次发送数据后,socket会发出bytesWritten信号,通过该信号进行循环发送,直到文件发送完毕,对应的槽函数如下:
    void MainWindow::onBytesWritten(const qint64 &bytes)
    {
        Q_UNUSED(bytes)
    
        QByteArray arry = m_file.read(m_blockSize);
        if (arry.isEmpty()) {
            reset();
            return;
        }
    
        int size = arry.size();
        m_outStream.writeRawData(arry.constData(), size);
    
        updateProgress(size);
    }
    
    void MainWindow::reset()
    {
        m_file.close();
        ui->pushButtonSend->setEnabled(true);
    
        m_currentFileBytesWritten = 0;
    
        if (m_fileQueue.isEmpty()) {
            m_socket.close();
    
            qint64 milliseconds = m_timer.elapsed();
            QMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("共耗时:%1 毫秒  平均:%2 KB/s")
                                     .arg(QString::number(milliseconds))
                                     .arg(QString::number(((double(m_totalFileSize) / 1024) / (double(milliseconds) / 1000)), 'f', 3)));
            m_totalFileSize = 0;
            m_totalFileBytesWritten = 0;
    
        } else {
            send();
        }
    }
    

    到此,客户端已经具备批量发送文件的能力了。

    4 总结

    理清思路后,用Qt实现文件传输功能还是很简单的。当然如果需要的话,也可以让服务端单独启动线程接收文件,这样客户端就可以多个文件同时发送,服务端多个文件同时接收,这样效率貌似会更高,这算是一个拓展吧,不管怎样理清设计思路才是根本所在。

    由于文件传输过程中会进行界面显示处理,性能可能会丢失一部分,如果将本例子程序改为纯后台的,效率应该会高一些。

    5 下载

    完整代码

  • 相关阅读:
    xml解析
    File
    IO操作
    Json解析
    JNI字段描述符
    Android JNI get Context
    快速排序
    Android Scroller与computeScroll的调用机制关系
    Android 更新视图函数ondraw() 和dispatchdraw()的区别
    Android Studio 两个包里的类冲突
  • 原文地址:https://www.cnblogs.com/luoxiang/p/13452889.html
Copyright © 2020-2023  润新知