#perl #sockets #sendto
#perl #сокеты #sendto
Вопрос:
У меня есть программа (слишком сложная, чтобы представлять ее здесь), которая использует UDP-сокет, созданный IO::Socket::INET->new()
с помощью обоих, Local...
и Peer...
адреса и порта. Таким образом, ожидается, что обычный запрос $sock->send($data, $flags)
будет отправлен $data
на одноранговый адрес, указанный при создании сокета. Кажется, это работает.
Однако, когда я пытаюсь отправить отдельный пакет другому адресату с помощью $sock->send($data, $flags, $dest)
, $dest
кажется, что он игнорируется (и пакет отправляется на одноранговый адрес сокета). Я добавил множество отладочных сообщений, и параметр $dest
передается правильно send
, но strace
показывает, что sendto()
вызывается с помощью NULL
for sockaddr
и 0
for socklen
.
perldoc -f send
мне это не помогает. Итак, почему игнорируется адрес назначения?
Как и было запрошено, вот (несколько излишне подробный) код send_packet:
$RE_saddr = qr/^(. ):(d )$/;
sub send_packet($$;$)
{
my ($sock, $packet, $dest) = @_;
my @params = ($packet, 0);
if (defined($dest)) {
if (my ($addr, $port) = $dest =~ $RE_saddr) {
if (my $addr_bin = inet_aton($addr)) {
if (defined($dest = sockaddr_in($port, $addr_bin))) {
push(@params, $dest);
} else {
warn "bad address or socket for $addr:$port";
}
} else {
warn "bad address $addr";
}
} else {
warn "bad destination address $dest";
}
}
return 1
if ($sock->send(@params));
return undef;
}
Очевидно, что адрес назначения передается как «хост : порт» (одна строка без пробелов (пробелы здесь вызваны дефицитом разметки)).
Комментарии:
1. Можете ли вы хотя бы добавить код, который показывает, как вы создаете
$dest
?2. Замечание: если вы используете
Peer*
для созданияIO::Socket::INET
сокета, тоconnect()
вызывается. Похоже, это влияет на поведениеsend()
, т.е. он больше не передаетTO
параметрsendto()
. Я бы посоветовал изучить исходный код Perl, чтобы выяснить причину.3. Хороший момент, но, строго говоря,
connect()
не имеет смысла для UDP. По крайней мере, это плохая документация на стороне Perl.4. F.ex. IO::Socket::send() игнорирует,
$peer
когдаpeername($sock)
возвращает адрес однорангового узла. Это будет одноранговый адрес, установленныйconnect()
.5. Как говорилось ранее: Документация неполная; в ней говорится: «Для неподключенных сокетов вы должны указать место назначения для отправки, и в этом случае выполняется системный вызов sendto (2)». Это логично: вам нужен адрес назначения. Но я предполагал, что третий (четвертый) аргумент будет автоматически вызываться
sendto()
с этим параметром, временно переопределяя одноранговый адрес сокета. Кто-нибудь поддерживает мой вопрос?
Ответ №1:
Если вы используете Peer*
при создании, будет вызван IO::Socket::INET
then connect()
, который задает peername сокета. На таком сокете вам никогда не придется указывать адрес удаленного сокета, потому что у него есть адрес по умолчанию.
IO::Socket::send() имеет «особенность»: он игнорирует TO
параметр, когда сокет имеет допустимое peername:
my $r = defined(getpeername($sock))
? send($sock, $_[1], $flags)
: send($sock, $_[1], $flags, $peer);
Это изменение было внесено в IO 1.12, который, к сожалению, предшествует истории текущего репозитория git:
Модифицированный ввод-вывод::Socket::send, чтобы не передавать 4 аргумента для отправки, если сокет подключен
Если вам не нравится такое поведение, вам придется использовать raw CORE::send()
вместо этого, т.Е.:
#!/usr/bin/perl
use warnings;
use strict;
use IO::Socket::INET;
use Socket qw(pack_sockaddr_in);
my $client = IO::Socket::INET->new(
PeerAddr => '127.0.0.1',
PeerPort => 2000,
Proto => 'udp',
) or die "client socket: $!n";
my $addr = pack_sockaddr_in(2000, inet_aton('127.0.0.1'));
$client->send('ABCD', 0)
or die "IO::Socket::send() no addr: $!n";
$client->send('ABCD', 0, $addr)
or die "IO::Socket::send() with addr: $!n";
send($client, 'ABCD', 0, $addr)
or die "send() with addr: $!n";
exit 0;
Тестовый запуск:
$ strace -e sendto,connect,socket perl dummy.pl
socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_UDP) = 4
connect(4, {sa_family=AF_INET, sin_port=htons(2000), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
sendto(4, "ABCD", 4, 0, NULL, 0) = 4
sendto(4, "ABCD", 4, 0, NULL, 0) = 4
sendto(4, "ABCD", 4, 0, {sa_family=AF_INET, sin_port=htons(2000), sin_addr=inet_addr("127.0.0.1")}, 16) = 4
Строка с моего компьютера Linux указывает, что она send()
сопоставлена с sendto()
, потому что код Perl вызывает send() и sendto().
ОБНОВЛЕНИЕ: я создал вышестоящий запрос # 133936.
Комментарии:
1. Поэтому я бы назвал версию ввода-вывода:: Socket «плохо документированной объектно-ориентированной путаницей» 😉 Спасибо за решение! Почему они не использовали
my $r = defined($peer) ? send($sock, $_[1], $flags, $peer) : send($sock, $_[1], $flags);
?2. Это не поможет, потому что
IO::Socket::send()
реализация начинается сmy $peer = $_[3] || $sock->peername;
. По вашему предложению вариант с тремя параметрамиCORE::send()
будет использоваться только для неподключенных сокетов, т. Е. он всегда будет давать сбой 🙁3. К вашему сведению: я написал комментарий на GitHub Dual-Life / IO PR # 17 .
4. К вашему сведению: я создал тикет RT # 133936