在Linux下编写GTK + GUI来管理其他进程时遇到问题。我绝对不是GTK +的专家,而且我似乎无法解决此问题。

我正在尝试编写一个GTK +应用程序,该应用程序应运行其他进程(尤其是iPerf-网络测量程序-客户端和iPerf服务器,这些进程使用system()popen()/pclose()进行管理,具体取决于单击的按钮)用户。

有一些与启动客户端有关的按钮,还有两个用于启动和停止服务器的按钮,它们分别调用各自的回调。

特别是服务器启动按钮,调用一个回调,该回调负责启动线程,该线程应从服务器读取数据(异步退出)并相应地更新GUI的一部分,而GUI应响应执行其他操作(例如启动客户)。

特别是,iPerf设置为每1秒输出一个新数据,并且每个数据每秒钟位于iPerf返回的每一行。

我尝试使用popen()从服务器读取数据。

如果我使用serverParserIdle()从GTK +回调中启动gdk_threads_add_idle()函数(如下所述),则它可以正常工作,但是有两个大问题阻止了程序正常运行:

1)iPerf输出由popen()缓冲,并且不实时解析数据,因为程序应该这样做

2)serverParserIdle()线程锁定了GUI,并且我无法同时执行其他操作,例如运行客户端,这是我需要做的事情

尝试解决(2),我尝试使用gdk_threads_add_idle()gdk_threads_add_timeout(1000,...)进行更改。在这种情况下,GUI不再处于锁定状态,但是popen返回0且服务器未启动。你知道为什么吗?

我该怎么办才能解决上面列出的所有问题?

这是前面提到的serverParserIdle()函数:

static gboolean serverParserIdle(gpointer data) {
    FILE *iperfFp;
    char linebuf[STRSIZE_LINEBUF];
    double goodput, final_goodput;
    char unit_letter;
    int total_datagrams, prev_total_datagrams=-1;
    struct parser_data *parser_data_struct=data;
    gchar *gput_label_str=NULL, *final_gput_label_str=NULL;
    char first_char;


    iperfFp=popen(parser_data_struct->cmd,"r"); //parser_data_struct->cmd contains a string containing the command to launch the iperf server "iperf -s -u -i 1 ..."

    if(!iperfFp) {
        // We enter here if gdk_threads_add_timeout(1000,...) is used to call serverParserIdle()
        return FALSE;
    }

    while(fgets(linebuf,sizeof(linebuf),iperfFp)!=NULL) {
        sscanf(linebuf,"%c %*s %*s %*f %*s %*f %*s %lf %c%*s %*f %*s %*s %d %*s",&first_char,&goodput,&unit_letter,&total_datagrams); // Parse useful data on this line

        if(first_char!='[' || (unit_letter!='K' && unit_letter!='M')) {
            // This is just to discrimate the useful lines
            continue;
        }

        if(unit_letter=='K') {
            goodput=goodput/1000;
        }

        // This is again a way to distinguish the last line of a client-server session from all the other lines
        if(prev_total_datagrams!=-1 && total_datagrams>prev_total_datagrams*2) {
            if(final_gput_label_str) {
                g_free(final_gput_label_str);
            }
            // Update final goodput value in the GUI
            final_goodput=goodput;
            prev_total_datagrams=-1;
            final_gput_label_str=g_strdup_printf("<b><span font=\"70\" foreground=\"blue\">%.2f</span></b>",goodput);
            gtk_label_set_text(GTK_LABEL(parser_data_struct->gput_labels.final_gput_info_label),final_gput_label_str);
        } else {
            if(gput_label_str) {
                g_free(gput_label_str);
            }
            prev_total_datagrams=total_datagrams;

            // Update current goodput value in the GUI (every 1s only when a client is being connected to the server)
            gput_label_str=g_strdup_printf("<b><span font=\"70\" foreground=\"#018729\">%.2f</span></b>",goodput);
            gtk_label_set_text(GTK_LABEL(parser_data_struct->gput_labels.gput_info_label),gput_label_str);
        }

        //fflush(iperfFp); <- tried flushing, but it does not work
    }

    pclose(iperfFp);

    g_free(gput_label_str);
    g_free(final_gput_label_str);


    return FALSE;
}

实际上,从回调(gdk_threads_add_idle())调用gdk_threads_add_timeout()start_server(),该回调使用以下命令分配给main()中的按钮:
g_signal_connect(button,"clicked",G_CALLBACK(start_server),&(data));

提前非常感谢您。

最佳答案

这是Perl中的示例,以防有人感兴趣。这仅显示了如何在GTK事件循环内执行异步操作的基本原理:

#! /usr/bin/env perl
use feature qw(say);
use strict;
use warnings;

use Glib 'TRUE', 'FALSE';
use Gtk3 -init;
use AnyEvent;  # Important: load AnyEvent after Glib!
use AnyEvent::Subprocess;

use constant {
    GTK_STYLE_PROVIDER_PRIORITY_USER => 800,
};

my $window = Gtk3::Window->new( 'toplevel' );
my $grid1 = Gtk3::Grid->new();
$window->add( $grid1 );
my $frame1 = Gtk3::Frame->new('Output');
$frame1->set_size_request(800,600);
$grid1->attach($frame1, 0,0,1,1);
my $scrolled_window = Gtk3::ScrolledWindow->new();
$scrolled_window->set_border_width(5);
$scrolled_window->set_policy('automatic','automatic');
my $textview = Gtk3::TextView->new();
my $buffer = $textview->get_buffer();
$buffer->set_text ("Hello, this is some text\nHello world\n");
$textview->set_wrap_mode('none');
$textview->set_editable(FALSE);
$textview->set_cursor_visible(FALSE);
set_widget_property( $textview, 'font-size', '18px' );
my $bg_color = Gtk3::Gdk::RGBA::parse( "#411934" );
$textview->override_background_color('normal', $bg_color);
my $color = Gtk3::Gdk::RGBA::parse( "#e9e5e8" );
$textview->override_color('normal', $color);
$textview->set_monospace(TRUE);

$scrolled_window->add($textview);
$frame1->add($scrolled_window);
$window->set_border_width(5);
$window->set_default_size( 600, 400 );
$window->set_position('center_always');
$window->show_all();
setup_background_command( $buffer );  # start background command
my $condvar = AnyEvent->condvar;
$window->signal_connect( destroy  => sub { $condvar->send } );
my $done = $condvar->recv;  # enter main loop...

sub setup_background_command {
    my ( $buffer ) = @_;

    my $job = AnyEvent::Subprocess->new(
        delegates     => [ 'StandardHandles', 'CompletionCondvar' ],
        code          => sub { exec 'unbuffer', 'myscript.pl' }
    );
    my $run = $job->run;
    $run->delegate('stdout')->handle->on_read(
        sub {
            my ( $handle ) = @_;
            my $line = $handle->rbuf;
            chomp $line;
            my $iter = $buffer->get_end_iter();
            $buffer->insert( $iter, $line . "\n" );
            $handle->rbuf = ""; # clear buffer
        }
    );
}

sub set_widget_property {
    my ( $widget, $prop, $value ) = @_;

    my $context = $widget->get_style_context();
    my $cls_name = $prop . '_class';
    $context->add_class( $cls_name );
    my $provider = Gtk3::CssProvider->new();
    my $css = sprintf ".%s {%s: %s;}", $cls_name, $prop, $value;
    $provider->load_from_data( $css );
    $context->add_provider($provider, GTK_STYLE_PROVIDER_PRIORITY_USER);
}

在这里,要在GTK事件循环中异步运行的命令是脚本myscript.pl:
#! /usr/bin/env perl
use feature qw(say);
use strict;
use warnings;

#STDOUT->autoflush(1);
sleep 1;
say "data 1";
sleep 1;
say "data 2";
sleep 1;
say "data 3";

注意,可以通过用autoflush(1)取消注释该行来使脚本无缓冲。但是总的来说,我们必须假设我们无法修改命令的内部,因此我使用 unbuffer 运行脚本。

09-30 14:47
查看更多