Qt-Web混合开发-QWebSocket作为QWebChannel通信数据传输接口(10) 原创
Qt-Web混合开发-QWebSocket作为QWebChannel通信数据传输接口💙🍓
文章目录
更多精彩内容 |
---|
👉个人内容分类汇总 👈 |
👉Qt - Web混合开发👈 |
1、概述🐛🦆
- Qt版本:V5.12.5
- 关于WebSocket API可以看
- QWebChannel基本使用
- QWebChannel默认使用的传输通道为WebChannelIPCTransportHost,内部的数据传输接口为qtwebchannel::mojom::WebChannelTransportRenderAssociatedPtr;
- WebChannelIPCTransportHost所在文件:D:\Qt\Qt5.12.5\5.12.5\Src\qtwebengine\src\core\renderer_host\web_channel_ipc_transport_host.h;
- QWebChannel默认情况下使用需要将QwebChannel传入
QWebEnginePage::setWebChannel()
,只支持Qt与嵌入Qt的Web界面进行通信,如果该web界面是使用浏览器打开的则无法找到qt.webChannelTransport
对象;- 为了解决这种问题,Qt也有考虑到,只需要继承QWebChannelAbstractTransport,使用QWebSocket作为数据传输接口,就可以实现Qt界面与嵌入Qt的web界面、浏览器打开的web界面进行通信了;
- 并且使用QWebSocket作为数据传输接口可以不需要使用
QWebEnginePage::setWebChannel()
设置。
2、实现效果😅🙏
3、实现功能🐮🐴
- 构建后将html、css、js文件自动拷贝到可执行程序A路径下;
- 使用QWebSocket作为QWebChannel通信接口;
- 通过QWebEngineView在Qt界面中嵌入Web网页;
- 一键调用浏览器打开html文件;
- 基于WebSocket、QWebChannel.js实现网页和Qt通信功能;
- 同时演示了Qt和嵌入网页界面、浏览器网页界面通信功能;
- 支持一个服务端与多个客户端Web界面进行通信交互,将WebSocket服务端封装在QWebChannel内部,一个WebChannel对应一个服务端、多个客户端。
4、Qt部分关键代码💳🛣️🍐
- 工程文件结构
- pro文件
QT += webchannel websockets webenginewidgets
- widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "swebchannel.h"
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_but_send_clicked();
void on_webButClicked(QString message);
void on_but_openWeb_clicked();
private:
Ui::Widget *ui;
SWebChannel m_channel;
};
/**
* @brief Qt和Web端交互的中介单例类
*/
class Core : public QObject
{
Q_OBJECT
public:
static Core* getInstance()
{
static Core core;
return &core;
}
signals:
/**
* @brief Qt发送给Web的信号
* @param str
*/
void qtButClicked(QString str);
/**
* @brief Web发送给Qt的信号
* @param str
*/
void webButClicked(QString str);
public slots:
/**
* @brief Web端需要调用Qt槽函数来传递,必须声明为public slots,否则web找不到
* @param str
*/
void on_webButClicked(QString str)
{
emit webButClicked(str);
}
};
#endif // WIDGET_H
- widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QDesktopServices>
#include <qdir.h>
#include <qurl.h>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
this->setWindowTitle(QString("使用QWebSocket客户端作为QWebChannel通信数据传输接口 - V%1").arg(APP_VERSION)); // 设置窗口标题
connect(Core::getInstance(), &Core::webButClicked, this, &Widget::on_webButClicked);
m_channel.registerObject("CoreId", Core::getInstance()); // 向QWebChannel注册用于Qt和Web交互的对象。
m_channel.listen(QHostAddress::AnyIPv4, 1234); // 设置WebSocket服务端开始监听
// ui->webEngineView->page()->setWebChannel(&m_channel); // 将与webEngineView要使用的web通道实例设置为channel(使用QWebSocket作为数据传输接口可以不需要这一步)
ui->webEngineView->setUrl(QDir("./web3/channelClient.html").absolutePath());
}
Widget::~Widget()
{
delete ui;
}
/**
* 发送数据
*/
void Widget::on_but_send_clicked()
{
QString str = ui->lineEdit->text().trimmed();
if(str.isEmpty()) return;
emit Core::getInstance()->qtButClicked(str);
}
/**
* @brief 显示接收到的web端信息
* @param message
*/
void Widget::on_webButClicked(QString message)
{
ui->textEdit->append(message);
}
/**
* @brief 点击一次打开一个Web程序
*/
void Widget::on_but_openWeb_clicked()
{
QDesktopServices::openUrl(QDir("./web3/channelClient.html").absolutePath());
}
- swebchannel.h
/******************************************************************************
* @文件名 swebchannel.h
* @功能 通过继承QWebChannel,将创建管理WebSocket服务端功能,使用connectTo设置通信接口功能
* 封装在类内部,简化外部操作连接,一个SWebChannel对应一个WebSocket服务端、多个客户端
*
* @开发者 mhf
* @邮箱 1603291350@qq.com
* @时间 2022/12/19
* @备注
*****************************************************************************/
#ifndef SWEBCHANNEL_H
#define SWEBCHANNEL_H
#include <QWebChannel>
#include "websockettransport.h"
class SWebChannel : public QWebChannel
{
Q_OBJECT
public:
explicit SWebChannel(QObject *parent = nullptr);
bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);
signals:
private slots:
private:
WebSocketServer m_server;
QHostAddress m_address;
quint16 m_port;
};
#endif // SWEBCHANNEL_H
- swebchannel.cpp
#include "swebchannel.h"
SWebChannel::SWebChannel(QObject *parent) : QWebChannel(parent)
{
// 将连接成功的通道传递给QWebChannel
connect(&m_server, &WebSocketServer::newWebSocketTransport, this, &SWebChannel::connectTo);
}
/**
* @brief 设置WebSocket服务端监听
* @param address 监听的地址
* @param port 监听的端口号
* @return
*/
bool SWebChannel::listen(const QHostAddress &address, quint16 port)
{
// 如果新设置的监听地址、端口和已经监听的相同,则直接返回
if(m_address == address && m_port == port && m_server.isListening())
{
return true;
}
m_address = address;
m_port = port;
if(m_server.isListening()) // 如果已经在监听了则关闭已有监听,重新监听新的地址、端口
{
m_server.close();
}
return m_server.listen(address, port);
}
- websockettransport.h
#ifndef WEBSOCKETTRANSPORT_H
#define WEBSOCKETTRANSPORT_H
#include <QWebChannelAbstractTransport>
#include <qwebsocketserver.h>
class QWebSocket;
/**
* 通过继承QWebChannelAbstractTransport实现QWebChannel交互功能
* 使用QWebSocket作为通信接口
*/
class WebSocketTransport : public QWebChannelAbstractTransport
{
Q_OBJECT
public:
explicit WebSocketTransport(QWebSocket *client = nullptr);
~WebSocketTransport() override;
public slots:
void sendMessage(const QJsonObject &message) override;
void on_textMessageReceived(const QString &message);
private:
QWebSocket* m_client = nullptr;
};
/**
* WebSocket服务端程序,用于监听webSocket客户端连接,将连接的客户端使用
* WebSocketTransport包装后传递出去
*/
class WebSocketServer : public QWebSocketServer
{
Q_OBJECT
public:
explicit WebSocketServer(QObject *parent = nullptr);
signals:
void newWebSocketTransport(WebSocketTransport* transport);
private slots:
void on_newConnection();
};
#endif // WEBSOCKETTRANSPORT_H
- websockettransport.cpp
#include "websockettransport.h"
#include <qjsondocument.h>
#include <qwebsocket.h>
#include <QJsonObject>
WebSocketTransport::WebSocketTransport(QWebSocket *client)
: QWebChannelAbstractTransport(client)
, m_client(client)
{
// 当WebSocket收到信息后,将信息处理后并传递给QWebChannelAbstractTransport
connect(m_client, &QWebSocket::textMessageReceived, this, &WebSocketTransport::on_textMessageReceived);
// 当WebSocket断开连接后,释放当前对象
connect(m_client, &QWebSocket::disconnected, this, &WebSocketTransport::deleteLater);
connect(m_client, &QWebSocket::disconnected, [=]()
{
qDebug() << "Web端断开连接!";
});
}
WebSocketTransport::~WebSocketTransport()
{
if(m_client)
{
m_client->deleteLater();
}
}
/**
* @brief QWebChannelAbstractTransport中定义的接口,当使用QChannel发送信息时,数据传递到这里
* 在这个函数中处理需要发送的数据,并通过WebSocket发送出去
* @param message
*/
void WebSocketTransport::sendMessage(const QJsonObject &message)
{
QJsonDocument doc(message);
m_client->sendTextMessage(QString::fromUtf8(doc.toJson(QJsonDocument::Compact))); // 将需要发送的JSON数据转成字符串发送
}
/**
* @brief 通过WebSocket接收到网页javascript发来的信息时,
* 在这个函数中将字符串数据转换为JSON数据传递给QWebChannel
* @param message
*/
void WebSocketTransport::on_textMessageReceived(const QString &message)
{
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8(), &error);
if(error.error) // 字符串转JSON成功,error为0,失败则error > 0
{
qWarning() << QString("%1 无法将文本消息解析为JSON对象:%2").arg(error.errorString()).arg(message);
return;
}
if(!doc.isObject())
{
qWarning() << QString("收到的JSON消息不是JSON对象:%1").arg(QString(doc.toJson()));
}
emit messageReceived(doc.object(), this); // 将收到的信息发送给QWebChannel
}
WebSocketServer::WebSocketServer(QObject *parent)
: QWebSocketServer("QChannel通信接口服务", NonSecureMode, parent)
{
connect(this, &QWebSocketServer::newConnection, this, &WebSocketServer::on_newConnection);
}
/**
* @brief 当有新的客户端连接时,将连接的客户端通过WebSocketTransport类包装后传递出去
*/
void WebSocketServer::on_newConnection()
{
qDebug() << "有新的连接";
emit newWebSocketTransport(new WebSocketTransport(this->nextPendingConnection()));
}
5、Web部分关键代码👊👎😷🦠
- channelClient.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>QWebSocket客户端作为QWebChannel通信数据传输接口</title>
<script src="./qwebchannel.js"></script>
<script src="./channelClient.js"></script>
<link rel="stylesheet" type="text/css" href="channelClient.css">
</head>
<body>
<div>
<h1>使用QWebSocket客户端作为<br/>QWebChannel通信数据传输接口</h1>
<p>
<textarea name="textShow" id="textShowId" cols="60" rows="10"></textarea>
</p>
<p>
<input id="input"/> <button id="but_send" onclick="sendMessage()">发送</button>
</p>
</div>
</body>
</html>
- channelClient.js
// 关于WebSocket的用法可以看【https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket】
/**
* 创建一个程序加载时自动执行的函数
* 注意:由于该函数执行时WebSocket会连接Qt中的WebSocket服务端,WebChannel会获取Qt中注册的对象
* 所以需要先启动Qt程序,再启动当前web程序,否则无法连接(也可以写成手动调用)
*/
window.onload = function()
{
var url = "ws://localhost:1234"; // WebSocket服务端地址
var client = new WebSocket(url);
/**
* 连接关闭后的回调函数
*/
client.onclose = function()
{
showMessage("关闭连接");
}
/**
* 连接失败后的回调函数。
*/
client.onerror = function(err)
{
showMessage("连接失败:" + err.data);
}
/**
* 连接成功后的回调函数
*/
client.onopen = function()
{
showMessage("WebSocket连接成功");
// 将创建的websocket客户端传递给QWebChannel,并绑定通信接口
// 注意:这里默认情况传入的是qt.webChannelTransport,但是这里使用的是WebSocket通信,所以需要传入client
window.channel = new QWebChannel(client, function(channel)
{
// 获取Qt注册的对象,Qt中registerObject注册的字符串
window.core = channel.objects.CoreId;
// 将Qt注册对象的qtButClicked信号绑定到readMessage函数
core.qtButClicked.connect(readMessage);
showMessage("初始化WebChannel完成");
});
}
}
/**
* 显示接收到的信息
*/
function readMessage(message)
{
showMessage("收到:" + message);
}
/**
* 点击按键调用Qt注册对象中on_webButClicked槽函数将数据发送给Qt
*/
function sendMessage()
{
var text = document.getElementById("input").value;
if(!text)
{
return;
}
window.core.on_webButClicked(text);
}
/**
* 将信息显示到文本框
*/
function showMessage(msg)
{
var textShow = document.getElementById("textShowId");
textShow.value += msg + "\n"; // 追加信息
textShow.scrollTop = textShow.scrollHeight; // 滚动条显示最下方
}
6、源代码🐍🉐
. ஓ๑⸜💗⸝๑ஓ
ᕬ ᕬ ∧ ∧
(˵ㅇ◡ㅇ˵) (ᓀ ֊ ᓂ˵ )
(つ☕O O🍵⊂)