首页 > 解决方案 > 使用 libsoup-2.4 的 WebsocketConnection 有时会阻塞 GTK ui 线程并阻止打开主窗口

问题描述

我有一个用 Vala 编写的带有 websocket 连接的简单 GTK 应用程序(使用 libsoup-2.4)。问题:我的应用程序有时会在启动时冻结(每 10 次启动)并且根本不显示 gui 窗口,因为在套接字连接上阻塞了 libsoup 中的某个位置。

瓦拉 0.40.25 与

这里的代码:

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 连接。

标签: websocketgtkgtk3valalibsoup

解决方案


您正在使用异步方法,但它需要一些工作。制定你的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();

推荐阅读