如何加密非阻塞PHP套接字流?

Pet*_*hie 8 php sockets ssl

我试图以非阻塞(异步)方式使用PHP的stream_socket_client()函数.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.";
Run Code Online (Sandbox Code Playgroud)

输出以下内容:

Total time taken: 0.76204109191895 secs.
Run Code Online (Sandbox Code Playgroud)

显然,该函数是阻塞的(还有一个事实是,遗漏STREAM_CLIENT_ASYC_CONNECT标志并没有有意义地改变"总耗时"脚本输出.

有关为什么会发生这种情况的任何想法,以及如何强制执行非阻塞连接尝试?

rdl*_*rey 19

为什么ssl://包装器方法不起作用......

此时不可能使用ssl://系列的流包装器在PHP中建立非阻塞连接,原因很简单:

要协商SSL/TLS握手,您必须同时发送和接收数据.

您无法在单个操作中(例如,流包装器执行的操作)将这样的信息双工化,而不会阻止脚本执行.并且因为PHP最初被设计为在严格同步的环境中运行(即阻塞Web SAPI,其中每个请求都有自己的进程),这种阻塞行为是很自然的事情.

其结果是,在SSL://流包装将不能工作你想怎么即使你设置STREAM_CLIENT_ASYNC_CONNECT标志它.但是,它仍然有可能在你的非阻塞套接字操作使用PHP的流加密功能.

如何在非阻塞套接字流上启用加密...

SSL/TLS协议在底层数据传输协议之上执行.这意味着我们只在TCP/UDP/etc 之后启用加密协议.建立连接.因此,我们可以首先使用STREAM_CLIENT_ASYC_CONNECT async标志连接到远程方,然后使用在(现在连接的)套接字上启用加密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
Run Code Online (Sandbox Code Playgroud)

关于返回值的注释

===在检查加密启用调用的结果时,使用相等性非常重要.如相关手册中所述:

成功时返回TRUE,如果协商失败则返回FALSE;如果没有足够的数据则返回0,您应该再次尝试(仅限非阻塞套接字).

如果我们不使用===我们无法区分false0.