Perl6 IO::Socket::Async усекает данные
Я переписываю свой сервер сокетов P5 в P6, используя IO::Socket::Async, но полученные данные получили усеченный 1 символ в конце, и этот символ был получен при следующем соединении. Кто-то из группы Perl6 в Facebook (Джонатан Уортингтон) отметил, что это может быть связано с природой строк и байтов, которые обрабатываются в P6 очень по-разному. Цитируется:
В Perl 6 строки и байты обрабатываются совершенно по-разному. Примечательно, что струны работают на уровне графемы. При получении данных Unicode не только возможно, что многобайтовая последовательность будет разделена на пакеты, но также и последовательность с несколькими кодовыми точками. Например, один пакет может иметь букву "а" в конце, а следующий будет сочетать острый акцент. Следовательно, он не может безопасно передать "а", пока не увидит, как начинается следующий пакет.
Мой P6 работает на MoarVM
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
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
).
В обоих случаях все еще возможно разделение сообщений во время передачи, но это (полностью и в основном, соответственно) позволит избежать требования "оставайся одним", которое влечет за собой нормализация графемы.