当前位置:   article > 正文

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

qt 文件传输

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

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

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

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

1.数据流 QDataStream

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

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

2. QTcpSocket QTcpServer

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

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

服务端界面如下:

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

 接下来先将服务端的

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

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

先看下流程:

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

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

 具体实现如下

  1. void FileServer::transferFileInfo(QTcpSocket *socket)
  2. {
  3. //获取文件数据,准备发送
  4. QByteArray DataInfoBlock = getFileContent(ui->fileEdit->text());
  5. QThread::msleep(10); //添加延时
  6. m_fileInfoWriteBytes = socket->write(DataInfoBlock) - typeMsgSize;
  7. qDebug()<< "传输文件信息,大小:"<< m_sendFileSize;
  8. //等待发送完成,才能继续下次发送,否则发送过快,对方无法接收
  9. if(!socket->waitForBytesWritten(10*1000)) {
  10. ui->textBrowser->append(QString("网络请求超时,原因:%1").arg(socket->errorString()));
  11. return;
  12. }
  13. ui->textBrowser->append(QString("文件信息发送完成,开始对[%1]进行文件传输------------------")
  14. .arg(socket->localAddress().toString()));
  15. qDebug()<<"当前文件传输线程id:"<<QThread::currentThreadId();
  16. m_localFile.setFileName(m_sendFilePath);
  17. if(!m_localFile.open(QFile::ReadOnly)){
  18. ui->textBrowser->append(QString("文件[%1]打开失败!").arg(m_sendFilePath));
  19. return;
  20. }
  21. }
  22. QByteArray FileServer::getFileContent(QString filePath)
  23. {
  24. if(!QFile::exists(filePath)) {
  25. ui->textBrowser->append(QString("没有要传输的文件!" + filePath));
  26. return "";
  27. }
  28. m_sendFilePath = filePath;
  29. ui->textBrowser->append(QString("正在获取文件信息[%1]......").arg(filePath));
  30. QFileInfo info(filePath);
  31. //获取要发送的文件大小
  32. m_sendFileSize = info.size();
  33. ui->textBrowser->append(QString("要发送的文件大小:%1字节,%2M").arg(m_sendFileSize).arg(m_sendFileSize/1024/1024.0));
  34. //获取发送的文件名
  35. QString currentFileName=filePath.right(filePath.size()-filePath.lastIndexOf('/')-1);
  36. QByteArray DataInfoBlock;
  37. QDataStream sendOut(&DataInfoBlock,QIODevice::WriteOnly);
  38. sendOut.setVersion(QDataStream::Qt_5_12);
  39. int type = MsgType::FileInfo;
  40. //封装发送的信息到DataInfoBlock中
  41. sendOut<<int(type)<<QString(currentFileName)<<qint64(m_sendFileSize);
  42. ui->textBrowser->append(QString("文件[%1]信息获取完成!").arg(currentFileName));
  43. //发送的文件总大小中,信息类型不计入
  44. QString msg;
  45. if(m_sendFileSize>1024*1024) {
  46. msg = QString("%1M").arg(m_sendFileSize/1024/1024.0);
  47. }
  48. else {
  49. msg = QString("%1KB").arg(m_sendFileSize/1024.0);
  50. }
  51. ui->textBrowser->append(QString("发送的文件名:%1,文件大小:%2").arg(currentFileName).arg(msg));
  52. return DataInfoBlock;
  53. }

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

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

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

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

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

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

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

传输文件的代码如下: 

  1. void FileServer::transferFileData(QTcpSocket *socket)
  2. {
  3. qint64 payloadSize = 1024*1; //每一帧发送1024*64个字节,控制每次读取文件的大小,从而传输速度
  4. double progressByte= 0;//发送进度
  5. qint64 bytesWritten=0;//已经发送的字节数
  6. while(bytesWritten != m_sendFileSize) {
  7. double temp = bytesWritten/1.0/m_sendFileSize*100;
  8. int progress = static_cast<int>(bytesWritten/1.0/m_sendFileSize*100);
  9. if(bytesWritten<m_sendFileSize){
  10. QByteArray DataInfoBlock = m_localFile.read(qMin(m_sendFileSize,payloadSize));
  11. qint64 WriteBolockSize = socket->write(DataInfoBlock, DataInfoBlock.size());
  12. //QThread::msleep(1); //添加延时,防止服务端发送文件帧过快,否则发送过快,客户端接收不过来,导致丢包
  13. QThread::usleep(3); //添加延时,防止服务端发送文件帧过快,否则发送过快,客户端接收不过来,导致丢包
  14. //等待发送完成,才能继续下次发送,
  15. if(!socket->waitForBytesWritten(3*1000)) {
  16. ui->textBrowser->append("网络请求超时");
  17. return;
  18. }
  19. bytesWritten += WriteBolockSize;
  20. ui->sendProgressBar->setValue(progress);
  21. }
  22. if(bytesWritten==m_sendFileSize){
  23. //LogWrite::LOG_DEBUG(QString("当前更新进度:100%,发送总次数:%1").arg(count), "server_"+socket->localAddress().toString());
  24. ui->textBrowser->append(QString("当前上传进度:%1/%2 -> %3%").arg(bytesWritten).arg(m_sendFileSize).arg(progress));
  25. ui->textBrowser->append(QString("-------------对[%1]的文件传输完成!------------------").arg(socket->peerAddress().toString()));
  26. ui->sendProgressBar->setValue(100);
  27. m_localFile.close();
  28. return;
  29. }
  30. if(bytesWritten > m_sendFileSize) {
  31. ui->textBrowser->append("意外情况!!!");
  32. return;
  33. }
  34. if(bytesWritten/1.0/m_sendFileSize > progressByte) {
  35. ui->textBrowser->append(QString("当前上传进度:%1/%2 -> %3%").arg(bytesWritten).arg(m_sendFileSize).arg(progress));
  36. progressByte+=0.1;
  37. }
  38. }
  39. }

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

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

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

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

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

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

 然后设置下载标识为true

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

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

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

 实现代码如下:

  1. void FileManager::fileDataRead()
  2. {
  3. qint64 readBytes = m_tcpSocket->bytesAvailable();
  4. if(readBytes <0) return;
  5. int progress = 0;
  6. // 如果接收的数据大小小于要接收的文件大小,那么继续写入文件
  7. if(myFile->bytesReceived < myFile->fileSize) {
  8. // 返回等待读取的传入字节数
  9. QByteArray data = m_tcpSocket->read(readBytes);
  10. myFile->bytesReceived+=readBytes;
  11. ui->textBrowser->append(QString("接收进度:%1/%2(字节)").arg(myFile->bytesReceived).arg(myFile->fileSize));
  12. progress =static_cast<int>(myFile->bytesReceived*100/myFile->fileSize);
  13. myFile->progressByte = myFile->bytesReceived;
  14. myFile->progressStr = QString("%1").arg(progress);
  15. ui->progressBar->setValue(progress);
  16. myFile->localFile.write(data);
  17. }
  18. // 接收数据完成时
  19. if (myFile->bytesReceived==myFile->fileSize){
  20. ui->textBrowser->append(tr("接收文件[%1]成功!").arg(myFile->fileName));
  21. progress = 100;
  22. myFile->localFile.close();
  23. ui->textBrowser->append(QString("接收进度:%1/%2(字节)").arg(myFile->bytesReceived).arg(myFile->fileSize));
  24. myFile->progressByte = myFile->bytesReceived;
  25. ui->progressBar->setValue(progress);
  26. isDownloading = false;
  27. myFile->initReadData();
  28. }
  29. if (myFile->bytesReceived > myFile->fileSize){
  30. qDebug()<<"myFile->bytesReceived > m_fileSize";
  31. }
  32. }

最终就达到这个效果啦:

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

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

有跟多小白私信我说,已运行就提示这个

这是因为服务器没有开的原因,连接不是导致的,因为我这是一个总工程,里面有两个子项目,一个是客户端,一个是服务器端,教一下怎么切项目吧,如下所示

除了这样切运行程序,也可以去生成程序的目录下启动

也可以在每个子项目的pro文件里面设置程序生成的位置,依赖库的位置等,这个一般要打包时程序给别的电脑用时必备的,这里就不说了

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

如下:

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/305144
推荐阅读
相关标签
  

闽ICP备14008679号