Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl 6 udp socket: how to read response from server?

Tags:

sockets

raku

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!");

How can the client read the servers response?
Perhaps this is not yet implemented?

For example in 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";
...
like image 452
Shniperson Avatar asked Jan 16 '18 14:01

Shniperson


2 Answers

Before I get to the answer, unless you don't care about who you're receiving data from, you're not listening for data on the server the right way for UDP sockets. You should be passing :datagram to IO::Socket::Async.Supply, which makes it so the Tappable returned by IO::Socket::Async.bind-udp emits objects containing the data received as well as the hostname and port of the peer, rather than the data alone:

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

The type used to represent datagrams is undocumented as of writing, but it's little more than a container, so this is how it is implemented in 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))
    }
}

With that out of the way, there is a way to listen for data received by clients using UDP without NativeCall; IO::Socket::Async.bind-udp and IO::Socket::Async.udp both return an IO::Socket::Async instance, so you'd listen for messages on a client the same way you would for a server:

my IO::Socket::Async:D $client .= udp;
react whenever $client.Supply(:datagram) -> $datagram {
    # ...
}
like image 74
Kaiepi Avatar answered Oct 02 '22 13:10

Kaiepi


First let me reiterate my comment above. From reading the documentation for IO::Socket::Async, I don't see an obvious way to do this. You can either set up a UDP sender, or a UDP receiver, but not both.

UDP connections are defined by 4 things, (sender address, sender port, receiver address, receiver port).

A server can listen on a given address/port. Once a packet has been received, there are usually ways to query for the sender's address/port. That's what I don't see for Perl 6.

A client can direct a packet to a specific server address/port. The client usually picks a random 'sender port', giving the fourth element needed for a 'connection' (in this connection-less protocol).

So, as in your examples from other languages, the client sends the packet, the server looks up the sender's address/port, then returns a packet to that same address/port. The client, after sending off its packet, listens again to the same random port it sent the packet out on, to receive the response from the server. I don't see an obvious way in Perl 6 to follow up a print-to with a recv on the same port just sent to.

With that said, Perl 6 has a fantastic NativeCall facility that can be used to call dynamic libraries directly, so you can do everything you need with the actual system calls if you are so inclined.

This isn't the 'official' Perl 6 way by any means, and once IO::Socket::Async can do what you want, purge all this from your brain, but here's how to do it with 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;
like image 24
Curt Tilmes Avatar answered Oct 02 '22 12:10

Curt Tilmes