Qt实现Socket从文件发送多幅图片(Qt③)

2024-03-01 21:40

本文主要是介绍Qt实现Socket从文件发送多幅图片(Qt③),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

初学者记录学习内容,如有错误请各位前辈指点。

这次的项目实现将在上次完成“单幅图片从客户端发送到服务器”的基础上继续添加。
主要添加的功能的是
①客户端和服务器可以选择读取和保存图片的文件,而不是存入到内存中。
②发送接收不同格式的图片。
③服务器读取文件中所有的图片,显示到界面上,并且可以切换图片。
文档目录

服务器Server

服务器端的界面依旧是那样的简洁
server界面
sendButton发送图片,disconnectButton断开连接,picturePathEdit得出读取图片的路径,saveButton选择路径。
直接贴代码:
filepictureserver.h

#ifndef FILEPICTURESERVER_H
#define FILEPICTURESERVER_H#include <QDialog>
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
#include <QFileDialog>
#include <QMessageBox>
#include <QStringList>
#include <vector>
#include <QByteArray>
#include <QDataStream>
#include <QBuffer>
#include <QString>using namespace std;//std::vectornamespace Ui {
class filePictureServer;
}class filePictureServer : public QDialog
{Q_OBJECTpublic:explicit filePictureServer(QWidget *parent = 0);~filePictureServer();private slots:void sendPictures();void showDirectory();void disconnectSocket();void acceptConnection();void displayError(QAbstractSocket::SocketError);private:Ui::filePictureServer *ui;QTcpServer *tcpServer;QTcpSocket *tcpSocket;QFileDialog *fileDialog;QStringList pictureString_list;vector<QPixmap> picturelist;int picIndex;};#endif // FILEPICTURESERVER_H

filepictureserver.cpp

#include "filepictureserver.h"
#include "ui_filepictureserver.h"
#include <windows.h>
#include "utility.h"filePictureServer::filePictureServer(QWidget *parent) :QDialog(parent),ui(new Ui::filePictureServer)
{ui->setupUi(this);tcpServer = new QTcpServer(this);if(!tcpServer->listen(QHostAddress::Any,7777)){qDebug()<<tcpServer->errorString();close();}tcpSocket = NULL;connect(ui->sendButton,SIGNAL(clicked(bool)),this,SLOT(sendPictures()));connect(ui->saveButton,SIGNAL(clicked(bool)),this,SLOT(showDirectory()));connect(ui->disconnectButton,SIGNAL(clicked(bool)),this,SLOT(disconnectSocket()));connect(tcpServer,SIGNAL(newConnection()),this,SLOT(acceptConnection()));picIndex = 0;}filePictureServer::~filePictureServer()
{delete ui;
}void filePictureServer::sendPictures()
{if(tcpSocket==NULL)return;QByteArray block;QBuffer buffer;QString style;QDataStream out(&block,QIODevice::WriteOnly);out.setVersion(QDataStream::Qt_5_8);style = pictureString_list[picIndex].right(3);picturelist[picIndex].save(&buffer,style.toStdString().c_str());picIndex++;out<<(quint32)buffer.data().size();out<<style;block.append(buffer.data());tcpSocket->write(block);if(picIndex>=picturelist.size())picIndex = 0;
}void filePictureServer::showDirectory()
{fileDialog=new QFileDialog(this);QString dir=fileDialog->getExistingDirectory(this,"Open Directory","D:\\QTproject\\pictureFromFileSocket",QFileDialog::ShowDirsOnly);ui->picturesPathEdit->setText(dir);if(dir==NULL)return;QString picturePath=ui->picturesPathEdit->text();GetFolderImages(picturePath,pictureString_list,false);for(int i=0;i<pictureString_list.size();i++){QPixmap pix;pix.load(pictureString_list[i]);picturelist.push_back(pix);}if(fileDialog)delete fileDialog;
}void filePictureServer::acceptConnection()
{tcpSocket = tcpServer->nextPendingConnection();connect(tcpSocket,SIGNAL(disconnected()),this,SLOT(deleteLater()));connect(tcpSocket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayError(QAbstractSocket::SocketError)));
}void filePictureServer::disconnectSocket()
{if(tcpSocket==NULL)return;tcpSocket->abort();QMessageBox::about(NULL,"Connection","Connection stoped!");
}void filePictureServer::displayError(QAbstractSocket::SocketError)
{qDebug()<<tcpSocket->errorString();
}

由于我只是记录这一周学习的结果,对于涉及到的函数并不能深刻地说清楚,只简单描述需要的功能可以用什么函数去实现,希望各位道友能有所获。
我们从showDirectory()开始说起,这里用到了QFileDialog,它提供了一个标准对话框通过文件系统去选择一个或者多个文件或目录。
getExistingDirectory()函数获取文件夹路径,注意第三个参数可以指定一个路径(绝对/相对皆可),在对话框打开的时候默认先到这个路径下,然后继续选择。然后由setText()显示到lineEdit上。
在使用文件路径之前,要加个判断得到的路径不为空。

隆重介绍自定义函数GetFolderImages(),并附上utility.cpp,在以后的学习和编程过程中,如果涉及到需要获取一个文件中的所有图片,我们可以另写一个utility.h与.cpp,直接将其拿来使用。

utility.h

#ifndef UTILITY_H
#define UTILITY_H
#include <QString>
#include <QStringList>int GetFolderImages(const QString path, QStringList &string_list, bool sub_dir);#endif // UTILITY_H

utility.cpp

#include <QDir>
#include <QDirIterator>
#include "utility.h"
int GetFolderImages(const QString path, QStringList &string_list, bool sub_dir)
{string_list.clear();int result = 0;
//    QString s = QDir::currentPath();QDir dir(path);if(!dir.exists()){return result;}QStringList filters;//用于设置文件名称过滤器,只为filters格式(后缀为.jpeg等图片格式)filters<<QString("*.jpeg")<<QString("*.jpg")<<QString("*.png")<<QString("*.tiff")<<QString("*.gif")<<QString("*.bmp")<<QString("*.mov")<<QString("*.mp4");QDirIterator *dir_iterator = NULL;if(sub_dir)//定义迭代器并设置过滤器,sub_dir若为true遍历子目录,若为false不遍历子目录。dir_iterator = new QDirIterator(path,filters,QDir::Files | QDir::NoSymLinks,    QDirIterator::Subdirectories);elsedir_iterator = new QDirIterator(path,filters,QDir::Files | QDir::NoSymLinks);while(dir_iterator->hasNext()){dir_iterator->next();QFileInfo file_info = dir_iterator->fileInfo();QString absolute_file_path = file_info.absoluteFilePath();string_list.append(absolute_file_path);++result;}if(dir_iterator!=NULL)delete dir_iterator;//该函数返回的图片路径储存在QStringList &string_list中,可取出数据调用。return result;
}

GetFolderImages()函数的三个参数,QString类型的path给出文件的路径;bool型的sub_dir若为true则还需要遍历当前目录的子目录下的图片,若为false则不遍历子目录。最后遍历得到的所有图片的路径会保存到QStringList类型的string_list中,注意查询操作QStringList类型变量的函数。

由于我们并不知道文件中图片的数量,因此应该使用vector容器来存储图片。

vector是一个动态数组,用于元素数量变化的对象数组。像数组一样,vector类也用从0开始的下标表示元素的位置;但和数组不同的是,当vector对象创建后,数组的元素个数会随着vector对象元素个数的增大和缩小而自动变化。

注意使用之前要添加#include ;和using namespace std;

   QStringList pictureString_list;vector<QPixmap> picturelist;

pictureString_list保存着所有图片的途径,声明一个QPixmap类型的vector容器。
注意函数bool QPixmap::load(const QString &fileName,……)会从给定的文件路径下载一个pixmap图片,返回值是bool型,得出是否加载成功。

   QPixmap pix;pix.load(pictureString_list[i]);picturelist.push_back(pix);

循环由路径加载图片到pix,然后将pix加入vector容器中。注意操作vector元素的函数void push_back(const T& x):向量尾部增加一个元素X。
然后说sendPictures()函数,与上一篇的相同,用QBuffer保存图片,写入到数据流中,并写入图片的大小,write()发送。不同之处在于,如果我们不只是发送BMP格式的图片,我们还需要保存图片的格式,一同发送过去,便于之后在客户端由数据流再转回到原本的格式。
一般情况下,图片路径的最后3个字符就是该图片的格式,我们用.right(3)获取。right()函数的功能是从字符串右端获取指定个数字符,保存到style中,写入到流中。
记得需要定义一个编号picIndex,注意每点击一次,发送一个图片,编号自增1,并加入判断,编号超过图片总数的时候,重置为0,可进行循环发送。
如果又看上一篇的道友应该会注意到这句代码,即将如下路径的图片保存到buffer中。

QPixmap(":/new/prefix1/sendPicture/007.bmp").save(&buffer,"BMP");

这次也是同样

picturelist[picIndex].save(&buffer,style.toStdString().c_str());

注意style是QString类型的图片格式,使用style.toStdString().c_str() 可以将Qstring转成char*。因为save函数的原型,第二个参数的类型是char*,这个转化方法需要记得,会经常使用。

bool QPixmap::save(QIODevice *device, const char *format = Q_NULLPTR, int quality = -1) const

客户端Client

客户端界面
贴代价如下:
filepictureclient.h

#ifndef FILEPICTURECLIENT_H
#define FILEPICTURECLIENT_H#include <QDialog>
#include <QObject>
#include <QFileDialog>
#include <QTcpSocket>
#include <QMessageBox>
#include <QBuffer>
#include <QByteArray>
#include <QImageReader>
#include <vector>
//#include <QDateTime>
//#include <QString>namespace Ui {
class filePictureClient;
}class filePictureClient : public QDialog
{Q_OBJECTpublic:explicit filePictureClient(QWidget *parent = 0);~filePictureClient();private slots:void showDirectory();void sendConnection();void displayError(QAbstractSocket::SocketError);void receivePictures();void plusPicture();void reducePicture();
private:Ui::filePictureClient *ui;QFileDialog *fileDialog;QTcpSocket *tcpSocket;quint32 blockSize;std::vector<QPixmap> pictureList;QPixmap pix;QImage image;int currentImgIndex;int pictureNumber;
};#endif // FILEPICTURECLIENT_H

filePictureClient.cpp

#include "filepictureclient.h"
#include "ui_filepictureclient.h"filePictureClient::filePictureClient(QWidget *parent) :QDialog(parent),ui(new Ui::filePictureClient)
{ui->setupUi(this);tcpSocket = new QTcpSocket(this);connect(ui->savingButton,SIGNAL(clicked(bool)),this,SLOT(showDirectory()));connect(ui->connectingButton,SIGNAL(clicked(bool)),this,SLOT(sendConnection()));connect(tcpSocket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayError(QAbstractSocket::SocketError)));connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(receivePictures()));connect(ui->lastPictureButton,SIGNAL(clicked(bool)),this,SLOT(reducePicture()));connect(ui->nextPictureButton,SIGNAL(clicked(bool)),this,SLOT(plusPicture()));blockSize=0;currentImgIndex=0;pictureNumber = 1;
}filePictureClient::~filePictureClient()
{delete ui;
}void filePictureClient::sendConnection()
{if(tcpSocket->state()!=QAbstractSocket::ConnectedState){tcpSocket->connectToHost(ui->ipAdressLineEdit->text(),ui->portLineEdit->text().toInt());if(tcpSocket->waitForConnected(1000)){QMessageBox::about(NULL,"Connection","Connection success");}else{QMessageBox::about(NULL,"Connection","Connection timed out");}}elseQMessageBox::information(NULL,"Connection","Connected!");
}void filePictureClient::showDirectory()
{fileDialog=new QFileDialog(this);QString dir=fileDialog->getExistingDirectory(this,"Open Directory","D:\\QTproject\\pictureFromFileSocket",QFileDialog::ShowDirsOnly);ui->picturesPathLineEdit->setText(dir);if(fileDialog)delete fileDialog;
}void filePictureClient::displayError(QAbstractSocket::SocketError)
{qDebug()<<tcpSocket->errorString();
}void filePictureClient::receivePictures()
{while(tcpSocket->bytesAvailable()>0){QDataStream in(tcpSocket);if(blockSize==0){in.setVersion(QDataStream::Qt_5_8);if(tcpSocket->bytesAvailable()<sizeof(quint32))return;in>>blockSize;}if(tcpSocket->bytesAvailable()<blockSize)return;QString style;in>>style;QByteArray array = tcpSocket->read(blockSize);QBuffer buffer(&array);buffer.open(QIODevice::ReadOnly);QImageReader reader(&buffer,style.toStdString().c_str());image = reader.read();blockSize=0;if(!image.isNull()){
//  QDateTime time=QDateTime::currentDateTime();
//  QString str=time.toString("yyyy-MM-dd hh:mm:ss ddd");QString filename = ui->picturesPathLineEdit->text()+"/"+QString("%1.").arg(pictureNumber)+style;image.save(filename);pix.load(filename);pictureList.push_back(pix);pictureNumber++;blockSize=0;}pictureList[0]= pictureList[0].scaled(ui->showPictureLabel->size());ui->showPictureLabel->setPixmap(pictureList[0]);}}void filePictureClient::reducePicture()
{if(currentImgIndex==0)return;--currentImgIndex;pictureList[currentImgIndex] = pictureList[currentImgIndex].scaled(ui->showPictureLabel->size());ui->showPictureLabel->setPixmap(pictureList[currentImgIndex]);}void filePictureClient::plusPicture()
{if(currentImgIndex==pictureList.size()-1)return;++currentImgIndex;pictureList[currentImgIndex] = pictureList[currentImgIndex].scaled(ui->showPictureLabel->size());ui->showPictureLabel->setPixmap(pictureList[currentImgIndex]);
}

同样savingButon得到保存图片的路径,最下面的lastPictureButton和nextPictureButton用于切换显示showPictureLabel上的保存到服务器文件中的图片。
同样在showDirectory()中,完成标准对话框选择文件路径的功能,注意和服务器相同,在局部函数中new一部分内存空间,要记得删除。
然后在receivePictures()中,与上一篇说到的相同,写出图片的大小,做两次判断。然后依数据流存入的顺序,用style接收QString类型的图片的格式,转化回原本的格式保存到QImage中。
保存图片就涉及到图片的命名问题,

bool QPixmap::save(const QString &fileName, const char *format = Q_NULLPTR, int quality = -1) const

注意这filename的格式是我们平常说的所在文件的路径+图片的命名,像上面提到的这个样子”:/new/prefix1/sendPicture/007.bmp”,此处常用的有两种方法:
①所在文件的路径+分隔符”/”+当前时间+”.”+图片格式。
我们也可以定义一个计数器,保存图片的同时让计数器变量自增,得到:
②所在文件的路径+分隔符”/”+计数变量+”.”+图片格式。
注意到注释中有按格式获取当前时间的函数,可做参考。我们用第二种方法

filename = ui->picturesPathLineEdit->text()+"/"+QString("%1.").arg(pictureNumber)+style;

需要注意到QString(“%1.”).arg(pictureNumber),这里是将arg()括号中的内容加转化为QString,然后替换%1,这个函数在数据库中最常用,下一篇就进行讲解。最后将图片加载到vector容器中,用于计数的图片编号+1以便下次点击时调用。
reducePicture(),plusPicture()两个槽函数不必多说,图片作为QPixmap类型已经加入到pictureList的vector容器中,直接调用即可。
最后的最后,用Label显示图片,需要注意图片自适应的问题。.scaled()括号内是Label的尺寸,然后再setPixmap()。

功能拓展与反思

在最初的设想中,是准备在一次点击之后,文件中的所有图片全部从服务器发送到客户端,实现方式是将全部图片一次性全部写入到流,只管发送,不考虑接收那边如何。原本想到是本地的服务器和客户端,应该可以传输成功的,但十张图片发送只能接收到一张。
后来改变思路,变成了如上解释的这种结构,没点击一次发送一张的结构。
随后贫僧还是想实现,一次点击能发送所有图片的功能,后简单实现实现如下:
在客户端实现了一张图片的保存和发送之后,向服务器反馈一个信号,即从客户端向服务器发送一个信息。

 if(tcpSocket==NULL)return;QByteArray block;QDataStream back(&block,QIODevice::WriteOnly);back.setVersion(QDataStream::Qt_5_8);back <<(int)0;tcpSocket->write(block);

然后在服务器端中利用发送图片时建立的tcpSocket句柄,去接收这个信号。

connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(sendPictures()));

注意写的位置不是在构造函数中,而是在

void filePictureServer::acceptConnection()

中,tcpSocket句柄就是在这个函数中建立的。收到信号,触发sendPictures()函数,发送一张图片,发完一张图片后再次发送信号,可以形成循环。
或者还有一种挺常用的思路,比如可以设置一个变量(int)switchSignal=0,然后在服务器端接收客户端发来的信号,如果收到1,将switchSignal置为1,服务器端第二次及其之后发送图片之前将判断条件与switchSignal相与。如伪代码:

while(要发送的图片数量)
{if(switchSignal == 0)continue;……
}

注意continue的用法,continue作用为结束本次循环,即跳过循环体中下面尚未执行的语句,接着进行下一次是否执行循环的判定。用来解决switchSignal第一次为0,后面为1的判断情况。

其实在不是本地传输,网速慢或者传输的文件比较大时候,一旦开始传输就会卡死,这就需要需要我们使用多线程的思路,使用QThread,将收发的函数写在run()中,调用另外的线程去执行,当需要断开或者暂停时,界面上可以操作而不至于卡住。

结束,如有错误,还望指正。

这篇关于Qt实现Socket从文件发送多幅图片(Qt③)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/763921

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、