Qt详解实现TCP文件传输例子(文件下载和上传)附源码

news2024/11/17 16:43:32

网络通信我们用的很频繁,如文字,语音,文件,图片等,这个些传输方式都差不多

QT文件传输主要考验对传输的控制,还是需要点逻辑的,文件传输的大致框架如下

先看一下简单例子实现的效果(界面有点丑,重点在于内容):

 

 接下来重点讲一下需要用到哪些东西:

1.数据流 QDataStream

通过数据流可以操作各种数据类型,包括类对象,存储到文件中数据可以还原到内存,对QDataStream不懂的,可以去看下我写的这个:QDataStream中 << 和 >> 输入输出重载的理解_qdatastream <<_只是个~小不点的博客-CSDN博客

在这个例子中用来封装传输消息类型,文件名,文件大小等数据

2. QTcpSocket QTcpServer

QTcpServer当服务器,QTcpSocket当客户端用,用来作为文件传输对象

3.一个服务器和一个客户端(这个例子目前仅支持单向连接),先从简单的一对一开始理解,扩展到一对多,多对多就容易点了

服务端界面如下:

需要一个自定义一个文件对象,存在下载的文件信息:如文件名,文件大小,已经接收的字节数等,如下所示

 

 接下来先将服务端的

定义一个枚举的消息类型,判断客户端想要哪些信息,文件信息还是文件数据

//消息类型
enum MsgType{
    FileInfo,   //文件信息,如文件名,文件大小等信息
    FileData,   //文件数据,即文件内容
};

先看下流程:

服务端接收到客户端连接后,监听客户端消息,如果收到客户端发送接收的消息类型是FileInfo,就发送文件信息给它。收到客户端发送接收的消息类型是FileData,就发送文件数据给它。

传输文件信息时,需要获取要发送的文件信息,如文件名,文件大小等,然后将这些信息发送给客户端。客户端处理存储这些信息即可,接收文件数据的时候需要用到

 具体实现如下

void FileServer::transferFileInfo(QTcpSocket *socket)
{
    //获取文件数据,准备发送
    QByteArray  DataInfoBlock = getFileContent(ui->fileEdit->text());

    QThread::msleep(10); //添加延时
    m_fileInfoWriteBytes = socket->write(DataInfoBlock) - typeMsgSize;
    qDebug()<< "传输文件信息,大小:"<< m_sendFileSize;
    //等待发送完成,才能继续下次发送,否则发送过快,对方无法接收
    if(!socket->waitForBytesWritten(10*1000)) {
        ui->textBrowser->append(QString("网络请求超时,原因:%1").arg(socket->errorString()));
        return;
    }

    ui->textBrowser->append(QString("文件信息发送完成,开始对[%1]进行文件传输------------------")
                        .arg(socket->localAddress().toString()));
    qDebug()<<"当前文件传输线程id:"<<QThread::currentThreadId();

    m_localFile.setFileName(m_sendFilePath);
    if(!m_localFile.open(QFile::ReadOnly)){
        ui->textBrowser->append(QString("文件[%1]打开失败!").arg(m_sendFilePath));
        return;
    }
}

QByteArray FileServer::getFileContent(QString filePath)
{
    if(!QFile::exists(filePath)) {
        ui->textBrowser->append(QString("没有要传输的文件!" + filePath));
        return "";
    }
    m_sendFilePath = filePath;
    ui->textBrowser->append(QString("正在获取文件信息[%1]......").arg(filePath));
    QFileInfo info(filePath);

    //获取要发送的文件大小
    m_sendFileSize = info.size();

    ui->textBrowser->append(QString("要发送的文件大小:%1字节,%2M").arg(m_sendFileSize).arg(m_sendFileSize/1024/1024.0));

    //获取发送的文件名
    QString currentFileName=filePath.right(filePath.size()-filePath.lastIndexOf('/')-1);
    QByteArray DataInfoBlock;

    QDataStream sendOut(&DataInfoBlock,QIODevice::WriteOnly);
    sendOut.setVersion(QDataStream::Qt_5_12);
    int type = MsgType::FileInfo;
    //封装发送的信息到DataInfoBlock中
    sendOut<<int(type)<<QString(currentFileName)<<qint64(m_sendFileSize);

    ui->textBrowser->append(QString("文件[%1]信息获取完成!").arg(currentFileName));
    //发送的文件总大小中,信息类型不计入
    QString msg;
    if(m_sendFileSize>1024*1024) {
        msg = QString("%1M").arg(m_sendFileSize/1024/1024.0);
    }
    else {
        msg = QString("%1KB").arg(m_sendFileSize/1024.0);
    }
    ui->textBrowser->append(QString("发送的文件名:%1,文件大小:%2").arg(currentFileName).arg(msg));

    return DataInfoBlock;
}

关键在于发送的序列信息封装,依次为:

    QByteArray DataInfoBlock;

    QDataStream sendOut(&DataInfoBlock,QIODevice::WriteOnly);
    sendOut.setVersion(QDataStream::Qt_5_12);
    int type = MsgType::FileInfo;
    //封装发送的信息到DataInfoBlock中
        //消息类型             文件名                  //文件大小
    sendOut<<int(type)<<QString(currentFileName)<<qint64(m_sendFileSize);

发送给客户端即可,客户端解析的时候也是按照这个顺序依次解析

发送给客户端后,等待客户端的下一步指令即可。客户端接收到文件信息后,都会发送获取文件数据的消息,服务器即可进行文件传输

变量qint64 payloadSize用来控制每次文件读取的字节数,progressByte用来存储发送的进度,我这里一次只发送1024个字节,因为区域网传输太快了,文明小传输过程不明显,你们可以调大小,比如1024*64个字节

接着就用while循环控制发送流程,直到发送的字节数等于文件的大小,说明文件数据发送完成

在循环中,要添加几微秒的延时来防止发送的文件帧过快,客户端接收不过来,导致丢包

传输文件的代码如下: 

void FileServer::transferFileData(QTcpSocket *socket)
{

    qint64 payloadSize = 1024*1; //每一帧发送1024*64个字节,控制每次读取文件的大小,从而传输速度

    double progressByte= 0;//发送进度
    qint64 bytesWritten=0;//已经发送的字节数

    while(bytesWritten != m_sendFileSize) {
        double temp = bytesWritten/1.0/m_sendFileSize*100;
        int  progress = static_cast<int>(bytesWritten/1.0/m_sendFileSize*100);
        if(bytesWritten<m_sendFileSize){

            QByteArray DataInfoBlock = m_localFile.read(qMin(m_sendFileSize,payloadSize));

            qint64 WriteBolockSize = socket->write(DataInfoBlock, DataInfoBlock.size());

            //QThread::msleep(1); //添加延时,防止服务端发送文件帧过快,否则发送过快,客户端接收不过来,导致丢包
            QThread::usleep(3); //添加延时,防止服务端发送文件帧过快,否则发送过快,客户端接收不过来,导致丢包
            //等待发送完成,才能继续下次发送,
            if(!socket->waitForBytesWritten(3*1000)) {
                ui->textBrowser->append("网络请求超时");
                return;
            }
            bytesWritten += WriteBolockSize;
            ui->sendProgressBar->setValue(progress);
        }

        if(bytesWritten==m_sendFileSize){
            //LogWrite::LOG_DEBUG(QString("当前更新进度:100%,发送总次数:%1").arg(count), "server_"+socket->localAddress().toString());
            ui->textBrowser->append(QString("当前上传进度:%1/%2 -> %3%").arg(bytesWritten).arg(m_sendFileSize).arg(progress));
            ui->textBrowser->append(QString("-------------对[%1]的文件传输完成!------------------").arg(socket->peerAddress().toString()));
            ui->sendProgressBar->setValue(100);
            m_localFile.close();
            return;
        }
        if(bytesWritten > m_sendFileSize) {
            ui->textBrowser->append("意外情况!!!");
            return;
        }

        if(bytesWritten/1.0/m_sendFileSize > progressByte) {
            ui->textBrowser->append(QString("当前上传进度:%1/%2 -> %3%").arg(bytesWritten).arg(m_sendFileSize).arg(progress));
            progressByte+=0.1;
        }

    }

}

服务端的核心代码讲完了,接下来将客户端的代码,界面如下:

先定义一个文件类对象,用来存储接收文件的对象,每个下载的文件就是一个文件类对象

 当连接上服务器后,点击下载文件后,客户端会先发送获取文件的消息

 服务端收到后,就会获取文件信息(流程上面讲过了),将文件信息发送给客户端

然后客户端根据服务器发送的消息类型处理消息

 收到服务器发送的文件信息消息后,进行读取,获取文件名,文件大小,用文件类对象进行存储,新建准备写入一个要下载的文件,准备工作完成后,向服务器发送获取文件数据的消息

 然后设置下载标识为true

bool isDownloading; //是否正在下载标识

标识接下来接下来收到的将全是文件数据,接收即可,直到文件全部接收完成,在将其设为false

文件数据接收的代码流程这样子:

 实现代码如下:

void FileManager::fileDataRead()
{
    qint64 readBytes = m_tcpSocket->bytesAvailable();
    if(readBytes <0) return;

    int progress = 0;
    // 如果接收的数据大小小于要接收的文件大小,那么继续写入文件
    if(myFile->bytesReceived < myFile->fileSize) {
        // 返回等待读取的传入字节数
        QByteArray data = m_tcpSocket->read(readBytes);
        myFile->bytesReceived+=readBytes;
        ui->textBrowser->append(QString("接收进度:%1/%2(字节)").arg(myFile->bytesReceived).arg(myFile->fileSize));
        progress =static_cast<int>(myFile->bytesReceived*100/myFile->fileSize);
        myFile->progressByte = myFile->bytesReceived;
        myFile->progressStr = QString("%1").arg(progress);
        ui->progressBar->setValue(progress);
        myFile->localFile.write(data);
    }

    // 接收数据完成时
    if (myFile->bytesReceived==myFile->fileSize){
        ui->textBrowser->append(tr("接收文件[%1]成功!").arg(myFile->fileName));
        progress = 100;
        myFile->localFile.close();

        ui->textBrowser->append(QString("接收进度:%1/%2(字节)").arg(myFile->bytesReceived).arg(myFile->fileSize));
        myFile->progressByte = myFile->bytesReceived;
        ui->progressBar->setValue(progress);
        isDownloading = false;
        myFile->initReadData();
    }

    if (myFile->bytesReceived > myFile->fileSize){
        qDebug()<<"myFile->bytesReceived > m_fileSize";
    }
}

最终就达到这个效果啦:

所有源代码在这里,只有15kB,直接下载就行了

链接: https://pan.baidu.com/s/1xOuNstwxgEbRbzqQdJA2Aw?pwd=8888 提取码: 8888

总结:这个文件传输的例子完成了文件传输的基本流程,基本传输能完成。但是如果要实现文件上传到服务器,并同时下载文件,就需要多线程了,同时,对编码能力也是个提升。界面可以做得好看点,获取到服务器文件列表后,可以选择下载,上传文件,删除文件等操作。难度会瞬间上升。

如下:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/634348.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python】使用pdf2docx库将.pdf转化为.docx

1、使用VS code搭建Python编译环境 2、安装pdf2doc库1 pip install pdf2docx3、编写代码 3.1 使用parse将pdf转化为docx 编写 pdf2docxParse.py from pdf2docx import parse # 文件名 pdf_file demo-image-overlap.pdf docx_file demo-image-overlap.docx# 将pdf转为doc…

vue 3 第三十六章:vite环境变量(.env文件的配置及使用)

文章目录 1. 环境变量使用场景2. 创建.env文件3. 在应用程序中使用环境变量3.1. 输出结果&#xff08;开发环境&#xff09;&#xff1a;3.2. 输出结果&#xff08;生产环境&#xff09;&#xff1a; 4. 在 vite 中使用环境变量 1. 环境变量使用场景 区分不同的环境。在Vite中…

【LeetCode】160. 相交链表

160. 相交链表&#xff08;简单&#xff09; 题解 对于这道题&#xff0c;首先要理解题意&#xff1a;如果两个单链表会相交&#xff0c;那么返回相交的起始节点&#xff1b;否则返回 null。 无需看“自定义评测”部分。 假设链表 A 的头节点到相交点的距离为 a&#xff0c;链…

S32K324芯片学习笔记-Clock

文章目录 Clock详解特征时钟生成MC_CGM mux 0时钟时钟输出概述其他时钟整体框图时钟源芯片时钟源芯片输入时钟芯片输出时钟FIRC快速内部RC振荡器待机模式下的FIRC_CLK行为 SIRC慢速内部RC振荡器待机模式下的SIRC行为 FXOSC快速外部晶振慢速外部晶振PLL锁相环配置PLL配置序列 芯…

QPushButton 基本使用

〇、PyQt 中的 Button 相关类介绍 PyQt 提供了多种按钮类&#xff0c;用于创建各种类型的按钮。这一部分将打算介绍一下 PyQt 各种 Button 及相关类&#xff0c;并扩展 Button 的功能用法&#xff0c;让你能够打造自己的个性化 Button 。在下面&#xff0c;我先来介绍 PyQt 中…

数据库系统理论 -- 关系查询处理和查询优化

本篇文章会先介绍数据库的查询处理&#xff0c;然后介绍数据库的查询优化。其中查询优化分为代数优化和物理优化。代数优化是指关系表达式的优化&#xff0c;物理优化是指通过存取路径和底层操作算法的选择进行优化。 查询处理 查询分析查询检查查询优化查询执行 查询分析 …

JNI开发Tips

异常的检测和打印&#xff1a; c中Native代码调用JNI的时候如果产生了异常不会展开原生堆栈&#xff1a; 所以在cJNI调用的时候构造一个FindClass时找不到类的异常&#xff0c;我们看到的实际的崩溃堆栈会是下面的样子&#xff0c;看不到c层代码的调用链路&#xff1a; 在JNI…

【Reids】搭建主从集群以及主从数据同步原理

目录 一、搭建主从集群 1、介绍 2、搭建 二、数据同步原理 1、全量同步 2、主节点如何判断是不是第一次连接 3、增量同步 4、优化主从数据同步 一、搭建主从集群 1、介绍 单节点的Redis并发能力存在上限&#xff0c;要提高并发能力就需要搭建主从集群&#xff0c;实现…

WinDbg安装入坑2(C#)

由于作者水平有限&#xff0c;如有写得不对的地方&#xff0c;请指正。 使用WinDbg的过程中&#xff0c;坑特别的多&#xff0c;对版本要求比较严格&#xff0c;如&#xff1a; 1 32位应用程序导出的Dump文件要用32位的WinDbg打开&#xff0c;想要没有那么多的问题&#xf…

数据库存储过程和函数

MySQL存储过程和存储函数 MySQL中提供存储过程&#xff08;procedure&#xff09;与存储函数&#xff08;function&#xff09;机制&#xff0c;我们先将其统称为存储程序&#xff0c;一般的SQL语句需要先编译然后执行&#xff0c;存储程序是一组为了完成特定功能的SQL语句集&…

惊人!截至6月10日全球COVID-19疫情玫瑰图,这些国家最危险

一、引言 自从COVID-19疫情在2020年爆发以来&#xff0c;数据可视化成为了了解疫情趋势和规模的重要手段。饱受争议的疫情数据可视化中的南丁格尔玫瑰图&#xff08;Rose Chart&#xff09;&#xff0c;由于具有简洁、直观、易于理解等特点&#xff0c;逐渐成为了一个备受欢迎的…

MySQL中索引失效的场景

1.对索引使用左或者左右模糊匹配 当我们使用左或者左右模糊匹配的时候&#xff0c;也就是 like %xx 或者 like %xx% 这两种方式都会造成索引失效。 比如下面的 like 语句&#xff0c;查询 name 后缀为「林」的用户&#xff0c;执行计划中的 typeALL 就代表了全表扫描&#xf…

什么是SOME/IP-SD?

SOME/IP-SD 是"Scalable service-Oriented MiddlewarE over IP - Service Discovery"的缩写&#xff0c;是SOME/IP的一种特殊报文&#xff0c;可以让Client知道Server可以提供哪些服务&#xff0c;SOME/IP有两种动态发现服务的机制&#xff1a;一种是Offer Service&a…

Javascript作用域 (局部作用域和全局作用域) 详细介绍

Javascript作用域 (局部作用域和全局作用域) 详细介绍 作用域是当前的执行上下文&#xff0c;值和表达式在其中“可见”或可被访问。 常见的作用域为&#xff1a; 全局作用域&#xff1a;脚本模式运行所有代码的默认作用域 函数作用域&#xff1a;由函数创建的作用域 局部作用域…

IP-Guard上传软件到软件中心服务器时,软件ID、显示名称等信息如何获取?

如何实现客户端只能从软件中心下载安装软件? 控制台设置禁止全部软件安装的软件管理策略即可。即使设置禁止,软件中心安装不会受影响的。 在控制台-策略-软件安装管理策略,勾选禁止全部软件的安装。 软件中心客户端下载安装软件后,下载安装包是否会自动删除? 1、http方式…

SpringBoot Actuator详解(四十八)

还是要开心的&#xff0c;万一梦想真得实现了呢 上一章简单介绍了SpringBoot日志配置(四十七) , 如果没有看过,请观看上一章 本章节详细参考了: https://www.cnblogs.com/caoweixiong/p/15325382.html Spring Boot Actuator 模块提供了生产级别的功能&#xff0c;比如健康检查…

.locked加密勒索数据库级别恢复---惜分飞

有客户数据库被加密成.locked结尾的扩展名,数据库无法正常使用 对应的READ_ME1.html文件中信息类似:send 0.1btc to my address:bc1ql8an5slxutu3yjyu9rvhsfcpv29tsfhv3j9lr4. contact email:servicehellowinter.online,if you can’t contact my email, please contact some d…

Leetcode-6425. 找到最长的半重复子字符串

题目描述 给你一个下标从 0 开始的字符串 s &#xff0c;这个字符串只包含 0 到 9 的数字字符。 如果一个字符串 t 中至多有一对相邻字符是相等的&#xff0c;那么称这个字符串是 半重复的 。 请你返回 s 中最长 半重复 子字符串的长度。 一个 子字符串 是一个字符串中一段…

Redis第十章 Redis HyperLogLog与事务、Redis 7.0前瞻

HyperLogLog HyperLogLog(Hyper[ˈhaɪpə])并不是一种新的数据结构(实际类型为字符串类型)&#xff0c;而是一种基数算法,通过 HyperLogLog 可以利用极小的内存空间完成独立总数的统计&#xff0c;数据集可以是 IP、Email、ID 等。 如果你的页面访问量非常大&#xff0c;比如…

JUC基础认识(2)

线程池(重点)&#xff1a;3大方法&#xff0c;7大参数&#xff0c;4种拒绝策略 程序运行的本质:占用系统资源&#xff01;优化资源的使用&#xff01;----->池化技术 池化技术的好处: 1.降低资源的消耗 2.提高响应速度 3.方便管理 线程复用&#xff0c;可以控制最大…