为什么通过 Perl Socket->send() 的 sendto() 会忽略对等地址?

Why does sendto() via Perl Socket->send() ignore the peer address?

我有一个程序(太复杂了,无法在此处展示)使用由 IO::Socket::INET->new() 创建的 UDP 套接字,同时具有 Local...Peer... 地址和端口。所以期望是一个普通的 $sock->send($data, $flags) 将发送 $data 到创建套接字时指定的对等地址。这似乎有效。

然而,当我尝试使用 $sock->send($data, $flags, $dest) 将单个数据包发送到不同的目的地时,$dest 似乎被忽略了(并且数据包正在发送到套接字的对等地址)。我添加了很多调试消息,并且参数 $dest 被正确传递给 send,但是 strace 显示 sendto() 是用 NULL 调用的 [=23] =] 和 0 对于 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;
}

所以很明显目的地被传递为“host : port”(一个字符串没有空格(这里的空格是由于加价不足造成的)。

如果在创建 IO::Socket::INET 时使用 Peer*,则将调用 connect(),这会设置套接字的对等名称。在这样的套接字上,您永远不必指定远程套接字地址,因为它有一个默认地址。

IO::Socket::send() 有一个 "feature":当套接字具有有效的对等名称时,它会忽略 TO 参数:

 my $r = defined(getpeername($sock))
     ? send($sock, $_[1], $flags)
     : send($sock, $_[1], $flags, $peer);

IO 1.12, which unfortunately predates the history of the current git repository 中引入了该更改:

Modified IO::Socket::send so not to pass 4 arguments to send if the socket is connected

如果您不喜欢这种行为,则必须改用原始 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 机器的 strace 表明 send() 被映射到 sendto(),因为 Perl 代码确实调用了 send() and sendto().


更新: 我创建了 upstream ticket #133936.