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

相关文章

Java中使用Java Mail实现邮件服务功能示例

《Java中使用JavaMail实现邮件服务功能示例》:本文主要介绍Java中使用JavaMail实现邮件服务功能的相关资料,文章还提供了一个发送邮件的示例代码,包括创建参数类、邮件类和执行结... 目录前言一、历史背景二编程、pom依赖三、API说明(一)Session (会话)(二)Message编程客

Java中List转Map的几种具体实现方式和特点

《Java中List转Map的几种具体实现方式和特点》:本文主要介绍几种常用的List转Map的方式,包括使用for循环遍历、Java8StreamAPI、ApacheCommonsCollect... 目录前言1、使用for循环遍历:2、Java8 Stream API:3、Apache Commons

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一

使用Python实现高效的端口扫描器

《使用Python实现高效的端口扫描器》在网络安全领域,端口扫描是一项基本而重要的技能,通过端口扫描,可以发现目标主机上开放的服务和端口,这对于安全评估、渗透测试等有着不可忽视的作用,本文将介绍如何使... 目录1. 端口扫描的基本原理2. 使用python实现端口扫描2.1 安装必要的库2.2 编写端口扫

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

MySQL分表自动化创建的实现方案

《MySQL分表自动化创建的实现方案》在数据库应用场景中,随着数据量的不断增长,单表存储数据可能会面临性能瓶颈,例如查询、插入、更新等操作的效率会逐渐降低,分表是一种有效的优化策略,它将数据分散存储在... 目录一、项目目的二、实现过程(一)mysql 事件调度器结合存储过程方式1. 开启事件调度器2. 创

使用Python实现操作mongodb详解

《使用Python实现操作mongodb详解》这篇文章主要为大家详细介绍了使用Python实现操作mongodb的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、示例二、常用指令三、遇到的问题一、示例from pymongo import MongoClientf

SQL Server使用SELECT INTO实现表备份的代码示例

《SQLServer使用SELECTINTO实现表备份的代码示例》在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误,在SQLServer中,可以使用SELECTINT... 在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误。在 SQL Server 中,可以使用 SE

基于Go语言实现一个压测工具

《基于Go语言实现一个压测工具》这篇文章主要为大家详细介绍了基于Go语言实现一个简单的压测工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录整体架构通用数据处理模块Http请求响应数据处理Curl参数解析处理客户端模块Http客户端处理Grpc客户端处理Websocket客户端

Java CompletableFuture如何实现超时功能

《JavaCompletableFuture如何实现超时功能》:本文主要介绍实现超时功能的基本思路以及CompletableFuture(之后简称CF)是如何通过代码实现超时功能的,需要的... 目录基本思路CompletableFuture 的实现1. 基本实现流程2. 静态条件分析3. 内存泄露 bug