react / socket-client
Requires
- php: >=5.3.0
- react/dns: 0.4.*|0.3.*
- react/event-loop: 0.4.*|0.3.*
- react/promise: ^2.1 || ^1.2
- react/promise-timer: ~1.0
- react/stream: ^0.6 || ^0.5 || ^0.4.5
Requires (Dev)
- clue/block-react: ^1.1
- phpunit/phpunit: ~4.8
- react/socket: ^0.5
- react/stream: ^0.6
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组件
为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 伙伴名称。这被SecureConnector
和DnsConnector
用于验证伙伴名称,也可以在您想要自定义 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.23
、5.6.7
和5.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文件。