【问题标题】:How to encrypt non-blocking PHP socket streams?如何加密非阻塞 PHP 套接字流?
【发布时间】:2012-06-11 01:30:15
【问题描述】:

我正在尝试以非阻塞(异步)方式使用 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.";

输出以下内容:

Total time taken: 0.76204109191895 secs.

显然,该函数正在阻塞(也支持省略 STREAM_CLIENT_ASYC_CONNECT 标志不会有意义地更改“总时间”脚本输出。

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

【问题讨论】:

  • 仅供参考 - 将标志更改为 STREAM_CLIENT_ASYNC_CONNECT | STREAM_CLIENT_CONNECT 似乎也没有强制执行非阻塞行为。
  • 搜索 Google 似乎表明“ssl://”和 STREAM_CLIENT_ASYNC_CONNECT 存在一些问题。也许非阻塞连接因此被禁用?或者也许不是非阻塞的实际连接而是读/写?

标签: php sockets ssl


【解决方案1】:

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

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

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

在不阻塞脚本执行的情况下,您根本无法在单个操作(例如流包装器的作用)中复制此类信息。而且因为 PHP 最初被设计为在严格同步的环境中运行(即每个请求都有自己的进程的阻塞 Web SAPI),所以这种阻塞行为是很自然的事情。

因此,即使您设置了 STREAM_CLIENT_ASYNC_CONNECT 标志,ssl:// 流包装器也不会按照您希望的方式运行。但是,仍然可以在您的非阻塞套接字操作中使用 PHP 的流加密功能。

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

SSL/TLS 协议在底层数据传输协议之上执行。这意味着我们只在 TCP/UDP/etc 之后启用加密协议。连接建立。因此,我们能够首先使用 STREAM_CLIENT_ASYC_CONNECT 异步标志连接到远程方,然后使用 stream_socket_enable_crypto() 在(现在连接的)套接字上启用加密。

没有错误处理的简单示例

本示例假设您了解如何使用stream_select()(或等效的描述符通知库以非阻塞方式使用套接字)。没有处理潜在的套接字错误。

<?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,您应该重试(仅适用于非阻塞套接字)。

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

【讨论】:

  • 很好的答案!感谢您提供非常有用的信息。
  • 要启用stream_socket_serverstream_select 的加密,我必须在stream_socket_accept 之前将套接字设置为阻塞。之后我可以立即将其设置回非阻塞。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-10-31
  • 2013-10-15
  • 1970-01-01
  • 1970-01-01
  • 2013-06-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多