【发布时间】:2011-03-17 12:55:51
【问题描述】:
现在,我在循环中一次读取一个字符,直到到达\0 字符。有没有更好的方法来做到这一点?
【问题讨论】:
现在,我在循环中一次读取一个字符,直到到达\0 字符。有没有更好的方法来做到这一点?
【问题讨论】:
当数据长度未知时,在 Perl 中从套接字接收数据的最佳方式是什么?
在任何语言中都不可能有一个合理的解决方案。如果你不知道数据长度有多长,那么你不可能知道你什么时候从套接字接收完所有数据。
您唯一的希望是使用某种指标来确定自数据开始传入以来是否已经“足够长”,从而做出数据流已停止的决定。但它不会是完美的。
【讨论】:
\o 是什么?你的意思是\0?
答案取决于协议。由于您的协议使用 '\0' 作为分隔符,因此您做对了。我很确定 Perl 会为您处理缓冲,因此一次读取一个字符并不是低效的。
许多面向网络的协议在字符串前面加上一个长度。要读取这样的协议,您需要读取长度(通常是一个或两个字节,具体取决于协议规范),然后将那么多字节读入一个字符串。
【讨论】:
$/ + getline 方法更有效且非常清晰,因此它获胜:)
将你的行尾设置为\x{00} (\0),确保将其本地化,并在句柄上设置getline,如下所示:
{
local $/ = "\x{00}";
while (my $line = $sock->getline) {
print "$line\n"; # do whatever with your data here
}
}
【讨论】:
您可以将FIONREAD 与ioctl 一起使用。下面的程序连接到 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::INET 和 IO::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。
您可以使用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 read 和perldoc -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;
【讨论】: