Каков наилучший способ получения данных из сокета в Perl, когда длина данных неизвестна? - PullRequest
4 голосов
/ 18 июля 2010

Сейчас я читаю по одному символу за раз в цикле, пока не достигну символа \0. Есть ли лучший способ сделать это?

Ответы [ 5 ]

6 голосов
/ 18 июля 2010

Установите конец строки на \x{00} (\ 0), убедитесь, что локализовали его, и getline на дескрипторе, например:

{
    local $/ = "\x{00}";
    while (my $line = $sock->getline) {
       print "$line\n"; # do whatever with your data here
   }
}
2 голосов
/ 18 июля 2010

Вы можете использовать FIONREAD с ioctl.Приведенная ниже программа подключается к 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";
}

Пример выполнения:

$ ./howmuch
39
got: [SSH-2.0-OpenSSH_5.3p1 Debian-3ubuntu4\r\n], length=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
got: [HTTP/1.0 200 OK\r\nDate: Sun, 18 Jul 2010 12:03:48 GMT\r\nExpires: -1\r\nCache-Control: private, max-age=0\r\nContent-Type: text/html; charset=ISO-8859-1\r\nSet-Cookie: PREF=ID=6742ab80dd810a95:TM=1279454628:LM=1279454628:S=ewNg64020FbnGzHR; expires=Tue, 17-Jul-2012 12:03:48 GMT; path=/; domain=.google.com\r\nSet-Cookie: NID=36=kn2wtTD4UJ3MYYQ5uvA4iAsrS2wcrb_W781pZ1hrVUhUDHrIJTMg_kOgVKhjQnO5SM6MdC_jrRdxFRyXwyyv5N3Xja1ydhVLWWaYqpMHQOmGVi2K5qRWAKwDhCVRd8WS; expires=Mon, 17-Jan-2011 12:03:48 GMT; path=/; domain=.google.com; HttpOnly\r\nServer: gws\r\nX-XSS-Protection: 1; mode=block\r\n\r\n], length=573
2 голосов
/ 18 июля 2010

Ответ зависит от протокола.Поскольку ваш протокол использует '\ 0' в качестве разделителя, вы делаете правильные вещи.Я почти уверен, что Perl обрабатывает буферизацию для вас, поэтому чтение по одному символу за раз не является неэффективным.

Многие сетевые протоколы предшествуют строкам с длиной.Чтобы прочитать такой протокол, вы читаете длину (обычно один или два байта, в зависимости от спецификации протокола), а затем читаете столько байтов в строку.

2 голосов
/ 18 июля 2010

Каков наилучший способ получения данных из сокета в Perl, когда длина данных неизвестна?

Надежное решение этой проблемы невозможно на любом языке. Если вы не знаете, какова длина данных, вы не сможете узнать, когда вы закончили получать все из сокета.

Ваша единственная надежда состоит в том, чтобы использовать какую-то метрику, чтобы определить, был ли он "достаточно длинным" с момента начала поступления данных, чтобы принять решение об остановке потока данных. Но это не будет идеально.

0 голосов
/ 22 сентября 2016

Вы можете использовать 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 обходит буферизованный ввод-вывод, поэтому не смешивайте его с буферизованным 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;
...