Fork me on GitHub

QT5.12 C++与前端JS/HTML实现通信交互

"WebSocket connection to 'ws://localhost:12345/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED"
最近在公司实习,使用QT开发需要导出分析报告,选择使用HTML&JS进行页面渲染,那么问题来了,如何实现 C++ QT 和 JavaScript 的通信呢?

前言

WebEngineView是 QT5 新提供的web通信方法,可以实现服务端监听本地端口和客户端实现数据交互。

参考 QT 5.12 官方文档: WebEngineView QML Type

本篇博客将结合官方文档 StandAlone 示例以及自己实际开发中遇到的问题进行分析,如有不当之处欢迎批评指教。

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int main(int argc, char** argv)
{
QApplication app(argc, argv);

QFileInfo jsFileInfo(QDir::currentPath() + "/qwebchannel.js");

if (!jsFileInfo.exists())
QFile::copy(":/qtwebchannel/qwebchannel.js",jsFileInfo.absoluteFilePath());

// setup the QWebSocketServer
QWebSocketServer server(QStringLiteral("QWebChannel Standalone Example Server"), QWebSocketServer::NonSecureMode);
if (!server.listen(QHostAddress::LocalHost, 12345)) {
qFatal("Failed to open web socket server.");
return 1;
}

// wrap WebSocket clients in QWebChannelAbstractTransport objects
WebSocketClientWrapper clientWrapper(&server);

// setup the channel
QWebChannel channel;
QObject::connect(&clientWrapper, &WebSocketClientWrapper::clientConnected,
&channel, &QWebChannel::connectTo);

// setup the UI
Dialog dialog;

// setup the core and publish it to the QWebChannel
Core core(&dialog);
channel.registerObject(QStringLiteral("core"), &core);

// open a browser window with the client HTML page
QUrl url = QUrl::fromLocalFile(BUILD_DIR "/index.html");
QDesktopServices::openUrl(url);

dialog.displayMessage(Dialog::tr("Initialization complete, opening browser at %1.").arg(url.toDisplayString()));
dialog.show();

return app.exec();
}

其实官方文档注释已经很清晰了,首先新建一个APP,然后连接官方提供的 qwebchannel.js 文件(这一步在大型项目中与示例可能不同)。

接着进入正题,开启服务器监听端口,这里是端口12345,如果出错(端口被占用,往往是已经有程序开启监听此端口)则发出错误信息。

然后是声明一个 QT 提供的WebSocketClientWrapper类型,它的内部还包含了一个自定义的类WebSocketTransport,这边就不进行深入剖析了,感兴趣可以自己分析一下它们的代码。

接下来是建立通道连接,声明 QT 提供的QWebChannel对象,利用connect函数将信号和槽进行连接以实现通信连接。

下面的几段代码主要涉及UI界面了,standalone 提供的是服务端使用QT界面,客户端是打开的HTML网页。

接下来的Core个人认为用处不大,因为我们实际开发中使用的是自己的界面类(比如我这次使用的是一个打印报告界面),比较重要的是需要将它发给QWebChannel,

channel.registerObject(QStringLiteral("core"), &core);

双引号里的字符串(名称)需要和JavaScript中使用的相同。

后面就是索引文件,打开这个页面。

最后显示服务端界面。

客户端

官网示例中 JavaScript 代码直接写在HTML中,摘取如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<script type="text/javascript" src="./qwebchannel.js"></script>
<script type="text/javascript">
//BEGIN SETUP
function output(message) {
var output = document.getElementById("output");
output.innerHTML = output.innerHTML + message + "\n";
}
window.onload = function() {
if (location.search != "")
var baseUrl = (/[?&]webChannelBaseUrl=([A-Za-z0-9\-:/\.]+)/.exec(location.search)[1]);
else
var baseUrl = "ws://localhost:12345";
output("Connecting to WebSocket server at " + baseUrl + ".");
var socket = new WebSocket(baseUrl);

socket.onclose = function() {
console.error("web channel closed");
};
socket.onerror = function(error) {
console.error("web channel error: " + error);
};
socket.onopen = function() {
output("WebSocket connected, setting up QWebChannel.");
new QWebChannel(socket, function(channel) {
// make core object accessible globally
window.core = channel.objects.core;

document.getElementById("send").onclick = function() {
var input = document.getElementById("input");
var text = input.value;
if (!text) {
return;
}

output("Sent message: " + text);
input.value = "";
core.receiveText(text);
}

core.sendText.connect(function(message) {
output("Received message: " + message);
});

core.receiveText("Client connected, ready to send/receive messages!");
output("Connected to WebChannel, ready to send/receive messages!");
});
}
}
//END SETUP
</script>

其中,8-14行是建立连接,如果连接失败,就会有16-21行的报错,如果一切正常,就会显示”WebSocket connected, setting up QWebChannel.”,那么恭喜你,任务已经实现大半了,接下来就是根据 QWebChannel 来进行数据交互实现自己需要的功能了。具体的请参考上面的代码以及 JavaScript 的内容。

遇到的问题

在这次建立通讯开发中我遇到的主要问题就是无法建立连接,也就是开头所写的错误:

"WebSocket connection to 'ws://localhost:12345/' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED"

从三个方面进行考虑:本地、服务端、客户端。

一开始以为是监听本地端口被占用等问题,在换了几个端口并且停止本地防火墙后仍然不行,网上的解答是端口关闭,但我在专门开启本地相关端口仍然未解决。

后来靠官方示例排除了是本地原因,因为我开发是使用 Visual Studio 2017 + QT5.11,一开始不能立即很方便地运行官方示例,后来安装了一个QT Creator,发现可以直接选取 example 里面的standalone打开编译成功运行,非常的方便。

那么既然官方示例可以正常运行,看来不是电脑的问题了,将客户端完全换成官方示例的HTML文件,排除客户端。

所以问题肯定出在服务端,也就是QT代码。我又一行一行地研究代码,先努力使我的代码和官方示例尽可能地接近(包括语句顺序等等),梳理了几遍仍然不成功。

最后突然发现standalone的服务端搭建是写在 main 函数中,而我开发的代码是写在打印按钮触发函数之中,在触发结束后便会被释放,也就是说服务端会停止监听端口,难怪客户端无法建立连接!

遂更改之,终于解决问题。(其中还牵扯到一些在哪里声明如何释放内存等问题这里就不一一细说了)

这件事也给我后面开发起到了很大帮助,会更多地关注函数的生命周期,也对C++面向对象有了更深的理解,学习了用智能指针管理对象实现内存释放等等。

最后,感谢公司的二师兄对我的耐心指导!开发路还很长,我还需要向有多年经验的师兄多多学习。

参考链接

QT 5.12 官方文档: WebEngineView QML Type

Qt WebChannel Standalone Example

跟着例子学Qt–2.standalone( C++ QWebChannel server and a HTML/JS client)

文章作者:Jinguo Dong (董金国)

最后更新:2018年12月28日 21:12:21

原始链接:http://blog.djinguo.com/2018-12-28/qtwithjs/

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 3.0 许可协议,转载请注明出处!

0%