Perl6 IO :: Socket :: Async усекает данные - программирование
Подтвердить что ты не робот

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

Я переписываю свой сервер сокетов P5 в P6, используя IO :: Socket :: Async, но полученные данные получили усеченный 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
4b9b3361

Ответ 1

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

В Perl 6 данные, поступающие через сокет, представляются в виде Supply. A 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).

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