Почему sendto () через Perl Socket-> отправить() игнорирует адрес однорангового узла?

#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