12

我正在使用 IO::Socket::Async 在 P6 中重写我的 P5 套接字服务器,但是接收到的数据在最后被截断了 1 个字符,并且在下一个连接时接收到了 1 个字符。来自 Perl6 Facebook 小组 (Jonathan Worthington) 的人指出,这可能是由于字符串的性质和字节在 P6 中的处理方式非常不同。引:

在 Perl 6 中,字符串和字节的处理方式非常不同。值得注意的是,字符串在字素级别工作。接收 Unicode 数据时,不仅有可能将多字节序列拆分为数据包,还可能将多码点序列拆分。例如,一个数据包的末尾可能有字母“a”,而下一个数据包可能是一个组合的重音符号。因此,在看到下一个数据包如何开始之前,它不能安全地传递“a”。

我的 P6 在 MoarVM 上运行

https://pastebin.com/Vr8wqyVu

use Data::Dump;
use experimental :pack;

my $socket = IO::Socket::Async.listen('0.0.0.0', 7000);

react {
    whenever $socket -> $conn {
        my $line = '';
        whenever $conn {

            say "Received --> "~$_;
            $conn.print: &translate($_) if $_.chars ge 100;  
            $conn.close;              

        }
    }
    CATCH {
        default {
            say .^name, ': ', .Str;
            say "handled in $?LINE";
        }
    }
}

sub translate($raw) {

    my $rawdata = $raw;
    $raw ~~ s/^\s+|\s+$//; # remove heading/trailing whitespace

    my $minus_checksum = substr($raw, 0, *-2);
    my $our_checksum = generateChecksum($minus_checksum);
    my $data_checksum = ($raw, *-2);

    # say $our_checksum;
    return $our_checksum;

}

sub generateChecksum($minus_checksum) {

    # turn string into Blob
    my Blob $blob = $minus_checksum.encode('utf-8');
    # unpack Blob into ascii list
    my @array = $blob.unpack("C*");
    # perform bitwise operation for each ascii in the list
    my $dec +^= $_ for $blob.unpack("C*");
    # only take 2 digits
    $dec = sprintf("%02d", $dec) if $dec ~~ /^\d$/;
    $dec = '0'.$dec if $dec ~~ /^[a..fA..F]$/;
    $dec = uc $dec;
    # convert it to hex
    my $hex = sprintf '%02x', $dec;
    return uc $hex; 

}

结果

Received --> $$0116AA861013034151986|10001000181123062657411200000000000010235444112500000000.600000000345.4335N10058.8249E00015
Received --> 0
Received --> $$0116AA861013037849727|1080100018112114435541120000000000000FBA00D5122500000000.600000000623.9080N10007.8627E00075
Received --> D
Received --> $$0108AA863835028447675|18804000181121183810421100002A300000100900000000.700000000314.8717N10125.6499E00022
Received --> 7
Received --> $$0108AA863835028447675|18804000181121183810421100002A300000100900000000.700000000314.8717N10125.6499E00022
Received --> 7
Received --> $$0108AA863835028447675|18804000181121183810421100002A300000100900000000.700000000314.8717N10125.6499E00022
Received --> 7
Received --> $$0108AA863835028447675|18804000181121183810421100002A300000100900000000.700000000314.8717N10125.6499E00022
Received --> 7
4

1 回答 1

13

首先,TCP 连接是流,因此没有承诺发送的“消息”将在接收端作为等效的“消息”接收。即使在考虑 Perl 6 行为之前,发送的内容也可以作为正常 TCP 行为的一部分进行拆分或合并。任何想要“消息”抽象的东西都需要在 TCP 流之上构建它(例如,通过将数据作为行发送,或者通过发送以字节为单位的大小,然后是数据)。

在 Perl 6 中,通过套接字到达的数据以Supply. Awhenever $conn { }的缩写whenever $conn.Supply { }whenever无论它被赋予什么,都会强制转换为 a Supply)。默认Supply是一个字符,解码为 UTF-8 成 Perl 6 的流Str。正如您已经收到的答案中所述,Perl 6 中的字符串在字素级别工作,因此它会保留一个字符,以防通过网络到达的下一个内容是组合字符。这是您正在经历的“截断”。(有些东西是永远无法组合的,比如\n永远不能有组合字符。这意味着面向行的协议不会遇到这种行为,可以简单地实现whenever $conn.Supply.lines { }。)

有几个选项可用:

  • Do whenever $conn.Supply(:bin) { },它将传递二进制Blob对象,这将对应于操作系统传递给 VM 的内容。然后可以.decode根据需要进行操作。这可能是你最好的选择。
  • 指定不支持组合字符的编码,例如whenever $conn.Supply(:enc('latin-1')) { }. (但是,请注意,既然\r\n是 1 个字形,那么如果消息以 1 结尾,那么如果\r下一个数据包带有 a ,则该消息将被阻止\n)。

在这两种情况下,仍然有可能在传输过程中拆分消息,但是这些将(完全和大部分分别)避免字素规范化所带来的保持一个后退的要求。

于 2018-11-23T23:14:11.570 回答