websocket - 使用 libsoup-2.4 的 WebsocketConnection 有时会阻塞 GTK ui 线程并阻止打开主窗口
问题描述
我有一个用 Vala 编写的带有 websocket 连接的简单 GTK 应用程序(使用 libsoup-2.4)。问题:我的应用程序有时会在启动时冻结(每 10 次启动)并且根本不显示 gui 窗口,因为在套接字连接上阻塞了 libsoup 中的某个位置。
瓦拉 0.40.25 与
- gtk+-3.0
- glib-2.0
- gobject-2.0
- libsoup-2.4
这里的代码:
using Gtk;
class WebsocketConnection {
private Soup.WebsocketConnection websocket_connection;
public signal void ws_message(int type, string message);
public signal void connection_succeeded();
public signal void connection_established();
public signal void connection_failed();
public signal void connection_disengaged();
private string host;
public WebsocketConnection(string host) {
this.host = host;
}
private static string decode_bytes(Bytes byt, int n) {
return (string)byt.get_data();
}
public void init_connection_for(string host) {
MainLoop loop = new MainLoop();
var socket_client = new Soup.Session();
string url = "ws://%s:8080/".printf(host);
message(@"connect to $url");
var websocket_message = new Soup.Message("GET", url);
socket_client.websocket_connect_async.begin(websocket_message, null, null, null, (obj, res) => {
try {
websocket_connection = socket_client.websocket_connect_async.end(res);
message("Connected!");
connection_succeeded();
if (websocket_connection != null) {
websocket_connection.message.connect((type, m_message) => {
ws_message(type, decode_bytes(m_message, m_message.length));
});
websocket_connection.closed.connect(() => {
message("Connection closed");
connection_disengaged();
});
}
} catch (Error e) {
message("Remote error: " + e.message + " " + e.code.to_string());
connection_failed();
loop.quit();
}
loop.quit();
});
loop.run();
}
}
class Main : Gtk.Application {
public Main() {
Object(
application_id: "com.github.syfds.websocket-libsoup-vala-example",
flags : ApplicationFlags.FLAGS_NONE
);
}
protected override void activate() {
var window = new ApplicationWindow(this);
window.title = "Hello, World!";
window.border_width = 10;
window.window_position = WindowPosition.CENTER;
window.set_default_size(350, 70);
window.destroy.connect(Gtk.main_quit);
var grid = new Grid();
grid.orientation = Orientation.VERTICAL;
grid.column_spacing = 5;
grid.row_spacing = 5;
var main_label = new Label("...");
var websocket_host_input = new Entry();
var send_message_btn = new Button.with_label("Connect");
var websocket_host = "192.168.1.252";
var connection = new WebsocketConnection(websocket_host);
connection.connection_succeeded.connect(() => {
message("Connection succeeded");
main_label.set_text("Connection succeeded");
});
connection.connection_failed.connect(() => {
message("Connection failed");
});
connection.ws_message.connect((type, msg) => {
message("message received " + msg);
});
connection.init_connection_for(websocket_host);
grid.add(main_label);
window.add(grid);
window.show_all();
}
public static int main(string[] args) {
var app = new Main();
return app.run(args);
}
}
如果我通过 gdb 连接到进程,我会看到以下图片:
(gdb) info thr
Id Target Id Frame
* 1 Thread 0x7f38e6cbbac0 (LWP 29885) "com.github.syfd" 0x00007f38e53098f6 in __libc_recv (
fd=14, buf=0x55890440bc00, len=1024, flags=0) at ../sysdeps/unix/sysv/linux/recv.c:28
2 Thread 0x7f38d5d1c700 (LWP 29886) "gmain" 0x00007f38e52fbcb9 in __GI___poll (
fds=0x558903d13b40, nfds=2, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29
3 Thread 0x7f38d551b700 (LWP 29887) "gdbus" 0x00007f38e52fbcb9 in __GI___poll (
fds=0x558903d30760, nfds=2, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29
4 Thread 0x7f38cffff700 (LWP 29888) "dconf worker" 0x00007f38e52fbcb9 in __GI___poll (
fds=0x558903f6ccb0, nfds=1, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29
(gdb) bt
#0 0x00007f38e53098f6 in __libc_recv (fd=14, buf=0x55890440bc00, len=1024, flags=0)
at ../sysdeps/unix/sysv/linux/recv.c:28
#1 0x00007f38e5eb54f4 in () at /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0
#2 0x00007f38e5667558 in () at /usr/lib/x86_64-linux-gnu/libsoup-2.4.so.1
#3 0x00007f38e59173a5 in g_main_context_dispatch () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#4 0x00007f38e5917770 in () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#5 0x00007f38e59177fc in g_main_context_iteration () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#6 0x00007f38e5ed8f3d in g_application_run () at /usr/lib/x86_64-linux-gnu/libgio-2.0.so.0
#7 0x0000558902d69e8f in main_main (args=0x7ffeb2b775f8, args_length1=1)
at /home/sergej/workspace/websocket-libsoup-vala-example-v2/src/Main.vala:121
#8 0x0000558902d69ed2 in main (argc=1, argv=0x7ffeb2b775f8)
at /home/sergej/workspace/websocket-libsoup-vala-example-v2/src/Main.vala:119
看起来__libc_recv at ../sysdeps/unix/sysv/linux/recv.c:28
这里被阻塞了,但我不明白为什么以及如何以非阻塞方式进行连接。
我感谢任何帮助/提示如何解决阻塞 UI 的问题或如何使用 libsoup 创建非阻塞 websocket 连接。
解决方案
您正在使用异步方法,但它需要一些工作。制定你的init_connection_for
方法async
:
public async void init_connection_for(string host) { ... }
然后yield
调用以建立 websocket 连接:
var websocket_connection = yield socket_client.websocket_connect_async(websocket_message, null, null, null);
然后你调用你的函数在开始之前进行设置,然后它会产生并被异步连接回调:
connection.init_connection_for.begin(websocket_host);
然后,您还需要使用 GIO 编译--pkg gio-2.0
异步方法。
这将使您继续前进,但还有很多事情可以做来简化和重新构建您的代码。例如,您不需要额外MainLoop
的。
这是帮助您进步的变化的差异:
--- original.vala 2021-08-08 15:59:41.757378207 +0100
+++ modifed.vala 2021-08-08 17:18:58.680837130 +0100
@@ -18,35 +18,33 @@
return (string)byt.get_data();
}
- public void init_connection_for(string host) {
+ public async void init_connection_for(string host) {
MainLoop loop = new MainLoop();
var socket_client = new Soup.Session();
string url = "ws://%s:8080/".printf(host);
message(@"connect to $url");
var websocket_message = new Soup.Message("GET", url);
- socket_client.websocket_connect_async.begin(websocket_message, null, null, null, (obj, res) => {
- try {
- websocket_connection = socket_client.websocket_connect_async.end(res);
- message("Connected!");
-
- connection_succeeded();
- if (websocket_connection != null) {
- websocket_connection.message.connect((type, m_message) => {
- ws_message(type, decode_bytes(m_message, m_message.length));
- });
- websocket_connection.closed.connect(() => {
- message("Connection closed");
- connection_disengaged();
- });
- }
- } catch (Error e) {
- message("Remote error: " + e.message + " " + e.code.to_string());
- connection_failed();
- loop.quit();
+ var websocket_connection = yield socket_client.websocket_connect_async(websocket_message, null, null, null);
+ try {
+ message("Connected!");
+
+ connection_succeeded();
+ if (websocket_connection != null) {
+ websocket_connection.message.connect((type, m_message) => {
+ ws_message(type, decode_bytes(m_message, m_message.length));
+ });
+ websocket_connection.closed.connect(() => {
+ message("Connection closed");
+ connection_disengaged();
+ });
}
+ } catch (Error e) {
+ message("Remote error: " + e.message + " " + e.code.to_string());
+ connection_failed();
loop.quit();
- });
+ }
+ loop.quit();
loop.run();
}
@@ -94,7 +92,7 @@
message("message received " + msg);
});
- connection.init_connection_for(websocket_host);
+ connection.init_connection_for.begin(websocket_host);
grid.add(main_label);
window.add(grid);
window.show_all();
推荐阅读
- xrandr - 在 VNC 中使用 xrandr 进行持久解析
- c# - 如何在 Moq 中短路属性设置器的调用
- html - 添加 index.html 以外的其他页面
- python - 如何将表导出到具有字段值作为文件名而不是部分的文件中
- c# - ASP Core HttpClientFactory 模式使用客户端证书
- php - 页面加载时的 Google reCAPTCHA 问题
- yahoo-finance - 如何从 URL 获取 Yahoo Finance 历史收盘价
- node.js - 错误:初始化会话后发送标头后无法设置标头
- sql - 连续对行的平均值
- javascript - 如何在没有 d.ts 文件的情况下导入第三方包?