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

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

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

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

如下:

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【伏羲八卦图】(PythonMatlab实现)

目录 1 与达尔文对话 2 与老子对话 2.1 Python实现 2.2 Matlab实现 1 与达尔文对话 140年前&#xff0c;1858年7月1日&#xff0c;达尔文在英伦岛发表了自己有关自然选择的杰出论文。他提出&#xff0c;生物的发展规律是物竞天择。经过物竞&#xff0c;自然界选择并存留最具…

k8s实践之mysql集群搭建(十五)

先下载 k8s实践之mysql集群搭建资料 主从模式简介&#xff1a; 当master主服务器上的数据发生改变时&#xff0c;则将其改变写入二进制&#xff08;binlog&#xff09;事件日志文件中&#xff1b; slave从服务器会在一定时间间隔内对master主服务器上的二进制日志进行探测&am…

【算法题解】38. 括号的生成

这是一道 中等难度 的题 https://leetcode.cn/problems/generate-parentheses/ 题目 数字 n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;["…

废柴日记8:从入门到入狱的Python爬虫学习笔记1(入门篇)

前言&#xff1a;我错了&#xff0c;但下次也不一定(●’◡’●) 米娜桑&#xff0c;好久不见&#xff0c;不知道这段时间各位手中的西瓜刀有没有按时擦亮呢&#xff1f; 我也是在摸爬滚打将近一年之后总算是找到了一点人生的方向所以当成救命稻草现在正死死握紧不放手的啊。…

java八股文-并发篇

并发篇 1. 线程状态 要求 掌握 Java 线程六种状态掌握 Java 线程状态转换能理解五种状态与六种状态两种说法的区别 六种状态及转换 分别是 新建 当一个线程对象被创建&#xff0c;但还未调用 start 方法时处于新建状态此时未与操作系统底层线程关联 可运行 调用了 start …

2023全国计算机二级考试时间(全年各阶段考试时间安排)

2023全国计算机二级考试时间(全年各阶段考试时间安排) 2023年全国计算机二级考试时间分别为&#xff1a;3月25日至27日(上半年3月)、9月23日至25日(下半年9月)。 其中3月和9月开考全部级别全部科目&#xff0c;5月和12月考试开考一、二级全部科目&#xff0c;各省级承办机构可根…

SQL锁总结

一、概述 介绍 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;除传统的计算资源(CPU、RAM、I/O)的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题&#xff0c;锁…

12 VI——变分推断

文章目录 12 VI——变分推断12.1 背景介绍12.2 Classical VI12.2.1 公式导出12.2.2 坐标上升法 12.3 SGVI——随机梯度变分推断12.3.1 一般化MC方法12.3.2 降方差——Variance Reduction 12 VI——变分推断 12.1 背景介绍 变分推断的作用就是在概率图模型中进行参数估计&…

.mdf.locked加密sql server完美恢复---惜分飞

有可能用友ERP软件的sql server 数据库所在机器被勒索病毒加密,扩展名为.locked和昨天恢复的基本类似(.locked加密勒索数据库级别恢复),通过分析确认sql server被这种病毒加密,也可以完美恢复 通过恢复之后数据库正常挂载成功 测试应用一切正常 对于类似这种被加密的勒索的数…

Hazel游戏引擎(010)预编译头

文中若有代码、术语等错误&#xff0c;欢迎指正 文章目录 前言如何实现 前言 此节目的 由于项目中的头文件或者cpp文件都包含着c的头文件&#xff0c;有些重复&#xff0c;可以将它们包含的c头文件放在一个头文件内&#xff0c;这样不仅使代码简洁&#xff0c;而且预编译头可以…

chatgpt赋能python:Python如何取消空格提升SEO排名

Python如何取消空格提升SEO排名 作为一种高效的编程语言&#xff0c;Python已经成为了许多网站开发人员和SEO优化人员的首选工具。在网站优化中&#xff0c;取消空格是一个重要的优化技术&#xff0c;它可以提升网站速度&#xff0c;提高网站体验&#xff0c;同时也可以提升SE…

前后端交互二、form表单与模板引擎

零、文章目录 前后端交互二、form表单与模板引擎 1、form表单的基本使用 HTML相关知识请参考HTML入门 &#xff08;1&#xff09;表单是什么 表单在网页中主要负责数据采集功能。HTML中的<form>标签&#xff0c;就是用于采集用户输入的信息的&#xff0c;并通过<…

【EasyX】实时时钟

目录 实时时钟1. 绘制静态秒针2. 秒针的转动3. 根据实际时间转动4. 添加时针和分针5. 添加表盘刻度 实时时钟 本博客介绍利用EasyX实现一个实时钟表的小程序&#xff0c;同时学习时间函数的使用。 本文源码可从github获取 1. 绘制静态秒针 第一步定义钟表的中心坐标center&a…

操作系统1-操作系统的基本特征和主要功能

目录 1、操作系统的目标和作用 &#xff08;1&#xff09;操作系统的目标 &#xff08;2&#xff09;操作系统的作用 2、操作系统的发展过程 &#xff08;1&#xff09;未配置操作系统的计算机系统 &#xff08;2&#xff09;单道批处理系统(Simple Batch Processing Sys…

Task Add-in Sample (C#)

下例显示了用 C# 编写Task Add-in 的完整源代码。 使用 C# 类库 &#xff08;.NET Framework&#xff09; 创建 Visual Studio 中的项目。实现 IEdmAddIn5。在“任务属性”对话框中创建自定义页。自定义任务详细信息页面。 注意&#xff1a; 若要填充下面的 GUID 属性&#x…

软考A计划-系统架构师-学习笔记-第三弹

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

Redis 持久化机制

Redis 是个基于内存的数据库。那服务一旦宕机&#xff0c;内存中数据必将全部丢失。所以丢失数据的恢复对于 Redis 是十分重要的&#xff0c;我们首先想到是可以从数据库中恢复&#xff0c;但是在由 Redis 宕机时&#xff08;说明相关工作正在运行&#xff09;且数据量很大情况…

上课补充的知识

题目 char类型的默认值是\u0000 数组的创建方式 数组的遍历 遍历:从头到尾,依次访问数组每一个位置,获取每一个位置的元素.形式如下: 我们通过数组的下标操作数组,所以for循环变量操作的也是数组下标 开始:开始下标0 结束:结束下标length-1 如何变化: 语法&#xff1a; for…

软考A计划-系统架构师-学习笔记-第二弹

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

【java】IO流

IO流 原理 分类 字节流与字符流 节点流与包装流 Java IO详解&#xff08;五)------包装流 - YSOcean - 博客园 (cnblogs.com)JAVA I/O流 字符流和字节流、节点流和处理流(包装流、过滤流)、缓冲流_过滤流和缓冲流,字节流的关系_X-Dragon烟雨任平生的博客-CSDN博客 字符流 i…