【问题标题】:Perl 6 udp socket: how to read response from server?Perl 6 udp 套接字:如何从服务器读取响应?
【发布时间】:2018-06-25 07:06:30
【问题描述】:

server-udp.pl

my $socket = IO::Socket::Async.bind-udp('localhost', 3333);
react {
    whenever $socket.Supply -> $v {
        if $v.chars > 0 {
            $v.print;
        }
    }
}

client-udp.pl

my $socket = IO::Socket::Async.udp();
await $socket.print-to('localhost', 3333, "\nHello, Perl 6!");

客户端如何读取服务器的响应?
也许这还没有实现?

例如在 Perl 5 中:

client.pl

...
my $data_send = "Test 1234567890";
$client_socket->send( $data_send )
    or die "Client error while sending: $!\n";

# read operation
$client_socket->recv( my $data_rcv , 1024 )
    or die "Client error while received: $!\n";

print "Received data: $data_rcv\n";
...

【问题讨论】:

  • 我正在学习 perl 6。我很好奇如何做到这一点。在 Perl 5 和 Python 3 中,我可以从服务器获得响应。
  • 我没有看到任何明显的方式来查询 UDP 发送方的地址/端口。供应只是通过消息本身发送。将角色与 peer-host() 和 peer-port() 混合起来会很酷...
  • 之前的评论是关于服务器端的。我也没有看到客户端在同一个端口上侦听的明显方法,它只是将 UDP 数据包发送出去,让它接收响应。
  • 正在路上:github.com/rakudo/rakudo/pull/1473。谢谢蒂莫!

标签: sockets raku


【解决方案1】:

在我得到答案之前,除非您不关心从谁那里接收数据,否则您不会以正确的方式在服务器上侦听 UDP 套接字的数据。您应该将:datagram 传递给IO::Socket::Async.Supply,这使得IO::Socket::Async.bind-udp 返回的Tappable 发出包含接收到的数据以及对等方的主机名和端口的对象,而不是单独的数据:

my IO::Socket::Async::D $server .= bind-udp: 'localhost', 3333;
react whenever $server.Supply(:datagram) -> $datagram {
    print $datagram.data if $datagram.data.chars > 0;
}

用于表示数据报的类型在编写时没有记录,但它只不过是一个容器,所以这就是它在 Rakudo 中的实现方式:

my class Datagram {
    has $.data;
    has str $.hostname;
    has int $.port;

    method decode(|c) {
        $!data ~~ Str
          ?? X::AdHoc.new( payload => "Cannot decode a datagram with Str data").throw
          !! self.clone(data => $!data.decode(|c))
    }
    method encode(|c) {
        $!data ~~ Blob
          ?? X::AdHoc.new( payload => "Cannot encode a datagram with Blob data" ).throw
          !! self.clone(data => $!data.encode(|c))
    }
}

除此之外,还有一种方法可以在没有 NativeCall 的情况下使用 UDP 侦听客户端接收到的数据; IO::Socket::Async.bind-udpIO::Socket::Async.udp 都返回一个 IO::Socket::Async 实例,因此您可以像在服务器上一样在客户端上侦听消息:

my IO::Socket::Async:D $client .= udp;
react whenever $client.Supply(:datagram) -> $datagram {
    # ...
}

【讨论】:

    【解决方案2】:

    首先让我重申我上面的评论。通过阅读IO::Socket::Async 的文档,我没有看到明显的方法来做到这一点。您可以设置 UDP 发送方或 UDP 接收方,但不能同时设置。

    UDP 连接由 4 个东西定义,(发送方地址、发送方端口、接收方地址、接收方端口)。

    服务器可以监听给定的地址/端口。收到数据包后,通常有一些方法可以查询发送者的地址/端口。这就是我在 Perl 6 中看不到的。

    客户端可以将数据包定向到特定的服务器地址/端口。客户端通常会选择一个随机的“发送端口”,给出“连接”所需的第四个元素(在此无连接协议中)。

    因此,就像您在其他语言的示例中一样,客户端发送数据包,服务器查找发送者的地址/端口,然后将数据包返回到相同的地址/端口。客户端在发送数据包后,再次侦听它发送数据包的同一随机端口,以接收来自服务器的响应。我在 Perl 6 中看不到一个明显的方式来跟进 print-torecv 在刚刚发送到的同一端口上。

    话虽如此,Perl 6 有一个很棒的NativeCall 工具,可以用来直接调用动态库,所以如果你愿意的话,你可以用实际的系统调用来做你需要的一切。

    这绝不是“官方”的 Perl 6 方式,一旦 IO::Socket::Async 可以做你想做的事,就从你的大脑中清除所有这些,但这里是使用 NativeCall 的方法:

    server-udp.pl

    use NativeCall;
    
    constant \AF_INET := 2;
    constant \SOCK_DGRAM := 2;
    
    class sockaddr_in is repr('CStruct')
    {
        has int16 $.sin_family;
        has uint16 $.sin_port;
        has int32 $.sin_addr;
        has int64 $.pad;
    }
    
    sub socket(int32, int32, int32 --> int32) is native() {}
    sub bind(int32, sockaddr_in, uint32 --> int32) is native() {}
    sub htons(uint16 --> uint16) is native() {}
    sub ntohs(uint16 --> uint16) is native() {}
    sub inet_ntoa(int32 --> Str) is native() {}
    sub perror(Str) is native() {}
    sub recvfrom(int32, Blob, size_t, int32, sockaddr_in, int32 is rw --> ssize_t) is native() {}
    sub sendto(int32, Blob, size_t, int32, sockaddr_in, int32 --> ssize_t) is native() {}
    
    my int32 $sock = socket(AF_INET, SOCK_DGRAM, 0);
    perror('socket') // die if $sock < 0;
    
    my $addr = sockaddr_in.new(sin_family => AF_INET,
                               sin_port => htons(3333),
                               sin_addr => 0);
    
    my $ret = bind($sock, $addr, nativesizeof(sockaddr_in));
    
    perror('bind') // die if $ret < 0;
    
    my $buf = buf8.allocate(1024);
    
    my $fromaddr = sockaddr_in.new;
    
    my int32 $addrsize = nativesizeof(sockaddr_in);
    
    loop
    {
        $ret = recvfrom($sock, $buf, $buf.bytes, 0, $fromaddr, $addrsize);
        perror('recvfrom') // die if $ret < 0;
    
        my $msg = $buf.decode;
        $msg.print;
    
        my $return-msg = "Thank you for saying $msg";
        my $return-buf = $return-msg.encode;
    
        $ret = sendto($sock, $return-buf, $return-buf.bytes, 0, $fromaddr, $addrsize);
        perror('sendto') // die if $ret < 0;
    }
    

    client-udp.pl

    use NativeCall;
    
    constant \AF_INET := 2;
    constant \SOCK_DGRAM := 2;
    
    class sockaddr_in is repr('CStruct')
    {
        has int16 $.sin_family;
        has uint16 $.sin_port;
        has int32 $.sin_addr;
        has int64 $.pad;
    }
    
    sub socket(int32, int32, int32 --> int32) is native() {}
    sub htons(uint16 --> uint16) is native() {}
    sub inet_ntoa(int32 --> Str) is native() {}
    sub inet_aton(Str, int32 is rw --> int32) is native() {}
    sub perror(Str) is native() {}
    sub recvfrom(int32, Blob, size_t, int32, sockaddr_in, int32 is rw --> ssize_t) is native() {}
    sub recv(int32, Blob, size_t, int32 --> ssize_t) is native() {}
    sub sendto(int32, Blob, size_t, int32, sockaddr_in, int32 --> ssize_t) is native() {}
    
    my int32 $sock = socket(AF_INET, SOCK_DGRAM, 0);
    perror('socket') // die if $sock < 0;
    
    my int32 $addr-ip;
    inet_aton('127.0.0.1', $addr-ip) or die "Bad address";
    
    my $addr = sockaddr_in.new(sin_family => AF_INET,
                               sin_port => htons(3333),
                               sin_addr => $addr-ip);
    
    my $msg = "Hello, Perl 6!\n".encode;
    
    my $ret = sendto($sock, $msg, $msg.bytes, 0, $addr, nativesizeof(sockaddr_in));
    perror('sendto') // die if $ret < 0;
    
    my $buf = buf8.allocate(1024);
    
    $ret = recv($sock, $buf, $buf.bytes, 0);
    
    say "Return Msg: ", $buf.decode;
    

    【讨论】:

    • 注意:所有这些都非常依赖于操作系统和架构。您可能需要根据自己的情况进行调整。
    • 您如何看待将the Inlines 之一与适当的模块一起使用?我之前发布了一条链接到this comment 的评论。我删除了它,因为使用 P5 意味着引入一个主要的依赖关系并且它是“作弊”。但实际上,P5 不是经常可用,并且比使用底层 C 库具有更高级别和更便携的 API 吗?我很乐意在此处阅读评论或在您的回答中阅读有关为此使用内联的评论。
    • 感谢大家的cmets!你的cmets对我来说很有趣。在第一篇文章中,我写道,也许它还没有实现。非常抱歉,但我希望将来它会这样做,甚至可能要感谢这个话题。
    猜你喜欢
    • 2018-07-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多