在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
运行脚本。