Perl: разветвление соединения WSS приводит к закрытию сокета, когда дочерний элемент выходит (не происходит с WS)

(Отредактировано, чтобы предоставить сокращенный тестовый пример в соответствии с комментариями ниже)

Я столкнулся с странной ситуацией, когда, если я разветвляю соединение «WSS» для отправки сообщения, сокет закрывается, когда дочерний элемент выходит. Однако, когда я выполняю форк для обработки соединения "WS", соединение остается открытым, когда дочерний элемент завершает работу.

  • Сведения о сервере: Perl 5.26, Ubuntu 16
  • Сведения о клиенте: Perl 5.18, OSX

Код сервера:

use Net::WebSocket::Server;
use IO::Socket::SSL;

$SIG{CHLD}='IGNORE';

my $enable_ssl = 1; # If you make this one the problem reveals itself

# you need to point this to your own certs
my $ssl_cert_file =  "/etc/letsencrypt/live/mydomain/fullchain.pem";
my $ssl_key_file =  "/etc/letsencrypt/live/mydomain/privkey.pem";

# To show the problem, all I'm doing is I'm forking and sending current time
sub process {
    my $serv = shift;
    my $pid = fork();
    if ($pid == 0 ) {
        print ("fork start\n");
        $_->send_utf8(time) for $serv->connections;
        print ("fork end\n");
        exit 0;
    }
}

my $ssl_server;

if ($ssl_enable) {
$ssl_server = IO::Socket::SSL->new(
              Listen        => 10,
              LocalPort     => 9000,
              Proto         => 'tcp',
              Reuse     => 1,
              ReuseAddr     => 1,
              SSL_cert_file => $ssl_cert_file,
              SSL_key_file  => $ssl_key_file
            );
 }

Net::WebSocket::Server->new(
    listen => $enable_ssl? $ssl_server: 9000,
    tick_period=>5,
    on_tick=> sub {
        my ($serv) = @_;
        process($serv);
        #$_->send_utf8(time) for $serv->connections;
    },
 )->start;

Вот код клиента:

my $client = AnyEvent::WebSocket::Client->new;

# replace with your server
$client->connect("wss://myserver:9000")->cb(sub {
  our $connection = eval { shift->recv };
  if ($@) {
    print ("connection error");
    warn $@;
    return;
  }

  # recieve message from the websocket...
  $connection->on(each_message => sub {
    my($connection, $message) = @_;
    my $msg = $message->body;
    print ("GOT $msg\n");
  });


});

AnyEvent->condvar->recv;

Ожидаемое поведение

Клиент продолжит отображать отметки времени

Наблюдаемое поведение

Клиент получает самое первое сообщение и распечатывает его. Когда сервер выходит из своей вилки, клиент перестает получать больше сообщений, и соединение разрывается.

Как заставить это работать

У нас есть два варианта:

  1. Не подключайтесь к серверу. Отправить сообщение прямо в подпрограмме процесса
  2. Не используйте SSL

Поэтому мой вывод - SSL + fork == проблема.

Мысли?

Я не уверен, что понимаю вашу проблему. Во-первых, поскольку здесь, похоже, нет никакого exec, попытки сделать close-on-exec и тому подобное не имеют смысла. Тогда: простой TCP-сокет не будет закрыт, если сокет находится и на родительском, и на дочернем, а потомок просто завершает работу. SSL-сокет отличается, поскольку состояние SSL пользовательского пространства не синхронизируется, если один и тот же сокет используется в родительском и дочернем. Я рекомендую вам создать программу минимальный, которая показывает проблему полный, с которой вы сталкиваетесь, и где другие могут ее воспроизводить. Показанный в настоящее время код не очень помогает.

Steffen Ullrich 05.10.2018 16:21

Частично проблема может заключаться в каком-то деструкторе, запущенном при выходе, и в этом случае может помочь POSIX::_exit. Но состояние SSL, вероятно, все равно вас убьет.

user2404501 05.10.2018 16:38

хорошо, я напишу небольшой клиент / сервер, который точно иллюстрирует проблему.

user1361529 05.10.2018 16:48

Я думаю, что @ WumpusQ.Wumbley находится на правильном пути в отношении состояния SSL. Переработан и повторно выложен Q

user1361529 05.10.2018 17:55

После работы над сокращенным тестовым примером и осознания проблемы в вилке SSL +, похоже, есть много сообщений на ту же тему. Я буду искать работоспособное решение, в котором я могу делать то, что мне нужно, и обмениваться данными между родительским и дочерним (Похоже, потоки не обмениваются данными по умолчанию ...)

user1361529 05.10.2018 18:43

Недавно я увидел еще один вопрос о TLS и форке, но сейчас не могу его найти. Главный ответ - назначить один процесс ответственным за все сокеты TLS, а все остальные процессы будут кормить его конвейерами. Но альтернативный ответ только для Linux - использовать TLS в режиме ядра

user2404501 05.10.2018 19:52

Я бы оставил сокет в основном процессе, а дочерний код запустил бы в «реальном» подпроцессе и перенаправил бы вывод в основной процесс. К сожалению, все модули AnyEvent: :( Sub) Process сами по себе fork (), так что вам нужно будет накрутить туда свои собственные ...

Corion 05.10.2018 21:25
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
360
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Therefore, my conclusion is SSL+fork == problem.

Да, проблема в том, что сначала выполняется квитирование SSL, а затем разветвляется. Таким образом, состояние SSL пользовательского пространства будет создано в родительском элементе, а вилка будет продублирована в дочернем, и эти два состояния SSL не будут синхронизироваться при первой отправке или получении данных SSL. Это означает, что два процесса не могут работать с одним и тем же сокетом SSL.

Если действительно необходимо, чтобы и родительский, и дочерний процессы использовали одно и то же SSL-соединение с одноранговым узлом, тогда дочерний процесс должен использовать родительский элемент в качестве «прокси», т.е. дочерний процесс не взаимодействует напрямую с SSL-узлом, но дочерний процесс должен общаться в plain с родителем (например, с помощью пары сокетов), который затем может перенаправить сообщение на узел SSL. Таким образом, состояние SSL сохраняется только в родительском процессе.

Но, учитывая, что только одно сообщение должно обрабатываться одновременно для одного соединения, можно было бы вместо этого не форкнуть для одного тика, а форкнуть дочерний элемент для каждого соединения, которое затем обрабатывает все сообщения в этом соединении. В этом случае рукопожатие SSL может быть выполнено полностью в дочернем элементе, прослушивая родительский сокет TCP, а не SSL, разветвляя on_connect и затем обновляя соединение до SSL в клиенте с помощью IO::Socket::start_SSL. Это также имело бы преимущество, заключающееся в том, что блокирующее подтверждение SSL (которое включает в себя несколько циклов и, следовательно, занимает некоторое время) будет выполняться в разветвленном дочернем элементе и не будет создавать родительский блок.

Ага, какая боль. Я действительно думал, что это будет просто. Спасибо, что попросили меня поработать над сокращенным тестовым примером - он помог мне понять, в чем заключалась основная проблема. Могу ли я прочитать ссылку (не RFC, что-нибудь попроще), которая расскажет мне, в чем на самом деле проблема с состояниями SSL, из-за которой это трудно сделать?

user1361529 05.10.2018 22:52

@ user1361529: это не имеет ничего общего с RFC. Просто большинство реализаций SSL выполняется как библиотека пользовательского пространства, и, таким образом, состояние SSL (то есть ключи шифрования, счетчик последовательностей ...) хранится в памяти процесса пользовательского пространства. И свойство fork заключается в том, что память процесса копируется, и, таким образом, родительский и дочерний элементы получают свои собственные версии состояния SSL, которые обновляются независимо. Но может быть только одно действительное состояние SSL для определенного SSL-соединения, то есть либо в родительском, либо в дочернем.

Steffen Ullrich 05.10.2018 23:00

Прекрасное объяснение. Спасибо.

user1361529 05.10.2018 23:03

Другие вопросы по теме