(Отредактировано, чтобы предоставить сокращенный тестовый пример в соответствии с комментариями ниже)
Я столкнулся с странной ситуацией, когда, если я разветвляю соединение «WSS» для отправки сообщения, сокет закрывается, когда дочерний элемент выходит. Однако, когда я выполняю форк для обработки соединения "WS", соединение остается открытым, когда дочерний элемент завершает работу.
Код сервера:
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;
Ожидаемое поведение
Клиент продолжит отображать отметки времени
Наблюдаемое поведение
Клиент получает самое первое сообщение и распечатывает его. Когда сервер выходит из своей вилки, клиент перестает получать больше сообщений, и соединение разрывается.
Как заставить это работать
У нас есть два варианта:
Поэтому мой вывод - SSL + fork == проблема.
Мысли?
Частично проблема может заключаться в каком-то деструкторе, запущенном при выходе, и в этом случае может помочь POSIX::_exit. Но состояние SSL, вероятно, все равно вас убьет.
хорошо, я напишу небольшой клиент / сервер, который точно иллюстрирует проблему.
Я думаю, что @ WumpusQ.Wumbley находится на правильном пути в отношении состояния SSL. Переработан и повторно выложен Q
После работы над сокращенным тестовым примером и осознания проблемы в вилке SSL +, похоже, есть много сообщений на ту же тему. Я буду искать работоспособное решение, в котором я могу делать то, что мне нужно, и обмениваться данными между родительским и дочерним (Похоже, потоки не обмениваются данными по умолчанию ...)
Недавно я увидел еще один вопрос о TLS и форке, но сейчас не могу его найти. Главный ответ - назначить один процесс ответственным за все сокеты TLS, а все остальные процессы будут кормить его конвейерами. Но альтернативный ответ только для Linux - использовать TLS в режиме ядра
Я бы оставил сокет в основном процессе, а дочерний код запустил бы в «реальном» подпроцессе и перенаправил бы вывод в основной процесс. К сожалению, все модули AnyEvent: :( Sub) Process сами по себе fork (), так что вам нужно будет накрутить туда свои собственные ...





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: это не имеет ничего общего с RFC. Просто большинство реализаций SSL выполняется как библиотека пользовательского пространства, и, таким образом, состояние SSL (то есть ключи шифрования, счетчик последовательностей ...) хранится в памяти процесса пользовательского пространства. И свойство fork заключается в том, что память процесса копируется, и, таким образом, родительский и дочерний элементы получают свои собственные версии состояния SSL, которые обновляются независимо. Но может быть только одно действительное состояние SSL для определенного SSL-соединения, то есть либо в родительском, либо в дочернем.
Прекрасное объяснение. Спасибо.
Я не уверен, что понимаю вашу проблему. Во-первых, поскольку здесь, похоже, нет никакого
exec, попытки сделатьclose-on-execи тому подобное не имеют смысла. Тогда: простой TCP-сокет не будет закрыт, если сокет находится и на родительском, и на дочернем, а потомок просто завершает работу. SSL-сокет отличается, поскольку состояние SSL пользовательского пространства не синхронизируется, если один и тот же сокет используется в родительском и дочернем. Я рекомендую вам создать программу минимальный, которая показывает проблему полный, с которой вы сталкиваетесь, и где другие могут ее воспроизводить. Показанный в настоящее время код не очень помогает.