2

这可能不是 Perl 特定的,但我的演示是在 Perl 中的。

我的主程序打开一个监听套接字,然后分叉一个子进程。孩子的第一份工作是连接到主人并打个招呼。然后它继续它的初始化,当它准备好时,它发送READY给master。

主人在分叉孩子之后,等待 HELLO 然后进行其他初始化(主要是分叉其他孩子)。一旦它分叉了所有的孩子并听到了每个孩子的 HELLO,它就会继续等待他们所有人说 READY。

它使用 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, $@ );
    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 !@ready;

} 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.";
4

1 回答 1

4

我不知道这是否是您的(唯一)问题,但始终使用sysreadwith select。从未使用过缓冲 IO,例如getline. getline双重没有意义,因为它可以阻止尚未收到的数据。

您的select循环应如下所示:

  1. 永远,
    1. 等待套接字准备好读取。
    2. 对于每个准备被读取的套接字,
      1.  sysread($that_socket, $buffer_for_that_socket, 64*1024,
             length($buffer_for_that_socket));
        
      2. 如果sysread返回 undef,

        1. 处理错误。
      3. 如果sysread返回假,

        1. 处理关闭的套接字。不要忘记缓冲区中留下的数据。
      4. 否则,处理读取数据:

        1.  while ($buffer_for_that_socket =~ s/^(.*)\n//) { my $msg = $1; ... }
          
于 2013-01-07T02:14:29.137 回答