react/socket-client

此包已被弃用且不再维护。作者建议使用react/socket包。

为ReactPHP提供的异步、流式纯文本TCP/IP和基于安全的TLS连接

v0.7.0 2017-04-02 20:32 UTC

README

该组件已被合并到Socket组件中,仅为了向后兼容而存在。

$ composer require react/socket

如果您之前使用SocketClient组件建立出站客户端连接,升级不会超过几分钟。所有类都已从最新的v0.7.0版本直接合并,没有任何其他更改,因此您只需将代码更新为使用更新的命名空间,如下所示

// old from SocketClient component and namespace
$connector = new React\SocketClient\Connector($loop);
$connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
    $connection->write('');
});

// new
$connector = new React\Socket\Connector($loop);
$connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
    $connection->write('');
});

有关更多详细信息,请参阅https://github.com/reactphp/socket

以下文档适用于此组件的最后一个版本。进一步的开发将在更新的Socket组件中进行,因此强烈建议您尽快升级。

旧版SocketClient组件

Build Status Code Climate

为ReactPHP提供的异步、流式纯文本TCP/IP和基于安全的TLS连接

您可以将其视为fsockopen()stream_socket_client()的异步版本。如果您想向远程服务器发送或接收数据,首先必须与远程端建立连接。通过互联网/网络建立此连接可能需要一些时间,因为它需要执行多个步骤(如解析目标主机名、完成TCP/IP握手和启用TLS)才能完成。此组件提供所有这些的异步版本,以便您可以建立和处理多个连接而不会阻塞。

目录

使用方法

ConnectorInterface

ConnectorInterface负责提供建立流式连接的接口,例如常规TCP/IP连接。

这是本包中定义的主要接口,并在React的庞大生态系统中被广泛使用。

大多数高级组件(如HTTP、数据库或其他网络服务客户端)接受实现此接口的实例以创建其到底层网络服务的TCP/IP连接。这通常通过依赖注入完成,因此实际上将此实现与其他任何此接口的实现交换相当简单。

该接口只提供一种方法

connect()

可以使用connect(string $uri): PromiseInterface<ConnectionInterface, Exception>方法创建到指定远程地址的流式连接。

它返回一个Promise,在连接成功时,通过实现ConnectionInterface的流来履行,或者在连接不成功时,以一个Exception来拒绝。

$connector->connect('google.com:443')->then(
    function (ConnectionInterface $connection) {
        // connection successfully established
    },
    function (Exception $error) {
        // failed to connect due to $error
    }
);

有关更多详细信息,请参阅ConnectionInterface

返回的Promise必须以这种方式实现,使其在仍然挂起时可以被取消。取消挂起的Promise必须用一个Exception来拒绝其值。它应该根据适用情况清理任何底层资源或引用。

$promise = $connector->connect($uri);

$promise->cancel();

ConnectionInterface

ConnectionInterface用于表示任何出站连接,例如正常的TCP/IP连接。

出站连接是一个双向流(可读和可写),实现了React的DuplexStreamInterface。它包含此连接已建立到的本地和远程地址的附加属性。

通常,实现此ConnectionInterface的实例都是由所有实现ConnectorInterface的类返回的。

请注意,此接口仅用于表示出站连接的客户端端点。它不得用于在服务器端上下文中表示入站连接。如果您想接受入站连接,请使用Socket组件。

因为ConnectionInterface实现了底层的DuplexStreamInterface,所以您可以使用其任何事件和方法,就像通常一样。

$connection->on('data', function ($chunk) {
    echo $chunk;
});

$connection->on('end', function () {
    echo 'ended';
});

$connection->on('error', function (Exception $e) {
    echo 'error: ' . $e->getMessage();
});

$connection->on('close', function () {
    echo 'closed';
});

$connection->write($data);
$connection->end($data = null);
$connection->close();
//

有关更多详细信息,请参阅DuplexStreamInterface

getRemoteAddress()

getRemoteAddress(): ?string方法可以用来返回此连接已建立到的远程地址(IP和端口)。

$address = $connection->getRemoteAddress();
echo 'Connected to ' . $address . PHP_EOL;

如果无法确定远程地址或目前未知(例如,在连接关闭后),它可能会返回一个NULL值。

否则,它将以字符串值的形式返回完整的远程地址。如果这是一个基于TCP/IP的连接,并且您只想得到远程IP,您可以使用类似以下的方式

$address = $connection->getRemoteAddress();
$ip = trim(parse_url('tcp://' . $address, PHP_URL_HOST), '[]');
echo 'Connected to ' . $ip . PHP_EOL;

getLocalAddress()

getLocalAddress(): ?string方法可以用来返回此连接已建立自的完整本地地址(IP和端口)。

$address = $connection->getLocalAddress();
echo 'Connected via ' . $address . PHP_EOL;

如果无法确定本地地址或目前未知(例如,在连接关闭后),它可能会返回一个NULL值。

否则,它将以字符串值的形式返回完整的本地地址。

此方法补充了getRemoteAddress()方法,因此不应混淆。

如果您的系统有多个接口(例如,WAN和LAN接口),您可以使用此方法来找出实际用于此连接的接口。

Connector

Connector类是这个包中的主要类,它实现了ConnectorInterface并允许您创建流连接。

您可以使用此连接器创建任何类型的流连接,例如明文TCP/IP、安全TLS或本地Unix连接流。

它绑定到主事件循环,可以像这样使用

$loop = React\EventLoop\Factory::create();
$connector = new Connector($loop);

$connector->connect($uri)->then(function (ConnectionInterface $connection) {
    $connection->write('...');
    $connection->end();
});

$loop->run();

要创建明文TCP/IP连接,您可以简单地传递一个主机和端口号组合,如下所示

$connector->connect('www.google.com:80')->then(function (ConnectionInterface $connection) {
    $connection->write('...');
    $connection->end();
});

如果您在目标URI中未指定URI方案,它将默认假定tcp://并建立明文TCP/IP连接。请注意,TCP/IP连接需要在目标URI中包含主机和端口号部分,如上所示,其他URI组件都是可选的。

要创建安全TLS连接,您可以使用tls:// URI方案,如下所示

$connector->connect('tls://www.google.com:443')->then(function (ConnectionInterface $connection) {
    $connection->write('...');
    $connection->end();
});

要创建本地Unix域套接字连接,您可以使用unix:// URI方案,如下所示

$connector->connect('unix:///tmp/demo.sock')->then(function (ConnectionInterface $connection) {
    $connection->write('...');
    $connection->end();
});

在底层,Connector被实现为该包中实现的低级连接器的高级包装器。这意味着它也共享它们的所有特性和实现细节。如果您想在高级协议实现中进行类型提示,您应使用通用的ConnectorInterface

特别是,Connector类默认使用谷歌的公共DNS服务器8.8.8.8将所有主机名解析为底层IP地址。这意味着它也会忽略您的hosts文件和resolve.conf,这意味着默认情况下您无法连接到localhost和其他非公共主机名。如果您想使用自定义DNS服务器(例如本地DNS中继),您可以像这样设置Connector

$connector = new Connector($loop, array(
    'dns' => '127.0.1.1'
));

$connector->connect('localhost:80')->then(function (ConnectionInterface $connection) {
    $connection->write('...');
    $connection->end();
});

如果您根本不想使用DNS解析器,并且只想连接到IP地址,您也可以像这样设置您的Connector

$connector = new Connector($loop, array(
    'dns' => false
));

$connector->connect('127.0.0.1:80')->then(function (ConnectionInterface $connection) {
    $connection->write('...');
    $connection->end();
});

高级:如果您需要一个自定义的DNS Resolver实例,您也可以像这样设置您的Connector

$dnsResolverFactory = new React\Dns\Resolver\Factory();
$resolver = $dnsResolverFactory->createCached('127.0.1.1', $loop);

$connector = new Connector($loop, array(
    'dns' => $resolver
));

$connector->connect('localhost:80')->then(function (ConnectionInterface $connection) {
    $connection->write('...');
    $connection->end();
});

默认情况下,tcp://tls:// URI方案将使用尊重您的default_socket_timeout ini设置的超时值(默认为60s)。如果您想自定义超时值,您可以简单地像这样传递

$connector = new Connector($loop, array(
    'timeout' => 10.0
));

同样,如果您不想应用超时并让操作系统处理,您可以传递一个布尔标志,如下所示

$connector = new Connector($loop, array(
    'timeout' => false
));

默认情况下,Connector支持tcp://tls://unix:// URI方案。如果您想明确禁止这些中的任何一个,您可以简单地传递布尔标志,如下所示

// only allow secure TLS connections
$connector = new Connector($loop, array(
    'tcp' => false,
    'tls' => true,
    'unix' => false,
));

$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $connection) {
    $connection->write('...');
    $connection->end();
});

tcp://tls://也接受传递给底层连接器的附加上下文选项。如果您想明确传递附加上下文选项,您可以简单地传递上下文选项的数组,如下所示

// allow insecure TLS connections
$connector = new Connector($loop, array(
    'tcp' => array(
        'bindto' => '192.168.0.1:0'
    ),
    'tls' => array(
        'verify_peer' => false,
        'verify_peer_name' => false
    ),
));

$connector->connect('tls://localhost:443')->then(function (ConnectionInterface $connection) {
    $connection->write('...');
    $connection->end();
});

有关上下文选项的更多详细信息,请参阅PHP文档中关于socket上下文选项SSL上下文选项的说明。

高级:默认情况下,Connector支持tcp://tls://unix:// URI方案。为此,它会自动设置所需的连接器类。如果您想为这些中的任何一个明确传递自定义连接器,您可以简单地传递实现ConnectorInterface的实例,如下所示

$dnsResolverFactory = new React\Dns\Resolver\Factory();
$resolver = $dnsResolverFactory->createCached('127.0.1.1', $loop);
$tcp = new DnsConnector(new TcpConnector($loop), $resolver);

$tls = new SecureConnector($tcp, $loop);

$unix = new UnixConnector($loop);

$connector = new Connector($loop, array(
    'tcp' => $tcp,
    'tls' => $tls,
    'unix' => $unix,

    'dns' => false,
    'timeout' => false,
));

$connector->connect('google.com:80')->then(function (ConnectionInterface $connection) {
    $connection->write('...');
    $connection->end();
});

在内部,tcp:// 连接器总会被 DNS 解析器包装,除非您像上述示例中那样禁用 DNS。在这种情况下,tcp:// 连接器接收实际的域名主机名,而不是仅接收解析的 IP 地址,因此负责执行查找。在内部,自动创建的 tls:// 连接器将始终包装底层的 tcp:// 连接器,以便在启用安全的 TLS 模式之前建立底层的明文 TCP/IP 连接。如果您只想为安全的 TLS 连接使用自定义的底层 tcp:// 连接器,则可以像上面那样显式传递一个 tls:// 连接器。在内部,除非您像上述示例中那样禁用超时,否则 tcp://tls:// 连接器将始终被 TimeoutConnector 包装。

高级用法

TcpConnector

React\SocketClient\TcpConnector 类实现了 ConnectorInterface,并允许您创建与任何 IP-端口组合的明文 TCP/IP 连接。

$tcpConnector = new React\SocketClient\TcpConnector($loop);

$tcpConnector->connect('127.0.0.1:80')->then(function (ConnectionInterface $connection) {
    $connection->write('...');
    $connection->end();
});

$loop->run();

请参阅第一个示例

可以通过取消其挂起的承诺来取消挂起的连接尝试,如下所示:

$promise = $tcpConnector->connect('127.0.0.1:80');

$promise->cancel();

在挂起的承诺上调用 cancel() 将关闭底层的套接字资源,从而取消挂起的 TCP/IP 连接,并拒绝生成的承诺。

您可以选择将额外的 套接字上下文选项 作为如下构造函数的参数传递:

$tcpConnector = new React\SocketClient\TcpConnector($loop, array(
    'bindto' => '192.168.0.1:0'
));

请注意,此类仅允许您连接到 IP-端口组合。如果给定的 URI 无效,不包含有效的 IP 地址和端口,或包含其他方案,它将使用 InvalidArgumentException 拒绝。

如果给定的 URI 似乎有效,但连接失败(例如,如果远程主机拒绝连接等),它将使用 RuntimeException 拒绝。

如果您想连接到主机名-端口组合,还可以参阅以下章节。

高级用法:内部,TcpConnector 为每个流资源分配一个空的 上下文 资源。如果目标 URI 包含 hostname 查询参数,其值将用于设置 TLS 伙伴名称。这被 SecureConnectorDnsConnector 用于验证伙伴名称,也可以在您想要自定义 TLS 伙伴名称时使用。

DnsConnector

DnsConnector 类实现了 ConnectorInterface,并允许您创建与任何主机名-端口组合的明文 TCP/IP 连接。

它通过装饰给定的 TcpConnector 实例来实现这一点,使其首先通过 DNS(如果适用)查找给定的域名,然后建立到解析的目标 IP 地址的底层 TCP/IP 连接。

确保按照如下方式设置您的 DNS 解析器和底层 TCP 连接器:

$dnsResolverFactory = new React\Dns\Resolver\Factory();
$dns = $dnsResolverFactory->createCached('8.8.8.8', $loop);

$dnsConnector = new React\SocketClient\DnsConnector($tcpConnector, $dns);

$dnsConnector->connect('www.google.com:80')->then(function (ConnectionInterface $connection) {
    $connection->write('...');
    $connection->end();
});

$loop->run();

请参阅第一个示例

可以通过取消其挂起的承诺来取消挂起的连接尝试,如下所示:

$promise = $dnsConnector->connect('www.google.com:80');

$promise->cancel();

在挂起的承诺上调用 cancel() 将取消底层的 DNS 查找和/或底层 TCP/IP 连接,并拒绝生成的承诺。

高级用法:内部,DnsConnector 依赖于 Resolver 来查找给定主机名的 IP 地址。然后它将使用此 IP 地址替换目标 URI 中的主机名,并附加一个 hostname 查询参数,并将此更新的 URI 传递给底层的连接器。底层连接器负责创建到目标 IP 地址的连接,而此查询参数可用于检查原始主机名,并由 TcpConnector 用于设置 TLS 伙伴名称。如果显式给出 hostname,则此查询参数不会被修改,这在您想自定义 TLS 伙伴名称时很有用。

SecureConnector

SecureConnector 类实现了 ConnectorInterface,并允许您创建到任何主机名-端口号组合的 TLS(以前称为 SSL)连接。

它是通过装饰给定的 DnsConnector 实例来实现的,这样它首先创建一个明文 TCP/IP 连接,然后在此流上启用 TLS 加密。

$secureConnector = new React\SocketClient\SecureConnector($dnsConnector, $loop);

$secureConnector->connect('www.google.com:443')->then(function (ConnectionInterface $connection) {
    $connection->write("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n");
    ...
});

$loop->run();

另请参阅第二个示例

可以通过取消其挂起的承诺来取消挂起的连接尝试,如下所示:

$promise = $secureConnector->connect('www.google.com:443');

$promise->cancel();

在挂起的承诺上调用 cancel() 将取消底层 TCP/IP 连接和/或 SSL/TLS 协商,并拒绝产生的承诺。

您可以可选地通过以下方式向构造函数传递额外的 SSL 上下文选项

$secureConnector = new React\SocketClient\SecureConnector($dnsConnector, $loop, array(
    'verify_peer' => false,
    'verify_peer_name' => false
));

高级用法:内部,SecureConnector 依赖于在底层流资源上设置所需的 上下文选项。因此,它应在连接器堆栈中的某个位置与 TcpConnector 一起使用,以便为每个流资源分配一个空的 上下文 资源并验证伙伴名称。否则,如果所有流资源都使用单个、共享的 默认上下文 资源,则可能会导致 TLS 伙伴名称不匹配错误或一些难以追踪的竞争条件。

TimeoutConnector

TimeoutConnector 类实现了 ConnectorInterface,并允许您为任何现有的连接器实例添加超时处理。

它是通过装饰任何给定的 ConnectorInterface 实例,并启动一个定时器来实现的,该定时器将在连接尝试耗时过长时自动拒绝和终止。

$timeoutConnector = new React\SocketClient\TimeoutConnector($connector, 3.0, $loop);

$timeoutConnector->connect('google.com:80')->then(function (ConnectionInterface $connection) {
    // connection succeeded within 3.0 seconds
});

另请参阅示例

可以通过取消其挂起的承诺来取消挂起的连接尝试,如下所示:

$promise = $timeoutConnector->connect('google.com:80');

$promise->cancel();

在挂起的承诺上调用 cancel() 将取消底层连接尝试,终止定时器并拒绝产生的承诺。

UnixConnector

UnixConnector 类实现了 ConnectorInterface,并允许您连接到类似这样的 Unix 域套接字路径

$connector = new React\SocketClient\UnixConnector($loop);

$connector->connect('/tmp/demo.sock')->then(function (ConnectionInterface $connection) {
    $connection->write("HELLO\n");
});

$loop->run();

连接到 Unix 域套接字是一个原子操作,即其承诺将立即解决(或解决或拒绝)。因此,在产生的承诺上调用 cancel() 没有任何效果。

安装

安装此库的推荐方法是通过 Composer初学者?

这将安装最新支持的版本

$ composer require react/socket-client:^0.7

有关版本升级的更多详细信息,请参阅变更日志

本项目支持在旧版PHP 5.3至当前PHP 7+和HHVM上运行。强烈建议为此项目使用PHP 7+,部分原因是其显著的性能提升,部分原因是旧版PHP需要以下所述的多个解决方案。

从PHP 5.6开始,安全TLS连接获得了一些重大升级,默认设置现在更为安全,而旧版本则需要明确设置上下文选项。此库不对这些上下文选项承担责任,因此用户需要根据上述描述设置适当的上下文选项。

PHP 5.6.8之前的所有版本都存在一个缓冲问题,即从流式TLS连接中读取可能会落后一个data事件。此库实现了绕过该问题的解决方案,尝试在上述版本中刷新完整的数据缓冲区,但我们收到一些报告称这仍然会影响某些旧版本(5.5.235.6.75.6.8)。请注意,这仅影响某些高层流式协议,例如TLS上的IRC,但不应该影响TLS上的HTTP(HTTPS)。需要进一步调查此问题。有关更多信息,此问题也由我们的测试套件覆盖。

本项目还支持在HHVM上运行。请注意,非常旧的HHVM版本(< 3.8)不支持安全的TLS连接,因为它缺少所需的stream_socket_enable_crypto()函数。因此,在受影响的版本上尝试创建安全的TLS连接将返回一个被拒绝的承诺。此问题也由我们的测试套件覆盖,测试套件将跳过受影响版本的相关测试。

测试

要运行测试套件,首先需要克隆此存储库,然后通过Composer安装所有依赖项(Composer)

$ composer install

要运行测试套件,请转到项目根目录并运行

$ php vendor/bin/phpunit

许可证

MIT,请参阅LICENSE文件