2013-01-07 92 views
2

这可能不是Perl特有的,但我的演示是在Perl中。Perl IO :: Socket/IO :: Select - 从“准备读取”套接字读取

我的主程序打开一个监听套接字,然后分叉一个子进程。孩子的第一份工作就是与主人联系并说出HELLO。然后它继续初始化,当它准备好时,它发送READY给主设备。

大师在分出孩子之后,等待HELLO然后进行其他初始化(主要是为其他孩子分配)。一旦它分叉了所有的孩子,并从每个孩子那里听到HELLO,它就会等待所有孩子说出就绪。

它使用IO :: Select-> can_read,然后使用$ socket-> getline来检索消息。

总之,父母未能收到READY,即使它是由孩子发送的。

这里是我的程序的匆匆简化版本,演示的错误(我试图消除不相关的问题,但有一些可能会保留)。对于是否保留消息边界以及是否需要“\ n”以及从套接字读取哪种方法,我仍然感到困惑。我真的不想考虑收集消息片段,并且我希望IO :: Select能够让我省却这些。

该演示仅生成一个孩子,为了简单起见。

#!/usr/bin/env perl 

use warnings; 
use strict; 
use Carp; 
use File::Basename; 
use IO::Socket; 
use IO::Select; 
use IO::File;     # for CONSTANTS 
use Net::hostent;    # for OO version of gethostbyaddr 
use File::Spec qw{rel2abs};  # for getting path to this script 
use POSIX qw{WNOHANG setsid}; # for daemonizing 

use 5.010; 

my $program = basename $0; 
my $progpath = File::Spec->rel2abs(__FILE__); 
my $progdir = dirname $progpath; 

$| = 1;       # flush STDOUT buffer regularly 

# Set up a child-reaping subroutine for SIGCHLD. Prevent zombies. 
# 
say "setting up sigchld"; 

$SIG{CHLD} = sub { 
    local ($!, $^E, [email protected]); 
    while ((my $kid = waitpid(-1, WNOHANG)) > 0) { 
     say "Reaping child process $kid"; 
    } 
}; 

# Open a port for incoming connections 
# 
my $listen_socket = IO::Socket::INET->new(
    Proto  => 'tcp', 
    LocalPort => 2000, 
    Listen => SOMAXCONN, 
    Reuse  => 1 
); 
croak "Can't set up listening socket: $!\n" unless $listen_socket; 

my $readers = IO::Select->new($listen_socket) 
    or croak "Can't create the IO::Select read object"; 

say "Forking"; 

my $manager_pid; 
if (!defined($manager_pid = fork)) { 
    exit; 
} 
elsif (0 == $manager_pid) { 
    # 
    # ------------------ BEGIN CHILD CODE HERE ------------------- 
    say "Child starting"; 

    my ($master_addr, $master_port) = split /:/, 'localhost:2000'; 

    my $master_socket = IO::Socket::INET->new(
     Proto => "tcp", 
     PeerAddr => $master_addr, 
     PeerPort => $master_port, 
    ) or die "Cannot connect to $master_addr:$master_port"; 

    say "Child sending HELLO."; 

    $master_socket->printflush("HELLO\n"); 

    # Simulate elapsed time spent initializing... 
    # 
    say "Child sleeping for 1 second, pretending to be initializing "; 

    sleep 1; 
    # 
    # Finished initializing. 

    say "Child sending READY."; 

    $master_socket->printflush("READY\n"); 
    say "Child sleeping indefinitely now."; 

    sleep; 
    exit; 
    # ------------------- END CHILD CODE HERE -------------------- 
} 

# Resume parent code 

# The following blocks until we get a connect() from the manager 

say "Parent blocking on ready readers"; 

my @ready = $readers->can_read; 

my $handle; 

for $handle (@ready) { 
    if ($handle eq $listen_socket) { #connect request? 

     my $manager_socket = $listen_socket->accept(); 
     say "Parent accepting connection."; 

     # The first message from the manager must be his greeting 
     # 
     my $greeting = $manager_socket->getline; 
     chomp $greeting; 
     say "Parent received $greeting"; 

    } 
    else { 
     say($$, "This has to be a bug"); 
    } 
} 

say "Parent will now wait until child sends a READY message."; 
say "NOTE: if the bug works, Ill never receive the message!!"; 

################################################################################ 
# 
# Wait until all managers have sent a 'READY' message to indicate they've 
# finished initializing. 
# 
################################################################################ 

$readers->add($handle); # add the newly-established socket to the child 

do { 
    @ready = $readers->can_read; 
    say "Parent is ignoring a signal." if [email protected]; 

} until @ready; 

# a lot of overkill for demo 

for my $socket (@ready) { 
    if ($socket ne $listen_socket) { 
     my $user_input; 
     $user_input = $socket->getline; 
     my $bytes = length $user_input; 
     if ($bytes > 0) { 
      chomp $user_input; 
      if ($user_input eq 'READY') { 
       say "Parent got $user_input!"; 
       $readers->remove($socket); 
      } 
      else { 
       say($$, "$program RECVS $user_input??"); 
      } 
     } 
     else { 
      say($$, "$program RECVs zero length message? EOF?"); 
      $readers->remove($socket); 
     } 
    } 
    else { 
     say($$, "$program RECVS a connect on the listen socket??"); 
    } 
} # end for @ready 
say "Parent is ready to sleep now."; 
+1

始终使用用'select'选择'sysread'。从未像“getline”那样使用缓冲IO。 'getline'双重没有意义,因为它是一个阻塞呼叫。 – ikegami

+0

但选择完成后,它不会......会吗? – Chap

回答

2

我不知道如果这是你(只)的问题,但总使用sysreadselect。从未使用缓冲IO,如getlinegetline双重没有意义,因为它可以阻止尚未收到的数据。

select循环应该是这样的:

  1. 对于以往,

    1. 等待套接字准备就绪阅读。
    2. 对于每一个插座准备好被读取,

      1. sysread($that_socket, $buffer_for_that_socket, 64*1024, 
            length($buffer_for_that_socket)); 
        
      2. 如果sysread返回民主基金,

        1. 处理错误。
      3. 如果sysread返回false,
        1. 关闭句柄插座。
      4. 否则,处理读取数据:

        1. while ($buffer_for_that_socket =~ s/^(.*)\n//) { my $msg = $1; ... } 
          
+0

所以,我认为,可变边界*不被保留。我非常喜欢你的伪代码;一个问题:我必须*完全*消耗$ buffer中的所有数据(包括部分完整的消息),之后can_read才会再次报告套接字的准备读取情况?从你的例子看起来好像答案是否定的...... – Chap

+0

不,你需要清空缓冲区。 'select'('can_read')对'$ buffer_for_that_socket'没有任何了解。不知道为什么你会想要坚持处理已收到的消息。如果您坚持处理已收到的消息,那么将套接字保留在select中直到您确实希望获得更多消息可能没有意义。 – ikegami

+0

主要只是试图明白是什么原因导致'can_read'完成与块。如果它完成了,我不采取行动,而是再次发出'can_read',它会阻止吗?或者立即再次完成? – Chap