Perl6 IO::Socket::Async усекает данные

Я переписываю свой сервер сокетов P5 в P6, используя IO::Socket::Async, но полученные данные усекаются на 1 символ в конце, и этот 1 символ получен при следующем соединении. Кто-то из группы Perl6 в Facebook (Джонатан Уортингтон) указал, что это может быть связано с тем, что строки и байты обрабатываются по-разному в P6. Цитата:

В Perl 6 строки и байты обрабатываются совершенно по-разному. Следует отметить, что строки работают на уровне графем. При получении данных Unicode возможно не только разделение многобайтовой последовательности на пакеты, но и последовательность с несколькими кодовыми точками. Например, один пакет может иметь в конце букву «а», а следующий — комбинированный острый ударение. Следовательно, он не может безопасно передать «а», пока не увидит, как начинается следующий пакет.

Мой 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

person Zarul Zakuan    schedule 23.11.2018    source источник
comment
IO::Socket::Async усекает данные. На самом деле P6 помогает вам предотвратить повреждение данных. P6 делает выбор разработчиков очевидным: байты или символы. ЛИБО Вы используете :bin, поэтому данные представляют собой последовательность байтов. Таким образом, единицей передачи является байт. ИЛИ Данные представляют собой текст, последовательность то, что пользователь считает символ. Таким образом, логической единицей передачи является один символ за раз. Таким образом, P6 буферизует байты, чтобы обеспечить доставку целого символа только тогда, когда известно, что он завершен. Эта буферизация является следствием дизайна Unicode.   -  person raiph    schedule 24.11.2018


Ответы (1)


Прежде всего, TCP-соединения являются потоками, поэтому нет никаких обещаний, что отправленные «сообщения» будут получены как эквивалентные «сообщения» на принимающей стороне. Отправляемые вещи могут быть разделены или объединены как часть нормального поведения TCP, даже до того, как будет рассмотрено поведение Perl 6. Все, что требует абстракции «сообщения», должно строить ее поверх потока TCP (например, отправляя данные в виде строк или отправляя размер в байтах, за которыми следуют данные).

В Perl 6 данные, поступающие через сокет, отображаются как Supply. whenever $conn { } — это сокращение от whenever $conn.Supply { } (whenever преобразует все, что ему дано, в Supply). По умолчанию Supply является символьной единицей, декодированной как UTF-8 в поток Perl 6 Str. Как отмечено в ответе, который вы уже получили, строки в Perl 6 работают на уровне графемы, поэтому он будет удерживать символ на случай, если следующее, что поступит по сети, будет объединяющим символом. Это "усечение", которое вы испытываете. (Есть некоторые вещи, которые никогда нельзя комбинировать. Например, к \n никогда нельзя помещать объединяющий символ. Это означает, что линейно-ориентированные протоколы не будут сталкиваться с таким поведением и могут быть реализован как просто whenever $conn.Supply.lines { }.)

Доступно несколько вариантов:

  • Выполните whenever $conn.Supply(:bin) { }, который доставит двоичные объекты Blob, которые будут соответствовать тому, что ОС передала виртуальной машине. Затем это можно .decode сделать по желанию. Это, вероятно, ваш лучший выбор.
  • Укажите кодировку, которая не поддерживает комбинирование символов, например whenever $conn.Supply(:enc('latin-1')) { }. (Однако обратите внимание, что, поскольку \r\n — это 1 графема, то, если сообщение должно было заканчиваться на \r, это было бы задержано на случай, если следующий пакет придет вместе с \n).

В обоих случаях сообщения все еще могут быть разделены во время передачи, но это позволит (полностью и в основном, соответственно) избежать требования сохранения одного обратно, которое влечет за собой нормализация графемы.

person Jonathan Worthington    schedule 23.11.2018
comment
Спасибо, Джонатан! Я был так близко. Вместо этого я использовал :$bin согласно документации. - person Zarul Zakuan; 25.11.2018
comment
@ZarulZakuan В документе показаны параметры подпрограммы. При вызове подпрограммы вы передаете аргументы. Аргументы связаны с параметрами как часть вызывающего процесса, но они не одинаковы до этого. :$bin в качестве параметра означает, что вызывающая сторона должна передать пару с именем bin в качестве аргумента. Вы можете передать :$bin для обозначения bin => $bin, но вы должны инициализировать $bin значением True, если хотите, чтобы :$bin в качестве аргумента стало bin => True. В качестве альтернативы вы можете написать bin => True или просто :bin. - person raiph; 25.11.2018
comment
@raif Я вижу. Начиная с Perl 5, здесь много новых терминов и понятий. Я чувствую такое же волнение, когда изучал P5! Спасибо! - person Zarul Zakuan; 25.11.2018