Bootstrap

Qt07-TCP文件传输

一、文件传输服务器端

  1. 服务器端前端设置
    在这里插入图片描述
  2. pro文件中添加配置 QT += network CONFIG+= C++11
  3. 头文件中定义变量
    serverwidget.h
    QTcpServer *tcpServer;  // 监听套接字
    QTcpSocket *tcpSocket;  // 通信套接字
    
    QFile file;             // 传输的文件对象
    QString fileName;       // 文件名字
    qint64  fileSize;       // 文件大小
    qint64 sendSize;        // 已经发送的文件的大小
    
    QTimer timer;           // 定时器
    void sendData();    // 发送文件数据
    
  4. 文件传输方法的实现
    先设置TCPServer和TCPSocket通信的连接
    添加文件选择对话框,读取所选文件的信息(文件名以及大小)
    文件传输,先传头部信息,为了防止粘包使用定时器在头信息传输成功后再传输文件内容
    ServerWidget::ServerWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::ServerWidget)
    {
        ui->setupUi(this);
    
        // 初始化时候两个按钮应该是无效的
        ui->buttonSend->setEnabled(false);
        ui->buttonFile->setEnabled(false);
    
        // 监听套接字
        tcpServer = new QTcpServer(this);
    
        // 监听
        tcpServer->listen(QHostAddress::Any, 8888);
        setWindowTitle("服务端口为:8888");
    
        // 如果客户端和服务器连接成功,tcpServer会自动触发 newConnection()
        connect(tcpServer, &QTcpServer::newConnection,
                [=](){
            // 获取连接好的套接字
            tcpSocket = tcpServer->nextPendingConnection();
            // 获取对方的ip好端口
            QString ip = tcpSocket->peerAddress().toString();
            quint16 port = tcpSocket->peerPort();
            QString str = QString("[%1, %2] 成功连接").arg(ip).arg(port);
            ui->textEdit->append(str);
    
            // 连接成功后启用文件选择按钮
            ui->buttonFile->setEnabled(true);
        });
    
        connect(&timer, &QTimer::timeout, [=](){
            // 关闭定时器
            timer.stop();
    
            sendData();
        });
    }
    
    ServerWidget::~ServerWidget()
    {
        delete ui;
    }
    
    // 选择文件的按钮
    void ServerWidget::on_buttonFile_clicked()
    {
        QString filePath = QFileDialog::getOpenFileName(this, "open", "../");
    
        if(false == filePath.isEmpty()){   // 如果选择的文件有效
            // 获取信息前应当先清空
            fileName.clear();
            fileSize = 0;
    
            // 获取文件信息(以只读的方式打开)
            QFileInfo info(filePath);
            fileName = info.fileName();
            fileSize = info.size();
    
            sendSize = 0;   // 发送文件的大小
    
            // 以只读的方式打开文件
            file.setFileName(filePath);        // 指定文件名字
            bool isOk = file.open(QIODevice::ReadOnly); // 只读方式打开文件
    
            if(false == isOk){
                ui->textEdit->append("只读方式打开文件失败 70");
            }
            else{
                ui->textEdit->append(QString("打开的文件是: %1").arg(filePath));
                ui->buttonSend->setEnabled(true);
            }
    
        }else{
            qDebug() << "选择文件路径出错 74";
        }
    }
    
    // 发送文件按钮
    void ServerWidget::on_buttonSend_clicked()
    {
        // 先发送文件头信息   文件名 ## 文件大小
        QString head = QString("%1##%2").arg(fileName).arg(fileSize);
        qint64 len = tcpSocket->write(head.toUtf8());   // 返回发送了多少信息
        if(len > 0){    // 头部信息发送成功
            // 发送真正的文件信息
            // 防止TCP粘包问题, 需要通过定时器延时20ms
            timer.start(20);
    
        }else{
            ui->textEdit->append("头部信息发送失败");
            file.close();
            ui->buttonSend->setEnabled(false);
        }
    }
    
    
    void ServerWidget::sendData()
    {
        qint64 len = 0;
        do{
            // 每次发送4k的数据
            char buf[4*1024] = {0};
            len = 0;
    
            // 读取file中数据,往buf中写数据,返回写入的数据长度
            len = file.read(buf, sizeof(buf));
            tcpSocket->write(buf, len);
    
            // 发送数据要累加
            sendSize += len;
        }while(len > 0);
    
        // 判断是否发送文件完毕
        if(sendSize == fileSize){
            ui->textEdit->append("文件发送完毕\n\n\n");
            file.close();
    
            // 与客户端连接关闭
            tcpSocket->disconnectFromHost();
            tcpSocket->close();
        }
    }
    

二、文件传输客户端

  1. 前端设计
    在这里插入图片描述

  2. clientwidget.cpp

#include "clientwidget.h"
#include "ui_clientwidget.h"

#include <QTcpSocket>
#include <QMessageBox>
#include <QHostAddress>

ClientWidget::ClientWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::ClientWidget)
{
    ui->setupUi(this);

    isHead = true;
    tcpSocket = new QTcpSocket(this);

    connect(tcpSocket, &QTcpSocket::readyRead,
        [=](){
        // 取出接收的内容
        QByteArray buf = tcpSocket->readAll();

        if(true == isHead){     // 头信息
            isHead = false;
            // 解析头部信息 buf = "文件名 ## 大小"
            fileName = QString(buf).section("##", 0, 0);    // 分割字符串
            fileSize = QString(buf).section("##", 1, 1).toInt();
            receiveSize = 0;
            ui->textEdit->append(buf);
            // 打开文件
            file.setFileName(fileName);
            bool isOK = file.open(QIODevice::WriteOnly);
            if(false == isOK){
                ui->textEdit->append("文件写入失败");

                tcpSocket->disconnectFromHost();
                tcpSocket->close();
                return ;
            }
            // 设置进度条
            ui->progressBar->setMinimum(0);
            ui->progressBar->setMaximum(fileSize/1024);
            ui->progressBar->setValue(0);

        }else{  // 文件信息
            qint64 len = file.write(buf);
            if(len > 0){
                receiveSize += len;
                ui->progressBar->setValue(receiveSize/1024);
            }

            if(receiveSize == fileSize){
                file.close();
                ui->textEdit->append("文件传输完成");
                QMessageBox::information(this, "完成", "文件接收完成");

                tcpSocket->disconnectFromHost();
                tcpSocket->close();
                isHead = true;
            }
        }
    });
}

ClientWidget::~ClientWidget()
{
    delete ui;
}

void ClientWidget::on_pushButton_clicked()
{
    // 获取服务器的ip和端口
    QString ip = ui->ipText->text();
    qint16 port = ui->portText->text().toInt();

    tcpSocket->connectToHost(QHostAddress(ip), port);
}
;