Как шифровать неблокирующие потоки сокетов PHP?

Я пытаюсь использовать функцию stream_socket_client () PHP в неблокирующем (асинхронном) режиме. Документация на веб-сайте PHP указывает, что флажок опции STREAM_CLIENT_ASYNC_CONNECT должен включить это. Однако следующий код …

$start_time = microtime(true); $sockets[$i] = stream_socket_client('ssl://74.125.47.109:993', $errint, $errstr, 1, STREAM_CLIENT_ASYNC_CONNECT); $end_time = microtime(true); echo "Total time taken: " . ($end_time-$start_time) . " secs."; 

Выводит следующее:

 Total time taken: 0.76204109191895 secs. 

Очевидно, что эта функция блокируется (также подтверждается тем фактом, что упущение флага STREAM_CLIENT_ASYC_CONNECT не приводит к существенному изменению вывода сценария «общее время».

Любые идеи о том, почему это может происходить, и как обеспечить соблюдение попытки неблокирующего соединения?

Почему подход оболочки ssl: // не работает …

Невозможно использовать семейство оболочек ssl: // для создания неблокирующих соединений в PHP в это время, и причина проста:

Чтобы согласовать рукопожатие SSL / TLS, вы должны отправлять и получать данные.

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

В результате оболочка потока ssl: // не будет работать так, как вы хотите, даже если вы установите флаг STREAM_CLIENT_ASYNC_CONNECT. Тем не менее, все еще возможно использовать возможности шифрования потока PHP в ваших неблокирующих операциях сокета.

Как включить шифрование в ваших неблокирующих потоках сокетов …

Протоколы SSL / TLS выполняются поверх базового протокола передачи данных. Это означает, что мы разрешаем только протоколы шифрования после TCP / UDP / etc. соединение установлено. В результате мы можем сначала подключиться к удаленной стороне, используя флаг async STREAM_CLIENT_ASYC_CONNECT, а затем включить криптографию в (теперь подключенном) сокете с помощью stream_socket_enable_crypto() .

Простой пример без обработки ошибок

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

 <?php // connect + encrypt a socket asynchronously $uri = 'tcp://www.google.com:443'; $timeout = 42; $flags = STREAM_CLIENT_ASYNC_CONNECT; $socket = stream_socket_client($uri, $errno, $errstr, $timeout, $flags); stream_set_blocking($socket, false); // Once the async connection is actually established it will be "writable." // Here we use stream_select to find out when the socket becomes writable. while (1) { $w = [$socket]; $r = $e = []; if (stream_select($r, $w, $e, 30, 0)) { break; // the async connect is finished } } // Now that our socket is connected lets enable crypto $crypto = STREAM_CRYPTO_METHOD_TLS_CLIENT; while (1) { $w = [$socket]; $r = $e = []; if (stream_select($r, $w, $e, 30, 0)) { break; // the async connect is finished $result = stream_socket_enable_crypto($socket, $enable=true, $crypto); if ($result === true) { // Crypto enabled, we're finished! break; } elseif ($result === false) { die("crypto failed :("); } else { // handshake isn't finished yet. Do another trip around the loop. } } } // Send/receive encrypted data on $socket here 

Примечание по возвращаемым значениям

Очень важно использовать равенство === при проверке результатов наших криптозащитных вызовов. Как указано в соответствующем руководстве:

Возвращает TRUE при успешном завершении, FALSE, если переговоры завершились неудачно или 0, если данных недостаточно, и вы должны попробовать еще раз (только для неблокирующих сокетов).

Если мы не используем === мы не можем различать false и 0 .