Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Qt - 聊天室发送图片/文件

Qt - 聊天室发送图片/文件

作者头像
何其不顾四月天
发布于 2023-03-10 04:52:20
发布于 2023-03-10 04:52:20
97400
代码可运行
举报
文章被收录于专栏:Qt&PyQtQt&PyQt四月天的专栏
运行总次数:0
代码可运行

Qt - 聊天室发送图片/文件

简介

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
好久没有发博客,上一篇的博客还是在上一份工作离职前整理的一篇博客。大半年没有发,一是工作繁忙,转成了音视频方向,新的工作内容暂时还不便发出来,二是不知道发什么内容,也没有整理。考虑了一下是python调用C库,但是整理起来比较费劲,想想就整理这个了 内容还相对少一点,比较有意思。

这个聊天室是我上一次的一个小项目,头像,签名,群聊,登录,图片发送等等相关功能,这次就单独说一下图片发送了。

思路

版本信息

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1.Qt version: 5.12.2
2.没有使用第三库

关键点

CSDN不支持plantuml,贴了一下图

聊天室收发图片时序图

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@startuml
title 时序图
entity clientA as clientA
entity clientB as clientB
== 初始化 ==
clientA -> clientA : tcp-socket初始化
clientB -> clientB : tcp-socket初始化
clientA <-> clientB : tcp连接建立
== 图片收发 ==
clientA -> clientA : 选择图片
clientA -> clientB : 发送图片
clientB -> clientB : 接收图片
clientB -> clientB : 保存图片
clientB -> clientB : 显示图片
== end ==
@enduml

关键点的选择

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1.建立TCP连接 : QTcpSocket ,不做说明
2.选择图片 : QFileDialog 实现图片选择
3.发送图片 :  消息拼装,QTcpSocket -> write(QByteArray)
4.接收图片 :  QTcpSocket -> readyRead(),消息解析
5.显示图片 : QWidget->show()

其中,关键点为,tcp在实际的场景中,会遇到拆包,丢包,沾包等一些意外的情况,当图片文件比较大的时候,tcp的单帧数据有限,必然会发生拆包现象,所以我们在接收时需要考虑从组包的情况,把完整的图片数据提取出来。

TCP连接建立
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    socket = new QTcpSocket;
    socketState = false;
	//ipAddressStr ip地址
	//port 端口号
    if(!socketState)
    {
        socket->connectToHost(ipAddressStr, port);
        if(socket->waitForConnected(3000))
        {
            qDebug() << "Connect2Server OK";
            ui->pushButtonConnect->setText("连接成功");
            socketState = true;
        }
        else
        {
            qDebug() << socket->errorString();
            return;
        }
    }
    else
    {
        socket->close();    //触发disconnected()信号
        ui->pushButtonConnect->setText("断开连接");
        socketState = false;
    }	
TCP接收数据
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    connect(socket, SIGNAL(readyRead()),this, SLOT(readyReadSlot()));          //接收消息
	//接收数据槽函数
    void Widget::readyReadSlot()
    {
        QByteArray data = socket->readAll();
        byteArray += data; //当前socket接收数据缓冲区,将新来的数据添加到数据缓冲区末尾
        emit sign_recvData(); //触发数据解析事件
    }
TCP发送数据
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void Widget::sendMsg(QString msg)
{
    if(socket->isOpen() && socket->isValid())
    {
        QByteArray _bufByteArry;
        //msg -> _bufByteArry : QString 转为 QByteArray
        socket->write(_bufByteArry);
    }
}
图片选择
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void Widget::on_pushButtonSend_img_clicked()
{
    QString fileName = QFileDialog::getOpenFileName(this,
                                                     tr("图片选择对话框"),
                                                     "F:",
                                                     tr("*png *jpg;"));
    QImage image(fileName);
    QByteArray imgBy;
    QBuffer imgBuf(&imgBy);
    image.save(&imgBuf, "png");

    emit chartMsg(ui->groupBox->title(), true, QString::fromLocal8Bit(imgBy.toBase64())); //送入到发送区
    //图片显示
    QString str = QString(QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss ddd")) + selfName + ":\n";
    ui->textBrowserRecv->append(QString(str));
    ui->textBrowserRecv->insertHtml(imgPathToHtml(fileName));
}
图片保存
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void UserChart::setRecvMsg(bool msgType, QString msgData)
{
    QString str = QString(QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss ddd")) + ui->groupBox->title() + ":\n";
    if(!msgType)
    {
        str += msgData;
        ui->textBrowserRecv->append(QString(str));
    }
    //如果消息类型为图片消息
    else
    {
        QImage image;
        image.loadFromData(QByteArray::fromBase64(msgData.toLocal8Bit()));
        image.save(QString("./" + QDateTime::currentDateTime().toString("yyyyMMddhhmmsddd") + ".png"), "png");
        ui->textBrowserRecv->append(QString(str));
        ui->textBrowserRecv->insertHtml(imgPathToHtml(QString("./" + QDateTime::currentDateTime().toString("yyyyMMddhhmmsddd") + ".png")));
    }
}
数据发送与数据解析

在上述的内容中,给出了一些的基础写法。还剩在发送的前的数据组包,接收数据后的拆包,组包等一些处理。在这些处理中,有一些关键问题。

在实际的通信过程,数据类型与内容时很复杂的,怎么确认数据是点对点的聊天数据,还是群聊的聊天数据,数据的发送人是谁,数据的接收人是谁,这些都是需要在业务过程实际的处理的一些问题。

其中涉及到的是通信数据包数据结构的定义,以及实际的拆包组包逻辑两个关键点的解决。

包结构

为了减少开发的成本以及高效的阅读性,序列化与反序列化的成本。选择通用json,来处理实际的有效用户数据。

数据结构如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
    "sendname" : "username",
    "recvname" : "username",
    "msgtype" : 0, //在实际的业务处理中,消息类型只包含两种数据, 文本数据,图片数据
    "msgdata" : "data"
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
    "type" : "", //消息类型
    "length" : "", //数据长度
    "data" : ""	//数据内容
}

user_msg为例全部的数据包如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
    "type" : "user_msg", 
    "length" : "",
    "data" : "
    {
    \"sendname\" : \"username\",
    \"recvname\" : \"username\",
    \"msgtype\" : 0, 
    \"msgdata\" : \"data\"}
	"
}

结构体内容如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct UserMsg
{
    QString sendName;
    QString recvName;
    bool msgType;
    QString msgData;
    QString parseJson()
    {
        QJsonObject jsonObj;
        jsonObj.insert("sendname", sendName);
        jsonObj.insert("recvname", recvName);
        jsonObj.insert("msgtype", msgType);
        jsonObj.insert("msgdata",msgData);
        QJsonDocument jsonDoc;
        jsonDoc.setObject(jsonObj);
        return QString::fromUtf8(jsonDoc.toJson(QJsonDocument::JsonFormat::Compact));
    }
    int parseJsonObject(QString data)
    {
        try
        {
            QJsonObject j = parse(data.toLocal8Bit(), err);
            if(err == QString(ERROR_UNSTR))
                return KERROR;
            sendName = get_value(j, "sendname").toString();
            recvName = get_value(j, "recvname").toString();
            msgType = get_value(j, "msgtype").toBool();
            msgData = get_value(j, "msgdata").toString();
            return KSUCCESS;
        } catch (const std::exception) {
            return KFAIL;
        }
    }
};
校验数据

在包数据完成之后,就涉及到实际的 沾包,组包,拆包的实际处理,怎样保证或者说判断你接收的数据是一个完整的数据包,就涉及到包的校验。就是传统的 包头,包长度,包数据,包尾。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//下边的数据结构就是类似的抽象概念
struct NetMsgHeader
{
    int startID;
    int length;
};

struct NetMsgEnd
{
    int endID;
};

struct NetMsg
{
    NetMsgHeader header;
    QString msg;
    NetMsgEnd end;
};
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#define MSG_HEAD_ID             123456 //定义包头
#define MSG_END_ID              654321 //定义包尾
组包数据
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void Widget::sendMsg(QString msg)
{
    if(socket->isOpen() && socket->isValid())
    {
        NetMsg netMsg;
        netMsg.header.startID = 123456; 								//包头赋值
        netMsg.end.endID = 654321;      								//包尾赋值
        netMsg.msg = msg;												//用户数据
        netMsg.header.length = sizeof(int) * 3 + netMsg.msg.length();	//数据长度

        qDebug() << "SendMsg:" << msg;

        QByteArray _bufByteArry;
        //append 方式尾插插入数据,注意数据的转换
        _bufByteArry.append((const char*)&netMsg.header.startID, sizeof(int));	//包头转为字节数组
        _bufByteArry.append((const char*)&netMsg.header.length, sizeof(int));	//包长度转为字节数组
        _bufByteArry.append(msg.toStdString().c_str(), msg.length());			//数据转为字节数组
        _bufByteArry.append((const char*)&netMsg.end.endID, sizeof(int));		//包尾转为字节数据
//        qDebug() << _bufByteArry << byteArrayToInt(_bufByteArry.mid(0, 4)) << byteArrayToInt(_bufByteArry.mid(4, 4))
//                 << QString::fromLocal8Bit(_bufByteArry.mid(8, (netMsg.header.length - 12))) << byteArrayToInt(_bufByteArry.mid((netMsg.header.length - 4), 4));
        socket->write(_bufByteArry); //写入数据到socket
    }
}
数据组包
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
QByteArray byteArray; 	//声明字节型数组缓冲区,将所有接收的数据,全量保存的数据缓冲区
void Widget::readyReadSlot()
{
    QByteArray data = socket->readAll(); 	//读取IO口缓冲区的所有数据
    byteArray += data; 						//采用尾插的方法将数据写入数据缓冲区
    emit sign_recvData();					//触发接收信号,进行数据解析
}

connect(this, &Widget::sign_recvData, this, &Widget::slt_packagetHandle); //信号槽

//数据解析
void Widget::slt_packagetHandle()
{
    NetMsg netMsg;
    //判断数据缓冲区的数据是否大于消息头,如果小于包头(haed + length),判断数据无效,跳出解析,继续等待下次数据到来
    if(byteArray.length() >= sizeof(NetMsgHeader))
    {
        //取出包头
        //注意提取方式
        netMsg.header.startID = byteArrayToInt(byteArray.mid(MSG_DEFAULT_POSTION, sizeof(int)));
        //取出包长度
        netMsg.header.length = byteArrayToInt(byteArray.mid(sizeof(int), sizeof(int)));
        //如果缓冲区长度大于包长度,进去数据解析
        if(byteArray.length() >= netMsg.header.length)
        {   
            //取出包尾
            netMsg.end.endID = byteArrayToInt(byteArray.mid(netMsg.header.length - sizeof(int), sizeof(int)));
            //校验包头包尾
            if(netMsg.end.endID == MSG_END_ID && netMsg.header.startID == MSG_HEAD_ID)
            {
                //触发用户消息,发送到主线程进行对应的消息处理
                emit sign_recvMsg(QString::fromLocal8Bit(byteArray.mid(8, netMsg.header.length - sizeof(int) * 3)));
                //数据缓冲区,移除已经处理的数据
                byteArray = byteArray.remove(MSG_DEFAULT_POSTION, netMsg.header.length);
                //如果数据不为空,继续进行下一次解析
                if(!byteArray.isEmpty())
                {
                    emit sign_recvData();
                }
            }
        }
        else if(byteArray.length() < netMsg.header.length)
        {
            return;
        }
    }
}

扩张

上述的消息结构只满足图片发送与文本发送,在发送文件的时候,文件格式以及文件名称的确实导致文件无法保存。所以需要将消息结构进行扩张。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
    "sendname" : "username",
    "recvname" : "username",
    "msgtype" : 0, //在实际的业务处理中,消息类型只包含两种数据, 0:文本数据 1:图片数据 2:文件数据
    "msgname" : "name", //消息名称 -0:text 1: image 2: filename.fmt
    "msgdata" : "data"
}

结构体改为如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct UserMsg
{
    QString sendName;
    QString recvName;
    int msgType;
    QString msgName;
    QString msgData;
    QString parseJson()
    {
        QJsonObject jsonObj;
        jsonObj.insert("sendname", sendName);
        jsonObj.insert("recvname", recvName);
        jsonObj.insert("msgtype", msgType);
        jsonObj.insert("msgname", msgName);
        jsonObj.insert("msgdata",msgData);
        QJsonDocument jsonDoc;
        jsonDoc.setObject(jsonObj);
        return QString::fromUtf8(jsonDoc.toJson(QJsonDocument::JsonFormat::Compact));
    }
    int parseJsonObject(QString data)
    {
        try
        {
            QJsonObject j = parse(data.toLocal8Bit(), err);
            if(err == QString(ERROR_UNSTR))
                return KERROR;
            sendName = get_value(j, "sendname").toString();
            recvName = get_value(j, "recvname").toString();
            msgType = get_value(j, "msgtype").toInt();
            msgName = get_value(j, "msgname").toString();
            msgData = get_value(j, "msgdata").toString();
            return KSUCCESS;
        } catch (const std::exception) {
            return KFAIL;
        }
    }
};

针对不同类型的文件保存,则需要一个 QFile 文件句柄,来保存文件。

备注

文档只写了关键内容以及关键思路,如有错误或者说更好的思路,欢迎指正,以及交流。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-08-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Qt网络聊天室客户端
QTcpSocket 是QAbstractSocket类非常方便的一个子类,让你创建一个TCP连接和数据流交流。
DeROy
2020/08/20
2.3K0
Qt网络聊天室客户端
【QT】Qt网络
实际开发中不会用Qt直接写服务器的,因为服务器是没有图形化界面的,一般我们会用其他的语言软件写好程序之后与Qt联合使用,这里只是演示
s-little-monster
2024/11/12
3120
【QT】Qt网络
qt中Qtcpserver服务端_qt websocket
TCP 协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP 通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。
全栈程序员站长
2022/09/30
1.4K0
qt中Qtcpserver服务端_qt websocket
如何批量识别出照片中的水印文字,并将文字作为照片名进行改名分类?基于QT和腾讯OCR的项目实战
在日常工作和生活中,我们常常需要处理大量的照片文件,这些照片中可能包含有用的文字信息。手动识别这些文字并对相应的照片进行重命名是一项繁琐且容易出错的工作。为了解决这一问题,本项目旨在开发一个基于QT和腾讯OCR(光学字符识别)技术的应用程序,实现批量识别照片中的文字并将识别出的文字作为照片的新文件名。
不负众望
2025/03/26
4140
如何批量识别出照片中的水印文字,并将文字作为照片名进行改名分类?基于QT和腾讯OCR的项目实战
C++ Qt开发:QTcpSocket网络通信组件
Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用QTcpSocket组件实现基于TCP的网络通信功能。
王 瑞
2024/03/18
9380
C++ Qt开发:QTcpSocket网络通信组件
Qt项目网络聊天室设计
3. 服务器接收到某个客户端的请求以及发送信息,经服务器发给其它客户端 最终实现一个共享聊天内容的聊天室!
DeROy
2020/08/19
2.5K0
Qt项目网络聊天室设计
C/C++ Qt 命令行版网络通信
实现简单的结构体传输: 两端传输结构体。 服务端 #include <QCoreApplication> #include <QTcpServer> #include <QTcpSocket> #include <iostream> struct MyStruct { char uname[7]; qint32 id; }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); MyStru
王 瑞
2022/12/28
7840
Qt学习之路_6(Qt局域网聊天软件)
http://www.cnblogs.com/tornadomeet/archive/2012/07/04/2576355.html
bear_fish
2018/09/20
3.5K0
Qt学习之路_6(Qt局域网聊天软件)
Qt实现客户端与服务器消息发送与文件传输(二)
客户端与服务器之间的数据传送在很多案例场景里都会有应用。这里Jungle用Qt来简单设计实现一个场景,即:
用户6557940
2022/07/24
2.4K0
Qt实现客户端与服务器消息发送与文件传输(二)
QT应用编程: 编写HC05串口蓝牙调试助手(Android系统APP)
完整工程源码下载地址(包含APK文件): https://download.csdn.net/download/xiaolong1126626497/19051787
DS小龙哥
2022/01/07
2.8K0
QT应用编程: 编写HC05串口蓝牙调试助手(Android系统APP)
Qt Socket 收发图片——图像拆包、组包、粘包处理(二)
之前给大家分享了一个使用python发图片数据、Qt server接收图片的Demo。之前的Demo用于传输小字节的图片是可以的,但如果是传输大的图片,使用socket无法一次完成发送该怎么办呢?本次和大家分享一个对大的图片拆包、组包、处理粘包的例子。
用户5908113
2019/08/08
2.7K0
Qt Socket 收发图片——图像拆包、组包、粘包处理(二)
Qt学习之路_5(Qt TCP的初步使用)
http://www.cnblogs.com/tornadomeet/archive/2012/06/30/2571001.html
bear_fish
2018/09/20
3.5K0
Qt学习之路_5(Qt TCP的初步使用)
Fdog系列(六):利用Qt通过服务端进行客户端与客户端通信(资料少,建议收藏)
Fdog系列(三):使用腾讯云短信接口发送短信,数据库写入,部署到服务器,web收尾篇。
花狗Fdog
2021/05/06
2K0
Qt | 安全的udp客户端搭建(代码框架值得学习)
通过网盘分享的文件:secureudpclient 链接: https://pan.baidu.com/s/1txCWIo7-WhM-CjVkp_aDdg?pwd=13j9 提取码: 13j9 【一定要转存】
Qt历险记
2024/12/15
3480
Qt | 安全的udp客户端搭建(代码框架值得学习)
基于Qt的UDP通信、TCP文件传输程序的设计与实现——QQ聊天群聊
QQ是一款优秀的聊天软件,本文将提供主要代码和思路来实现一个类似于QQ群聊的网络聊天软件,大致有以下俩个功能:
秋名山码神
2023/11/23
1.1K0
基于Qt的UDP通信、TCP文件传输程序的设计与实现——QQ聊天群聊
C++ Qt开发:QUdpSocket网络通信组件
Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用QUdpSocket组件实现基于UDP的网络通信功能。
王 瑞
2024/03/19
8620
C++ Qt开发:QUdpSocket网络通信组件
QT_使用TCP/IP传输文件
首选预览一下最终实现的效果,如下图: 一、服务器端 1、widget.h #ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QTcpServer>  //监听套接字 #include <QTcpSocket>  //通信套接字 #include <QFile> #include <QTimer> namespace Ui { class Widget; } class Widget : public QWidget {  
Sindsun
2019/12/06
1.4K0
【QT】qt 文件操作
文件操作是应用程序必不可少的部分。Qt 作为⼀个通用开发库,提供了跨平台的文件操作能力。 Qt 提供了很多关于文件的类,通过这些类能够对文件系统进行操作,如文件读写、文件信息获取、文件复制或重命名等。
YoungMLet
2024/07/30
4110
【QT】qt 文件操作
Qt应用开发--国产工业开发板全志T113-i的部署教程
Qt在工业上的使用场景包括工业自动化、嵌入式系统、汽车行业、航空航天、医疗设备、制造业和物联网应用。Qt被用来开发工业设备的用户界面、控制系统、嵌入式应用和其他工业应用,因其跨平台性和丰富的功能而备受青睐。
阿志小管家
2024/02/02
5730
Qt应用开发--国产工业开发板全志T113-i的部署教程
QT实现登录界面(利用MySQL保存数据和邮箱辅助注册)
       断断续续黑框框下的学生管理系统,也写了不下三次,总在黑框框下呆着也不是一回事,想挑战用qt做一个界面版的学生管理系统,至于为什么选用qt+C++,而不用C#之类的,是因为我目前学习C++,手头只接触了qt,所以想着挑战一下自己。从他人博客获取知识,为我所用,最后再输出到博客,供他人学习,这种感觉真的很棒。这几篇文章重在出效果,大牛勿喷,边学边做边发博,文中的代码可能不完整,我是拿出单个功能来说的,或许会出现错误,不用着急啦,需要用到的资源以及代码,我都会打包上传,如有需要的,可自行下载。
花狗Fdog
2020/10/28
6.8K0
QT实现登录界面(利用MySQL保存数据和邮箱辅助注册)
推荐阅读
相关推荐
Qt网络聊天室客户端
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验