Qt-Web混合开发-QWebSocket作为QWebChannel通信数据传输接口(10) 原创

Qt-Web混合开发-QWebSocket作为QWebChannel通信数据传输接口💙🍓

更多精彩内容
👉个人内容分类汇总 👈
👉Qt - Web混合开发👈

1、概述🐛🦆

  1. QWebChannel默认使用的传输通道为WebChannelIPCTransportHost,内部的数据传输接口为qtwebchannel::mojom::WebChannelTransportRenderAssociatedPtr;
  2. WebChannelIPCTransportHost所在文件:D:\Qt\Qt5.12.5\5.12.5\Src\qtwebengine\src\core\renderer_host\web_channel_ipc_transport_host.h;
  3. QWebChannel默认情况下使用需要将QwebChannel传入QWebEnginePage::setWebChannel(),只支持Qt与嵌入Qt的Web界面进行通信,如果该web界面是使用浏览器打开的则无法找到qt.webChannelTransport对象;
  4. 为了解决这种问题,Qt也有考虑到,只需要继承QWebChannelAbstractTransport,使用QWebSocket作为数据传输接口,就可以实现Qt界面与嵌入Qt的web界面、浏览器打开的web界面进行通信了;
  5. 并且使用QWebSocket作为数据传输接口可以不需要使用QWebEnginePage::setWebChannel()设置。

2、实现效果😅🙏

在这里插入图片描述

3、实现功能🐮🐴

  1. 构建后将html、css、js文件自动拷贝到可执行程序A路径下;
  2. 使用QWebSocket作为QWebChannel通信接口;
  3. 通过QWebEngineView在Qt界面中嵌入Web网页;
  4. 一键调用浏览器打开html文件;
  5. 基于WebSocket、QWebChannel.js实现网页和Qt通信功能;
  6. 同时演示了Qt和嵌入网页界面、浏览器网页界面通信功能;
  7. 支持一个服务端与多个客户端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🍵⊂)

posted @ 2024-08-07 17:33  mahuifa  阅读(0)  评论(0编辑  收藏  举报  来源