Qt

您所在的位置:网站首页 qq怎么合并转发送图片 Qt

Qt

2024-03-08 18:53| 来源: 网络整理| 查看: 265

Qt - 聊天室发送图片/文件 简介 好久没有发博客,上一篇的博客还是在上一份工作离职前整理的一篇博客。大半年没有发,一是工作繁忙,转成了音视频方向,新的工作内容暂时还不便发出来,二是不知道发什么内容,也没有整理。考虑了一下是python调用C库,但是整理起来比较费劲,想想就整理这个了 内容还相对少一点,比较有意思。 这个聊天室是我上一次的一个小项目,头像,签名,群聊,登录,图片发送等等相关功能,这次就单独说一下图片发送了。 思路 版本信息 1.Qt version: 5.12.2 2.没有使用第三库 关键点

CSDN不支持plantuml,贴了一下图

聊天室收发图片时序图

@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

在这里插入图片描述

关键点的选择

1.建立TCP连接 : QTcpSocket ,不做说明 2.选择图片 : QFileDialog 实现图片选择 3.发送图片 : 消息拼装,QTcpSocket -> write(QByteArray) 4.接收图片 : QTcpSocket -> readyRead(),消息解析 5.显示图片 : QWidget->show()

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

TCP连接建立 socket = new QTcpSocket; socketState = false; //ipAddressStr ip地址 //port 端口号 if(!socketState) { socket->connectToHost(ipAddressStr, port); if(socket->waitForConnected(3000)) { qDebug() pushButtonConnect->setText("连接成功"); socketState = true; } else { qDebug() errorString(); return; } } else { socket->close(); //触发disconnected()信号 ui->pushButtonConnect->setText("断开连接"); socketState = false; } TCP接收数据 connect(socket, SIGNAL(readyRead()),this, SLOT(readyReadSlot())); //接收消息 //接收数据槽函数 void Widget::readyReadSlot() { QByteArray data = socket->readAll(); byteArray += data; //当前socket接收数据缓冲区,将新来的数据添加到数据缓冲区末尾 emit sign_recvData(); //触发数据解析事件 } TCP发送数据 void Widget::sendMsg(QString msg) { if(socket->isOpen() && socket->isValid()) { QByteArray _bufByteArry; //msg -> _bufByteArry : QString 转为 QByteArray socket->write(_bufByteArry); } } 图片选择 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)); } 图片保存 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,来处理实际的有效用户数据。

数据结构如下所示:

{ "sendname" : "username", "recvname" : "username", "msgtype" : 0, //在实际的业务处理中,消息类型只包含两种数据, 文本数据,图片数据 "msgdata" : "data" } { "type" : "", //消息类型 "length" : "", //数据长度 "data" : "" //数据内容 }

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

{ "type" : "user_msg", "length" : "", "data" : " { \"sendname\" : \"username\", \"recvname\" : \"username\", \"msgtype\" : 0, \"msgdata\" : \"data\"} " }

结构体内容如下所示:

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; } } }; 校验数据

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

//下边的数据结构就是类似的抽象概念 struct NetMsgHeader { int startID; int length; }; struct NetMsgEnd { int endID; }; struct NetMsg { NetMsgHeader header; QString msg; NetMsgEnd end; }; #define MSG_HEAD_ID 123456 //定义包头 #define MSG_END_ID 654321 //定义包尾 组包数据 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()


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3