【问题标题】:What is the best way to receive data from a socket in Perl, when the data length is unknown?当数据长度未知时,从 Perl 中的套接字接收数据的最佳方法是什么?
【发布时间】:2011-03-17 12:55:51
【问题描述】:

现在,我在循环中一次读取一个字符,直到到达\0 字符。有没有更好的方法来做到这一点?

【问题讨论】:

    标签: perl sockets recv


    【解决方案1】:

    当数据长度未知时,在 Perl 中从套接字接收数据的最佳方式是什么?

    在任何语言中都不可能有一个合理的解决方案。如果你不知道数据长度有多长,那么你不可能知道你什么时候从套接字接收完所有数据。

    您唯一的希望是使用某种指标来确定自数据开始传入以来是否已经“足够长”,从而做出数据流已停止的决定。但它不会是完美的。

    【讨论】:

    • 我知道每条消息都以'\o'结尾,这有帮助吗?
    • 只要您知道确定该字符不能作为数据流的一部分发送,那么它的作用就像一个数据结束标记,其中如果您不需要知道数据长度。在这种情况下,您的解决方案是有效的。
    • @Gal:\o 是什么?你的意思是\0
    【解决方案2】:

    答案取决于协议。由于您的协议使用 '\0' 作为分隔符,因此您做对了。我很确定 Perl 会为您处理缓冲,因此一次读取一个字符并不是低效的。

    许多面向网络的协议在字符串前面加上一个长度。要读取这样的协议,您需要读取长度(通常是一个或两个字节,具体取决于协议规范),然后将那么多字节读入一个字符串。

    【讨论】:

    • PerlIO 确实会处理缓冲,因此 1-char 读取不会产生 syscall 开销,但它们仍然会在 Perl 操作循环中浪费时间(更不用说数字可能发生的字符串连接,具体取决于代码)。不进行微优化,但$/ + getline 方法更有效且非常清晰,因此它获胜:)
    【解决方案3】:

    将你的行尾设置为\x{00} (\0),确保将其本地化,并在句柄上设置getline,如下所示:

    {
        local $/ = "\x{00}";
        while (my $line = $sock->getline) {
           print "$line\n"; # do whatever with your data here
       }
    }
    

    【讨论】:

      【解决方案4】:

      您可以将FIONREADioctl 一起使用。下面的程序连接到 localhost 上的 SSH 服务器并等待它的问候:

      #! /usr/bin/perl
      
      use warnings;
      use strict;
      
      use subs 'FIONREAD';
      require "sys/ioctl.ph";
      use Socket;
      socket my $s, PF_INET, SOCK_STREAM, getprotobyname "tcp"
        or die "$0: socket: $!";
      connect $s, sockaddr_in 22, inet_aton "localhost"
        or die "$0: connect: $!";
      
      my $rin = "";
      vec($rin, fileno($s), 1) = 1;
      my $nfound = select my$rout=$rin, "", "", undef;
      die "$0: select: $!" if $nfound < 0;
      
      if ($nfound) {
        my $size = pack "L", 0;
        ioctl $s, FIONREAD, $size
          or die "$0: ioctl: $!";
      
        print unpack("L", $size), "\n";
        sysread $s, my $buf, unpack "L", $size
          or die "$0: sysread: $!";
      
        my $length = length $buf;
        $buf =~ s/\r/\\r/g;
        $buf =~ s/\n/\\n/g;
        print "got: [$buf], length=$length\n";
      }
      

      示例运行:

      $ ./多少
      39
      得到:[SSH-2.0-OpenSSH_5.3p1 Debian-3ubuntu4\r\n],长度=39

      但您可能更喜欢使用 IO::Socket::INETIO::Select 模块,如下面的代码中与 Google 对话:

      #! /usr/bin/perl
      
      use warnings;
      use strict;
      
      use subs "FIONREAD";
      require "sys/ioctl.ph";
      use IO::Select;
      use IO::Socket::INET;
      
      my $s = IO::Socket::INET->new(PeerAddr => "google.com:80")
        or die "$0: can't connect: $@";
      
      my $CRLF = "\015\012";
      print $s "HEAD / HTTP/1.0$CRLF$CRLF" or warn "$0: print: $!";
      
      my @ready = IO::Select->new($s)->can_read;
      die "$0: umm..." unless $s == $ready[0];
      
      my $size = pack "L", 0;
      ioctl $s, FIONREAD, $size
        or die "$0: ioctl: $!";
      
      print unpack("L", $size), "\n";
      sysread $s, my $buf, unpack "L", $size
        or die "$0: sysread: $!";
      
      my $length = length $buf;
      $buf =~ s/\r/\\r/g;
      $buf =~ s/\n/\\n/g;
      print "got: [$buf], length=$length\n";
      

      输出:

      573
      得到:[HTTP/1.0 200 OK\r\n日期:星期日,2010 年 7 月 18 日 12:03:48 GMT\r\n过期:-1\r\n缓存控制:私有,max-age=0\r\n内容-类型:文本/html; charset=ISO-8859-1\r\nSet-Cookie: PREF=ID=6742ab80dd810a95:TM=1279454628:LM=1279454628:S=ewNg64020FbnGzHR;到期=格林威治标准时间 2012 年 7 月 17 日星期二 12:03:48;路径=/; domain=.google.com\r\nSet-Cookie: NID=36=kn2wtTD4UJ3MYYQ5uvA4iAsrS2wcrb_W781pZ1hrVUhUDHrIJTMg_kOgVKhjQnO5SM6MdC_jrRdxFRyXwyyv5N3Xja1ydhVLWWaYqpMHQOmGVi2K5qRWAKwDhCVRd8WS; expires=星期一,2011 年 1 月 17 日 12:03:48 GMT;路径=/;域=.google.com; HttpOnly\r\n服务器:gws\r\nX-XSS-保护:1;模式=块\r\n\r\n],长度=573
      

      【讨论】:

      • sysread 在有可用数据时立即返回,因此我们可以跳过FIONREAD,直接调用大尺寸的sysread
      【解决方案5】:

      您可以使用sysread 读取任何可用数据:

      my $data;
      my $max_length = 1000000;
      sysread $sock, $data, $max_length;
      

      Perl 的 read 函数等待您请求的全部字节数,或 EOF。
      这类似于 libc stdio fread(3)

      Perl 的sysread 函数在收到任何数据后立即返回。
      这类似于 UNIX read(2)
      请注意,sysread 会绕过缓冲 IO,因此不要将其与缓冲的 read 混合使用。

      查看perldoc -f readperldoc -f sysread 了解更多信息。

      对于这个特定的问题,最好按照上面的答案,使用getline,行尾为\0,但如果没有终止字符,我们可以使用sysread

      这里有一个小例子。它请求一个网页,并打印收到的第一块数据。

      #!/usr/bin/perl -w
      use strict; use warnings;
      use IO::Socket;
      
      my $host = $ARGV[0] || 'google.com';
      my $port = $ARGV[1] || 80;
      my $sock = IO::Socket::INET->new(Proto => 'tcp', PeerAddr => $host, PeerPort => $port)
          or die "connect failed: $!";
      $sock->autoflush(1);
      # use HTTP/1.1, which keeps the socket open by default
      $sock->print("GET / HTTP/1.1\r\nHost: $host\r\n\r\n");
      my $reply;
      my $max_length = 1000000;
      # $sock->read($reply, $max_length);   # read would hang waiting for 1000000 bytes
      my $count = $sock->sysread($reply, $max_length);
      if (!defined $count) {
          die "read failed: $!";
      }
      print $reply;
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-02-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-28
        • 1970-01-01
        相关资源
        最近更新 更多