【问题标题】:Why does Perl's IO::Socket on Windows complain about "Resource Not Available" after 64 connections?为什么 Windows 上的 Perl 的 IO::Socket 在 64 次连接后会抱怨“资源不可用”?
【发布时间】:2013-06-19 22:35:13
【问题描述】:

我在 Windows (ActivePerl 5.10.1 build 1006) 下使用 Perl 创建了一个服务器,该服务器在连接时分叉,接受一些 JSON 数据,并将其写入数据库。我在 64 个客户端连接到服务器后遇到问题,尝试分叉时出现“资源不可用”的错误消息。

在 Linux 下运行此代码,我发现许多已失效的子进程,通过在父进程上添加 wait() 调用解决了这个问题。然而这并没有解决问题。在 Linux 下运行代码可以超过 Windows 中允许的 64 次调用。

我还启动了一个虚拟 Windows 服务器,以防服务器受到限制,但全新安装的 Perl 导致相同的 64 个连接限制。

感谢任何想法。


use IO::Socket; 
use Net::hostent; 
use JSON;
use DBI;
use Data::Dumper;

my $port=shift || 9000;
my $clients_served = 0;

while(1){
  my $server = IO::Socket::INET->new( Proto => 'tcp', 
    LocalPort => $port, 
    Listen => 1, 
    Reuse => 1); 

  die "can't setup server" unless $server; 
  print "[Server $0 is running]\n"; 

#### 
# wait for a client to connect
# once it has, fork to a seperate thread and
# retrieve the JSON data
#### 
  while (my $client = $server->accept()) { 
    my $pid = fork();

      if ($pid != 0) {
        print ("Serving client " . $clients_served++ . "\n");
      }else{
        $client->autoflush(1); 
        my $JSONObject = JSON->new->ascii->pretty->allow_nonref();
        my $hostinfo = gethostbyaddr($client->peeraddr); 
        my $client_hostname = ($hostinfo->name || $client->peerhost);

        printf "connect from %s\n", $client_hostname;

        print " $client_hostname connected..\n";
        syswrite($client, "Reached Server\n", 2048);
        if (sysread($client, my $buffer, 2048) > 0) {

          foreach my $tasks($JSONObject->decode($buffer)){
            foreach my $task (@$tasks){
              insert_record($client_hostname, $task); #empty method, contents does not affect result
            }
          }
        }

        print " $client_hostname disconnected..\n";
        close $client; 
        exit 0;
      }
  }
  $server->close();
}

exit 0;

【问题讨论】:

  • 64 个套接字对任何人来说都应该足够了——比尔·盖茨
  • 哪个 Windows 版本?非服务器版本对传入连接数有各种限制。
  • 我仍然惊讶于 Windows 如何教会人们重新安装是首先要做的事情之一。 :)

标签: windows perl sockets fork


【解决方案1】:

尝试从完成的事务中获取僵尸进程。我可以得到你的示例代码 如果我再添加几行,请继续运行:

    use POSIX ':sys_wait_h';

    if ($pid != 0) {
        print ("Serving client " . $clients_served++ . "\n");
        1 while waitpid -1, WNOHANG > 0;

如果您可能有 64 个同时 个连接,您可能不得不考虑其他事情——在 Windows 上安装 SIGCHLD 处理程序并不好。

【讨论】:

  • 这很奇怪,如果是真的——如果没有 CHLD 处理程序,你不应该得到僵尸,除非父级严重崩溃。即使在 Windows 上。
  • 在 Windows 上使用 Perl 的 Unix 风格的信号是一个巨大的组合。各种具有不同且不相关机制的 Windows 事件被硬塞到信号框架中。到目前为止,在 Windows 中终止子进程(实际上是一个 psuedo-process,一个单独的线程但不是一个单独的进程)不会向父进程传递 SIGCHLD 事件,尽管您可以使用 @ 987654323@ 同步检测是否有孩子完成并收获。
【解决方案2】:

这是一种回避,因为它不会直接回答您的问题,但有时消除错误的最佳方法是编写更少的代码——为什么不让其他人为你做进程管理和套接字处理——即@ 987654321@? Net::Server::Fork 个性提供了与您现在所写相同的行为,尽管我个人会考虑 Net::Server::PreFork

使用 Net::Server,您的应用将如下所示:

use strict;
use warnings;
use base 'Net::Server::Fork';

sub process_request {
    my $self = shift;
    # Do JSON stuff here.
    # The client is attached to *STDIN and *STDOUT.
}

# You might omit this and the arguments to run() because
# Net::Server has command-line and config-file handling too.

my $port = shift || 90000;

__PACKAGE__->run(
    proto => "tcp",
    port => $port,
);

我不得不说,这真的很整洁。

【讨论】:

  • 如果我要为 Linux 构建它,我会使用 Net::Server。不幸的是,根据他们的 CPAN 站点,他们的测试在 Windows 中没有通过,这让我不得不去别处寻找。非常同意 Net::Server 解决方案更加优雅。
  • @JGaudette:你到底在看哪里? CPAN 测试人员为 Win32 上的 Net::Server 展示了一组非常健康的 PASS:matrix.cpantesters.org/…
【解决方案3】:

我已经在 Windows (ActiveState Perl 5.10) 上挖掘了 perl 的 fork 问题。问题是子退出后,模拟为线程的分叉“进程”的句柄没有关闭。而且似乎有 64 个线程句柄的限制。由于我没有找到如何关闭这些线程句柄,所以我寻找了另一种解决方案,发现使用真正的线程可以超过 64 个限制(这来自 http://www.perlmonks.org/?node_id=722374):

#! perl -slw
use strict;
use IO::Socket;
use threads;

our $ADDR ||= 'localhost:35007';

my $listener = IO::Socket::INET->new(
    LocalAddr => $ADDR,
    Listen => 5,
    Reuse   => 1,
) or die $^E;

while( my $client = $listener->accept ) {
    async {
        my($port, $iaddr) = sockaddr_in( getpeername( $client ) );
        printf "From %s:%d: %s", join('.',unpack 'C4', $iaddr ), $port, $_
            while <$client>;
        close $client;
    }->detach;
}

这确实有效,至少在 Windows XP 上是这样。任何警告,有人吗?

【讨论】:

    【解决方案4】:

    我花了很长时间解决这个问题。

    每个 Windows perl 程序有 64 个线程的限制,不支持 POSIX。

    在你 fork 一个子节点后,父节点需要调用 waitpid($childPID,0) 来释放线程,但这会导致父节点阻塞,导致问题:如果父节点必须要分叉,那么分叉的意义何在等孩子说完?

    不清楚的是,父母可以在以后的任何时间发出 waitpid($childPID,0)!

    当我的子线程完成后,它所做的最后一件事就是创建一个文件...

    open OUT, ">$$.pid";
    print OUT $$;
    close OUT;
    exit(0);
    

    每次父进程要分叉时,它都会检查 .pid 文件,并为每个文件发出 waitpid,然后删除 .pid 文件。

    这将不允许同时超过 64 个连接,但它允许父线程在子线程完成工作时继续,然后在子线程完成后释放线程。

    另一个值得一提的是,孩子应该做的第一件事是关闭侦听套接字(问题中的 $server),这样只有父母才能回答新客户。

    【讨论】:

      猜你喜欢
      • 2015-04-08
      • 1970-01-01
      • 1970-01-01
      • 2012-12-11
      • 2011-01-31
      • 2019-12-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多