How to encrypt non-blocking PHP socket streams?

I am trying to use the PHP function stream_socket_client () in non-blocking (asynchronous) mode. The documentation on the PHP website indicates that the option flag STREAM_CLIENT_ASYNC_CONNECT should enable this. However, the following code ...

$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."; 

Outputs the following:

 Total time taken: 0.76204109191895 secs. 

Obviously, this function is blocked (also confirmed by the fact that omitting the flag STREAM_CLIENT_ASYC_CONNECT does not significantly change the output of the "total time" script.

Any ideas on why this might happen, and how to enforce a non-blocking connection attempt?

+8
php ssl sockets
source share
1 answer

Why ssl: // shell approach doesn't work ...

It is not possible to use the ssl: // shell family to create non-blocking connections in PHP at this time, and the reason is simple:

To negotiate SSL / TLS acknowledgment, you must send and receive data.

You simply cannot duplicate information like this in a single operation (for example, what thread wrappers do) without blocking the execution of the script. And since PHP was originally designed to work in strictly synchronous environments (i.e., Blocking Web SAPIs, where each request has its own process), this locking behavior is a natural thing.

As a result, the ssl: // stream wrapper will not work the way you want, even if you set the STREAM_CLIENT_ASYNC_CONNECT flag. However, you can still use the PHP stream encryption capabilities in your non-blocking socket operations.

How to enable encryption in your non-blocking socket streams ...

SSL / TLS protocols run on top of the underlying transport protocol. This means that we only allow encryption protocols after TCP / UDP / etc. connection is established. As a result, we can first connect to the remote side using the async STREAM_CLIENT_ASYC_CONNECT flag, and then enable cryptography on the (now connected) socket using stream_socket_enable_crypto() .

A simple example without error handling

This example assumes that you understand how to use stream_select() (or equivalent lib notification information to work with sockets in a non-blocking way). Unable to handle potential socket errors.

 <?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 

Return Values ​​Note

It is very important to use equality === when checking the results of our cryptocurrency calls. As indicated in the relevant manual:

Returns TRUE on success, FALSE if negotiations failed, or 0 if there is not enough data, and you should try again (only for non-blocking sockets).

If we do not use === , we cannot distinguish between false and 0 .

+18
source share

All Articles