2008年11月19日星期三

Perl Socket设置有效的timeout

不论使用LWP还是IO::Socket,timeout参数都是一个古怪的问题,它要么不起作用,要么有很大的局限性,比如只有在目标地址能够连通,但 Socket无法建立的情况下才有效,如果完全连不上目标地址,程序就会阻塞,timeout设置的时间不起作用,这种情况一般叫做DNS解析错误,即使是用ip连接也一样。
要实现完全可控制的timeout连接,常见的办法是使用alarm:

#!/usr/bin/perl -w

use strict;
use IO::Socket::INET;

my $timeout = 5;

eval
{
local $SIG{ALRM} = sub { die 'Timed Out'; };
alarm $timeout;
my $sock = IO::Socket::INET->new(
PeerAddr => 'somewhere',
PeerPort => '80',
Proto => 'tcp',
## timeout => ,
);

$sock->autoflush(1);

print $sock "GET / HTTP/1.0\n\n";

undef $/;
my $data = <$sock>;
$/ = "\n";

print "Resp: $data\n";

alarm 0;
};

alarm 0; # race condition protection
print "Error: timeout." if ( $@ && $@ =~ /Timed Out/ );
print "Error: Eval corrupted: $@" if $@;


但这在Win32中似乎没有效果,其实比较合理的做法是在Socket创建时不设定目标地址,然后将Socket设置为非阻塞模式,最后再连接地址:

#!/usr/bin/perl

use strict;
use IO::Socket::INET;
use IO::Select;
use IO::Handle;

BEGIN
{
if($^O eq 'MSWin32')
{
eval '*EINPROGRESS = sub { 10036 };';
eval '*EWOULDBLOCK = sub { 10035 };';
eval '*F_GETFL = sub { 0 };';
eval '*F_SETFL = sub { 0 };';
*IO::Socket::blocking = sub
{
my ($self, $blocking) = @_;
my $nonblocking = $blocking ? 0 : 1;
ioctl($self, 0x8004667e, \$nonblocking);
};
}
else
{
require Errno;
import Errno qw(EWOULDBLOCK EINPROGRESS);
}
}

my $socket;
my $timeout = 5;

if (!($socket = IO::Socket::INET->new(
Proto => "tcp",
Type => SOCK_STREAM) ))
{
print STDERR "Error creating socket: $@";
}

$socket->blocking(0);

my $peeraddr;
if(my $inetaddr = inet_aton("somewhere"))
{
$peeraddr = sockaddr_in(80, $inetaddr);
}
else
{
print STDERR "Error resolving remote addr: $@";
}

$socket->connect($peeraddr);
$socket->autoflush(1);

my $select = new IO::Select($socket);

if($select->can_write($timeout))
{
my $req = "GET / HTTP/1.0\n\n";
print $socket $req;

if($select->can_read($timeout))
{
my $resp;
if($resp = scalar <$socket>)
{
chomp $resp;
print "Resp: $resp\n";
}
}
else
{
print "Response timeout.\n";
}
}
else
{
print "Connect timeout.\n";
}

close $socket;
exit;


由于在Win32中不能直接使用blocking(0),所以用ioctl进行设置,以上方法在Linux和Win32中都能正常工作,但如在Win32中把IO::Socket::INET换成IO::Socket::SSL就不行了,后来我去perlmonks问了这个问题,但并没有得到解决:
http://www.perlmonks.org/?node_id=676887

GTK2-Perl程序示例:远程桌面客户端gtkremote

这个例子说明了如何将Glade界面直接放在程序中方便在邮件列表和论坛上直接发布程序,以及如何用Gtk2::Helper调用网络程序防止连接超时导致界面阻塞。程序中用Expect实现了VNC密码输入。


#! /usr/bin/perl -w

#----------------------------------------------------------------------
# gtkremote.pl
#
# A remote desktop front-end of Gtk2/GladeXML
#
# Copyright (C) 2008 Viperii <hominid@tom.com>
# Licensed under the GPL
#
#----------------------------------------------------------------------

use strict;
use warnings;

use Glib qw/TRUE FALSE/;
use Gtk2 '-init';
use Gtk2::Helper;
use Gtk2::GladeXML;

use FindBin qw($Bin);
use Expect;

my $CMD;
my $PID;
my $TAG;
my $CONN = 0;
my $PREFIX = '@PREFIX@';
my $BIN_DIR = (-d $PREFIX ? "$PREFIX/bin" : $Bin);
my $VNC_TMP = "/tmp/vncpasswd.tmp";

$| = 1;

my $glade_data; {local $/ = undef; $glade_data = <DATA>;}
my $gladexml = Gtk2::GladeXML->new_from_buffer($glade_data);
#my $gladexml = Gtk2::GladeXML->new($BIN_DIR.'/gtkremote.glade');
$gladexml->signal_autoconnect_from_package('main');

my $window = $gladexml->get_widget('main');
my $host_entry = $gladexml->get_widget('entry1');
my $port_entry = $gladexml->get_widget('entry2');
my $user_entry = $gladexml->get_widget('entry3');
my $pass_entry = $gladexml->get_widget('entry4');
my $combobox = $gladexml->get_widget('combobox1');
my $button = $gladexml->get_widget('button1');
my $status_icon = $gladexml->get_widget('image2');

my @prot_list = ("VNC", "RDP");
$combobox->append_text($_) foreach (@prot_list);
$combobox->set_active(0);

$window->show_all();
Gtk2->main();
$window->destroy;
exit 0;

sub on_main_delete_event
{
kill 15 => $PID if $PID;
unlink $VNC_TMP if -f $VNC_TMP;
Gtk2->main_quit;
exit;
}

sub on_combobox1_changed
{
$combobox->get_active_text eq "VNC" ? $user_entry->set_sensitive(FALSE) : $user_entry->set_sensitive(TRUE);
}

sub on_button1_clicked
{
unless($CONN)
{
remote_connect();
}
else
{
kill 15 => $PID if $PID;
}
}

sub status_connect
{
$CONN = 1;
$button->child->set_text("Disconnect");
$status_icon->set_from_stock('gtk-connect', 'large-toolbar');
}

sub status_disconnect
{
$CONN = 0;
$button->child->set_text("Connect");
$status_icon->set_from_stock('gtk-disconnect', 'large-toolbar');
}

sub message_dialog_show
{
#$icon can be: a) 'info'
# b) 'warning'
# c) 'error'
# d) 'question'
#$button_type can be: a) 'none'
# b) 'ok'
# c) 'close'
# d) 'cancel'
# e) 'yes-no'
# f) 'ok-cancel'

my ($parent,$icon,$text,$button_type) = @_;

my $dialog = Gtk2::MessageDialog->new_with_markup ($parent,
[qw/modal destroy-with-parent/],
$icon,
$button_type,
sprintf "$text");
my $retval = $dialog->run;
$dialog->destroy;
return $retval;
}

sub remote_connect
{
status_connect();

my $host = $host_entry->get_text;
my $port = $port_entry->get_text;
my $user = $user_entry->get_text;
my $pass = $pass_entry->get_text;
my $prot = $combobox->get_active_text;

if($host)
{
if($prot eq "VNC")
{
unlink $VNC_TMP if -f $VNC_TMP;

$CMD = "vncviewer $host";
$CMD .= "::$port" if $port =~ /^(\d+)$/;
if(length($pass) >= 6)
{
my $sh = Expect->spawn('/bin/sh') or die "Cannot spawn shell: $!\n";
my $exp = new Expect;
my $command = "vncpasswd $VNC_TMP";
$exp->spawn($command) or die "Cannot spawn $command: $!\n";
$exp->expect(10, 'Password:');
$exp->send("$pass\n");
$exp->expect(10, 'Verify:');
$exp->send("$pass\n");
$exp->interact();
$exp->soft_close();
$sh->hard_close();

$CMD .= " -passwd $VNC_TMP";
}
else
{
message_dialog_show($window,
'error',
'VNC password too short.',
'ok'
);
status_disconnect();
return;
}
}
elsif($prot eq "RDP")
{
$CMD = "rdesktop $host";
$CMD .= ":$port" if $port =~ /^(\d+)$/;
$CMD .= " -u $user" if $user;
$CMD .= " -p $pass" if $pass;
}
else
{
status_disconnect();
return;
}
}
else
{
status_disconnect();
return;
}

$PID = open my $pipe, '-|', "$CMD" or die "Failed open pipe: $CMD\n";

$TAG = Gtk2::Helper->add_watch(fileno($pipe), in => sub
{
if(eof($pipe))
{
Gtk2::Helper->remove_watch($TAG);
close($pipe);
unlink $VNC_TMP if -f $VNC_TMP;
status_disconnect();
0;
}
else
{
#my $line = <$pipe>; # stdout string.
#print "OUTPUT: ".$line."\n" if $line;
}

1;
} );
}

__DATA__
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
<!--Generated with glade3 3.4.0 on Wed Feb 6 18:08:05 2008 -->
<glade-interface>
<widget class="GtkWindow" id="main">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="window_position">GTK_WIN_POS_CENTER</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
<signal name="delete_event" handler="on_main_delete_event"/>
<child>
<widget class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child>
<widget class="GtkFixed" id="fixed5">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child>
<widget class="GtkImage" id="image1">
<property name="width_request">71</property>
<property name="height_request">63</property>
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-network</property>
<property name="icon_size">6</property>
</widget>
<packing>
<property name="y">53</property>
</packing>
</child>
</widget>
</child>
<child>
<widget class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child>
<widget class="GtkFixed" id="fixed2">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child>
<widget class="GtkLabel" id="label1">
<property name="width_request">39</property>
<property name="height_request">22</property>
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Host:</property>
</widget>
<packing>
<property name="x">1</property>
<property name="y">10</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="entry1">
<property name="width_request">125</property>
<property name="height_request">25</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
</widget>
<packing>
<property name="x">78</property>
<property name="y">10</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label2">
<property name="width_request">34</property>
<property name="height_request">23</property>
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Port:</property>
</widget>
<packing>
<property name="x">205</property>
<property name="y">10</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="entry2">
<property name="width_request">50</property>
<property name="height_request">25</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="max_length">5</property>
</widget>
<packing>
<property name="x">240</property>
<property name="y">10</property>
</packing>
</child>
</widget>
</child>
<child>
<widget class="GtkFixed" id="fixed4">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child>
<widget class="GtkLabel" id="label3">
<property name="width_request">61</property>
<property name="height_request">22</property>
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Protocol:</property>
</widget>
<packing>
<property name="y">7</property>
</packing>
</child>
<child>
<widget class="GtkComboBox" id="combobox1">
<property name="width_request">86</property>
<property name="height_request">27</property>
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="items" translatable="yes"></property>
<signal name="changed" handler="on_combobox1_changed"/>
</widget>
<packing>
<property name="x">79</property>
<property name="y">6</property>
</packing>
</child>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkFixed" id="fixed3">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child>
<widget class="GtkLabel" id="label4">
<property name="width_request">76</property>
<property name="height_request">22</property>
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Username:</property>
</widget>
<packing>
<property name="y">8</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="entry3">
<property name="width_request">115</property>
<property name="height_request">25</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="max_length">128</property>
</widget>
<packing>
<property name="x">79</property>
<property name="y">6</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label5">
<property name="width_request">71</property>
<property name="height_request">21</property>
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Password:</property>
</widget>
<packing>
<property name="y">36</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="entry4">
<property name="width_request">115</property>
<property name="height_request">25</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="max_length">128</property>
<property name="visibility">False</property>
</widget>
<packing>
<property name="x">79</property>
<property name="y">34</property>
</packing>
</child>
</widget>
<packing>
<property name="position">2</property>
</packing>
</child>
<child>
<widget class="GtkFixed" id="fixed1">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child>
<widget class="GtkButton" id="button1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Connect</property>
<property name="response_id">0</property>
<signal name="clicked" handler="on_button1_clicked"/>
</widget>
<packing>
<property name="x">77</property>
<property name="y">10</property>
</packing>
</child>
<child>
<widget class="GtkImage" id="image2">
<property name="width_request">32</property>
<property name="height_request">32</property>
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-disconnect</property>
<property name="icon_size">3</property>
</widget>
<packing>
<property name="x">3</property>
<property name="y">7</property>
</packing>
</child>
<child>
<widget class="GtkLayout" id="layout1">
<property name="width_request">295</property>
<property name="height_request">10</property>
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
</widget>
<packing>
<property name="x">4</property>
<property name="y">43</property>
</packing>
</child>
</widget>
<packing>
<property name="position">3</property>
</packing>
</child>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

Windows在没有TCP/IP协议的情况下获取网卡MAC地址

Win NT/XP中,获得网卡MAC地址,一般是通过netapi32.dll中的NetBIOS API发送NCBASTAT命令来获取,但这样做的前提是网络连接启用了TCP/IP协议,否则的话就不起作用,而且即使用ipconfig /all命令也看不到任何内容。

但MAC地址是固化在网卡硬件中的一个序列号,即使不安装任何网络协议也应该能读取,最简单的办法是用Windows的wmic命令进行查询:

wmic nic where netconnectionid!=NULL get macaddress


很多人比较关心如何从注册表中获取MAC地址,其实注册表中的MountPoints2项中的确是保存有MAC地址,但很难从其它设备中查找出来,比较好的办法是先从注册表中找到网卡的设备编号,再通过DeviceIoControl查找硬件的MAC地址,以下是Perl Win32的实现:

#!/usr/bin/perl -w

use strict;

use Win32API::File qw(CreateFile DeviceIoControl :FILE_SHARE_ :Misc);
use Win32::TieRegistry;

#use Data::Dump qw(dump);

sub IOCTL_NDIS_QUERY_GLOBAL_STATS () { 0x17 << 16 | 2 };
sub OID_802_3_PERMANENT_ADDRESS () { 0x01010101 };
sub OID_802_3_CURRENT_ADDRESS () { 0x01010102 };

sub NDIS_Query
{
my ($handle, $oid) = @_;

my $nBytes = 0;
my $buf = "\0"x10;

DeviceIoControl($handle,
IOCTL_NDIS_QUERY_GLOBAL_STATS(),
pack("L", $oid), 0,
$buf, length($buf),
$nBytes,
[] );

return join "-", unpack("(a2)*", unpack("H*", $buf) );
}

my $adapters = $Registry->Open("LMachine/SOFTWARE/Microsoft/Windows NT/CurrentVersion/NetworkCards", { Access => "KEY_READ", Delimiter => "/" } );

#print dump($adapters);

foreach(keys %$adapters)
{
my $adapterName = $adapters->{$_}->{ServiceName};
print "Adapter name = $adapterName\n";

my $hMAC = CreateFile("//./$adapterName", 0, FILE_SHARE_READ(), [], OPEN_EXISTING(), 0, []);

for (
[ "permanent" => OID_802_3_PERMANENT_ADDRESS() ],
[ "current " => OID_802_3_CURRENT_ADDRESS() ],
) {
my ($type, $oid) = @$_;

my $mac = NDIS_Query($hMAC, $oid);
print "MAC $type = $mac\n";
}
}

2008年3月26日星期三

第一件CLF坛衫

编号001,很有纪念意义,但貌似尺码有点小了,有紧身衣的感觉。