react / socket
为ReactPHP提供异步、流式文本TCP/IP和安全的TLS套接字服务器和客户端连接
Requires
- php: >=5.3.0
- evenement/evenement: ^3.0 || ^2.0 || ^1.0
- react/dns: ^1.13
- react/event-loop: ^1.2
- react/promise: ^3.2 || ^2.6 || ^1.2.1
- react/stream: ^1.4
Requires (Dev)
- phpunit/phpunit: ^9.6 || ^5.7 || ^4.8.36
- react/async: ^4.3 || ^3.3 || ^2
- react/promise-stream: ^1.4
- react/promise-timer: ^1.11
- 3.x-dev
- 1.x-dev
- v1.16.0
- v1.15.0
- v1.14.0
- v1.13.0
- v1.12.0
- v1.11.0
- v1.10.0
- v1.9.0
- v1.8.0
- v1.7.0
- v1.6.0
- v1.5.0
- v1.4.0
- v1.3.0
- v1.2.1
- v1.2.0
- v1.1.0
- v1.0.0
- v0.8.12
- v0.8.11
- v0.8.10
- v0.8.9
- v0.8.8
- v0.8.7
- v0.8.6
- v0.8.5
- v0.8.4
- v0.8.3
- v0.8.2
- v0.8.1
- v0.8.0
- v0.7.2
- v0.7.1
- v0.7.0
- v0.6.0
- v0.5.1
- v0.5.0
- v0.4.6
- v0.4.5
- v0.4.4
- v0.4.3
- v0.4.2
- v0.4.1
- v0.4.0
- v0.3.4
- v0.3.3
- v0.3.2
- v0.3.1
- v0.3.0
- v0.2.6
- v0.2.3
- v0.2.0
- v0.1.1
- v0.1.0
This package is auto-updated.
Last update: 2024-08-26 10:47:37 UTC
README
为ReactPHP提供异步、流式文本TCP/IP和安全的TLS套接字服务器和客户端连接。
开发版本:此分支包含即将发布的v3版本的代码。要获取当前稳定版v1的代码,请查看
1.x
分支。即将发布的v3版本将是此包的未来发展方向。然而,我们仍将积极支持v1,以帮助尚未升级到最新版本的用户。有关更多信息,请参阅安装说明。
套接字库提供了基于EventLoop
和Stream
组件的套接字层服务器和客户端的可重用接口。其服务器组件允许您构建接受来自网络客户端(如HTTP服务器)的传入连接的网络服务器。其客户端组件允许您构建建立到网络服务器(如HTTP或数据库客户端)的传出连接的网络客户端。此库提供了处理所有这些的异步、流式方法,因此您可以处理多个并发连接而不会阻塞。
目录
快速入门示例
这是一个当你发送任何内容时就会关闭连接的服务器
$socket = new React\Socket\SocketServer('127.0.0.1:8080'); $socket->on('connection', function (React\Socket\ConnectionInterface $connection) { $connection->write("Hello " . $connection->getRemoteAddress() . "!\n"); $connection->write("Welcome to this amazing server!\n"); $connection->write("Here's a tip: don't say anything.\n"); $connection->on('data', function ($data) use ($connection) { $connection->close(); }); });
请参阅示例。
这是一个客户端,它会输出上述服务器的输出,然后尝试发送一个字符串给它
$connector = new React\Socket\Connector(); $connector->connect('127.0.0.1:8080')->then(function (React\Socket\ConnectionInterface $connection) { $connection->pipe(new React\Stream\WritableResourceStream(STDOUT)); $connection->write("Hello World!\n"); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
连接使用
ConnectionInterface
ConnectionInterface
用于表示任何传入和传出连接,如常规TCP/IP连接。
传入或传出连接是一个双工流(可读和可写),它实现了React的DuplexStreamInterface
。它包含一些附加属性,用于表示此连接已建立或从中建立的本地和远程地址(客户端IP)。
最常见的情况下,实现此ConnectionInterface
的实例由所有实现ServerInterface
的类生成,并由所有实现ConnectorInterface
的类使用。
因为 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
方法返回已建立此连接的完整远程地址(URI)。
$address = $connection->getRemoteAddress(); echo 'Connection with ' . $address . PHP_EOL;
如果无法确定远程地址或此时未知(例如,在连接关闭后),它可能会返回一个 NULL
值。
否则,它将以字符串值返回完整地址(URI),例如 tcp://127.0.0.1:8080
、tcp://[::1]:80
、tls://127.0.0.1:443
、unix://example.sock
或 unix:///path/to/example.sock
。请注意,各个 URI 组件是应用程序特定的,并取决于底层传输协议。
如果这是一个基于 TCP/IP 的连接,并且您只想获取远程 IP,您可以使用类似以下内容
$address = $connection->getRemoteAddress(); $ip = trim(parse_url($address, PHP_URL_HOST), '[]'); echo 'Connection with ' . $ip . PHP_EOL;
getLocalAddress()
getLocalAddress(): ?string
方法返回已建立此连接的完整本地地址(URI)。
$address = $connection->getLocalAddress(); echo 'Connection with ' . $address . PHP_EOL;
如果无法确定本地地址或此时未知(例如,在连接关闭后),它可能会返回一个 NULL
值。
否则,它将以字符串值返回完整地址(URI),例如 tcp://127.0.0.1:8080
、tcp://[::1]:80
、tls://127.0.0.1:443
、unix://example.sock
或 unix:///path/to/example.sock
。请注意,各个 URI 组件是应用程序特定的,并取决于底层传输协议。
此方法与 getRemoteAddress()
方法互补,因此不应混淆。
如果您的 TcpServer
实例正在监听多个接口(例如,使用地址 0.0.0.0
),您可以使用此方法来找出哪个接口实际上接受了此连接(例如,公共或本地接口)。
如果您的系统有多个接口(例如,WAN 和 LAN 接口),您可以使用此方法来找出实际用于此连接的接口。
服务器使用
ServerInterface
ServerInterface
负责提供接受传入流式连接的接口,例如正常的 TCP/IP 连接。
大多数高级组件(如 HTTP 服务器)接受实现此接口的实例以接受传入流式连接。这通常通过依赖注入来完成,因此实际上交换此实现与其他接口实现相对简单。这意味着您应该对接口类型进行提示,而不是对接口的具体实现进行提示。
除了定义一些方法之外,此接口还实现了 EventEmitterInterface
,允许您对某些事件做出反应。
连接事件
当建立新的连接时,即新客户端连接到此服务器套接字时,将发出 connection
事件。
$socket->on('connection', function (React\Socket\ConnectionInterface $connection) { echo 'new connection' . PHP_EOL; });
有关处理传入连接的详细信息,请参阅 ConnectionInterface
。
错误事件
当从客户端接受新连接时出现错误时,将发出 error
事件。
$socket->on('error', function (Exception $e) { echo 'error: ' . $e->getMessage() . PHP_EOL; });
请注意,这不是一个致命的错误事件,即服务器即使在发生此事件后还会继续监听新的连接。
getAddress()
您可以使用 getAddress(): ?string
方法来返回服务器当前正在监听的完整地址(URI)。
$address = $socket->getAddress(); echo 'Server listening on ' . $address . PHP_EOL;
如果无法确定地址或此时未知(例如,在套接字关闭后),它可能会返回一个 NULL
值。
否则,它将以字符串值返回完整地址(URI),例如 tcp://127.0.0.1:8080
、tcp://[::1]:80
、tls://127.0.0.1:443
、unix://example.sock
或 unix:///path/to/example.sock
。请注意,各个 URI 组件是应用程序特定的,并取决于底层传输协议。
如果这是一个基于TCP/IP的服务器,并且您只想使用本地端口,您可以使用类似以下的方式:
$address = $socket->getAddress(); $port = parse_url($address, PHP_URL_PORT); echo 'Server listening on port ' . $port . PHP_EOL;
暂停()
可以使用pause(): void
方法来暂停接受新的连接。
从EventLoop中移除socket资源,从而停止接受新的连接。请注意,监听socket保持活跃状态,不会被关闭。
这意味着新的传入连接将保持在操作系统的backlog中,直到其可配置的backlog被填满。一旦backlog被填满,操作系统可能会拒绝进一步的传入连接,直到backlog通过恢复接受新的连接再次被清空。
一旦服务器被暂停,不应再发出任何connection
事件。
$socket->pause(); $socket->on('connection', assertShouldNeverCalled());
尽管一般不推荐,此方法仅为建议性,服务器可能继续发出connection
事件。
除非另有说明,成功打开的服务器不应以暂停状态启动。
您可以通过再次调用resume()
来继续处理事件。
请注意,这两种方法可以多次调用,特别是多次调用pause()
不应有任何效果。同样,在调用close()
之后调用也是无操作的。
恢复()
可以使用resume(): void
方法来恢复接受新的传入连接。
在之前的pause()
之后将socket资源重新附加到EventLoop。
$socket->pause(); Loop::addTimer(1.0, function () use ($socket) { $socket->resume(); });
请注意,这两种方法可以多次调用,特别是如果没有先前的pause()
而调用resume()
不应有任何效果。同样,在调用close()
之后调用也是无操作的。
关闭()
可以使用close(): void
方法来关闭此监听socket。
这将停止在此socket上监听新的传入连接。
echo 'Shutting down server socket' . PHP_EOL; $socket->close();
在同一实例上多次调用此方法是无操作的。
SocketServer
SocketServer
类是本包中实现ServerInterface
的主要类,允许您接受传入的流连接,如明文TCP/IP或安全的TLS连接流。
为了接受明文TCP/IP连接,您可以简单地传递一个主机和端口号组合,如下所示:
$socket = new React\Socket\SocketServer('127.0.0.1:8080');
监听本地主机地址127.0.0.1
意味着它将无法从系统外部访问。为了更改socket监听的主机,您可以提供接口的IP地址或使用特殊的0.0.0.0
地址来监听所有接口。
$socket = new React\Socket\SocketServer('0.0.0.0:8080');
如果您想监听IPv6地址,您必须将主机用方括号括起来。
$socket = new React\Socket\SocketServer('[::1]:8080');
为了使用随机端口分配,您可以使用端口号0
。
$socket = new React\Socket\SocketServer('127.0.0.1:0'); $address = $socket->getAddress();
为了监听Unix域socket(UDS)路径,您必须使用unix://
方案来前缀URI。
$socket = new React\Socket\SocketServer('unix:///tmp/server.sock');
为了监听现有的文件描述符(FD)编号,您必须使用php://fd/
方案来前缀URI,如下所示:
$socket = new React\Socket\SocketServer('php://fd/3');
如果给定的URI无效,不包含端口号,包含其他方案,或者它包含主机名,它将抛出InvalidArgumentException
。
// throws InvalidArgumentException due to missing port $socket = new React\Socket\SocketServer('127.0.0.1');
如果给定的URI看起来是有效的,但监听它失败(例如,如果端口已被占用,或者端口号低于1024可能需要root访问等),它将抛出RuntimeException
。
$first = new React\Socket\SocketServer('127.0.0.1:8080'); // throws RuntimeException because port is already in use $second = new React\Socket\SocketServer('127.0.0.1:8080');
请注意,这些错误条件可能因您的系统或/和配置而异。请参阅异常消息和代码以获取有关实际错误条件的更多详细信息。
您可以选择指定底层的流socket资源的TCP socket上下文选项,如下所示:
$socket = new React\Socket\SocketServer('[::1]:8080', [ 'tcp' => [ 'backlog' => 200, 'so_reuseport' => true, 'ipv6_v6only' => true ] ]);
请注意,可用的套接字上下文选项、它们的默认值以及更改这些选项的效果可能会根据您的系统和/或PHP版本而有所不同。传递未知上下文选项不会产生任何效果。除非显式指定,否则
backlog
上下文选项的默认值为511
。
您可以通过简单地添加tls://
URI方案来启动安全的TLS(以前称为SSL)服务器。内部,它将等待明文TCP/IP连接,然后对每个连接执行TLS握手。因此,它需要有效的TLS上下文选项,如果您使用PEM编码的证书文件,其基本形式可能如下所示
$socket = new React\Socket\SocketServer('tls://127.0.0.1:8080', [ 'tls' => [ 'local_cert' => 'server.pem' ] ]);
请注意,证书文件不会在实例化时加载,而是在传入连接初始化其TLS上下文时加载。这意味着任何无效的证书文件路径或内容都只会导致稍后发生一个
error
事件。
如果您的私钥被密码加密,您必须像这样指定它
$socket = new React\Socket\SocketServer('tls://127.0.0.1:8000', [ 'tls' => [ 'local_cert' => 'server.pem', 'passphrase' => 'secret' ] ]);
默认情况下,此服务器支持TLSv1.0+,并排除了对旧版SSLv2/SSLv3的支持。您还可以明确选择您希望与远程端协商的TLS版本
$socket = new React\Socket\SocketServer('tls://127.0.0.1:8000', [ 'tls' => [ 'local_cert' => 'server.pem', 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER ] ]);
请注意,可用的TLS上下文选项、它们的默认值以及更改这些选项的效果可能会根据您的系统和/或PHP版本而有所不同。外部上下文数组允许您同时使用
tcp
(以及可能更多的)上下文选项。传递未知上下文选项不会产生任何效果。如果您不使用tls://
方案,则传递tls
上下文选项不会产生任何效果。
每当客户端连接时,它将发出一个带有实现ConnectionInterface
的连接实例的connection
事件
$socket->on('connection', function (React\Socket\ConnectionInterface $connection) { echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL; $connection->write('hello there!' . PHP_EOL); … });
有关更多详细信息,请参阅ServerInterface
此类接受一个可选的LoopInterface|null $loop
参数,可以用于传递用于此对象的事件循环实例。您可以使用null
值来使用默认循环。除非您确信要显式使用给定的事件循环实例,否则不应提供此值。
请注意,
SocketServer
类是TCP/IP套接字的具体实现。如果您想在高级协议实现中做类型提示,您应使用通用的ServerInterface
高级服务器使用
TcpServer
TcpServer
类实现了ServerInterface
,并负责接受明文TCP/IP连接。
$server = new React\Socket\TcpServer(8080);
如上所述,$uri
参数可以仅包含端口号,在这种情况下,服务器将默认监听localhost地址127.0.0.1
,这意味着它无法从系统外部访问。
为了使用随机端口分配,您可以使用端口号0
。
$server = new React\Socket\TcpServer(0); $address = $server->getAddress();
为了更改套接字监听的宿主地址,您可以通过构造函数提供的第一个参数提供一个IP地址,该地址可以带有可选的tcp://
方案
$server = new React\Socket\TcpServer('192.168.0.1:8080');
如果您想监听IPv6地址,您必须将主机用方括号括起来。
$server = new React\Socket\TcpServer('[::1]:8080');
如果给定的URI无效,不包含端口号,包含其他方案,或者它包含主机名,它将抛出InvalidArgumentException
。
// throws InvalidArgumentException due to missing port $server = new React\Socket\TcpServer('127.0.0.1');
如果给定的URI看起来是有效的,但监听它失败(例如,如果端口已被占用,或者端口号低于1024可能需要root访问等),它将抛出RuntimeException
。
$first = new React\Socket\TcpServer(8080); // throws RuntimeException because port is already in use $second = new React\Socket\TcpServer(8080);
请注意,这些错误条件可能因您的系统或/和配置而异。请参阅异常消息和代码以获取有关实际错误条件的更多详细信息。
此类接受一个可选的LoopInterface|null $loop
参数,可以用于传递用于此对象的事件循环实例。您可以使用null
值来使用默认循环。除非您确信要显式使用给定的事件循环实例,否则不应提供此值。
可选地,您可以指定底层数据流套接字资源的套接字上下文选项,如下所示
$server = new React\Socket\TcpServer('[::1]:8080', null, [ 'backlog' => 200, 'so_reuseport' => true, 'ipv6_v6only' => true ]);
请注意,可用的套接字上下文选项、它们的默认值以及更改这些选项的效果可能会根据您的系统和/或PHP版本而有所不同。传递未知上下文选项不会产生任何效果。除非显式指定,否则
backlog
上下文选项的默认值为511
。
每当客户端连接时,它将发出一个带有实现ConnectionInterface
的连接实例的connection
事件
$server->on('connection', function (React\Socket\ConnectionInterface $connection) { echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL; $connection->write('hello there!' . PHP_EOL); … });
有关更多详细信息,请参阅ServerInterface
SecureServer
SecureServer
类实现了ServerInterface
,并负责提供安全的TLS(以前称为SSL)服务器。
它是通过包装一个TcpServer
实例来实现的,该实例等待明文TCP/IP连接,然后对每个连接执行TLS握手。因此,它需要有效的TLS上下文选项,如果您使用PEM编码的证书文件,其基本形式可能如下所示
$server = new React\Socket\TcpServer(8000); $server = new React\Socket\SecureServer($server, null, [ 'local_cert' => 'server.pem' ]);
请注意,证书文件不会在实例化时加载,而是在传入连接初始化其TLS上下文时加载。这意味着任何无效的证书文件路径或内容都只会导致稍后发生一个
error
事件。
如果您的私钥被密码加密,您必须像这样指定它
$server = new React\Socket\TcpServer(8000); $server = new React\Socket\SecureServer($server, null, [ 'local_cert' => 'server.pem', 'passphrase' => 'secret' ]);
默认情况下,此服务器支持TLSv1.0+,并排除了对旧版SSLv2/SSLv3的支持。您还可以明确选择您希望与远程端协商的TLS版本
$server = new React\Socket\TcpServer(8000); $server = new React\Socket\SecureServer($server, null, [ 'local_cert' => 'server.pem', 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER ]);
请注意,可用的TLS上下文选项、它们的默认值以及更改这些选项的效果可能因您的系统或PHP版本而异。传递未知上下文选项不会产生任何效果。
每当客户端完成TLS握手时,它将发出一个带有实现ConnectionInterface
的连接实例的connection
事件。
$server->on('connection', function (React\Socket\ConnectionInterface $connection) { echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL; $connection->write('hello there!' . PHP_EOL); … });
每当客户端无法成功执行TLS握手时,它将发出一个error
事件,然后关闭底层的TCP/IP连接。
$server->on('error', function (Exception $e) { echo 'Error' . $e->getMessage() . PHP_EOL; });
有关更多详细信息,请参阅ServerInterface
请注意,SecureServer
类是TLS套接字的实际实现。如果您想在高级协议实现中使用类型提示,您应该使用通用的ServerInterface
。
此类接受一个可选的LoopInterface|null $loop
参数,可以用于传递用于此对象的事件循环实例。您可以使用null
值来使用默认循环。除非您确信要显式使用给定的事件循环实例,否则不应提供此值。
高级用法:尽管允许任何
ServerInterface
作为第一个参数,除非您知道自己在做什么,否则您应该将TcpServer
实例作为第一个参数传递。内部,SecureServer
必须设置底层数据流资源所需的TLS上下文选项。这些资源不会通过本包中定义的任何接口暴露,而仅通过内部Connection
类暴露。TcpServer
类保证发出实现ConnectionInterface
的连接,并使用内部Connection
类来暴露这些底层资源。如果您使用自定义的ServerInterface
,并且其connection
事件不符合此要求,则SecureServer
将发出一个error
事件,然后关闭底层连接。
UnixServer
UnixServer
类实现了ServerInterface
,并负责在Unix域套接字(UDS)上接受连接。
$server = new React\Socket\UnixServer('/tmp/server.sock');
与上述内容相同,$uri
参数可以只包含套接字路径或以unix://
方案前缀的套接字路径。
如果给定的URI似乎有效,但监听失败(例如,如果套接字已被使用或文件不可访问等),则将抛出RuntimeException
。
$first = new React\Socket\UnixServer('/tmp/same.sock'); // throws RuntimeException because socket is already in use $second = new React\Socket\UnixServer('/tmp/same.sock');
请注意,这些错误条件可能因您的系统或/和配置而异。特别是,当UDS路径已存在且无法绑定时,Zend PHP只报告“未知错误”。在这种情况下,您可能想检查给定的UDS路径上的
is_file()
,以报告更友好的错误消息。有关实际错误条件的更多详细信息,请参阅异常消息和代码。
此类接受一个可选的LoopInterface|null $loop
参数,可以用于传递用于此对象的事件循环实例。您可以使用null
值来使用默认循环。除非您确信要显式使用给定的事件循环实例,否则不应提供此值。
每当客户端连接时,它将发出一个带有实现ConnectionInterface
的连接实例的connection
事件
$server->on('connection', function (React\Socket\ConnectionInterface $connection) { echo 'New connection' . PHP_EOL; $connection->write('hello there!' . PHP_EOL); … });
有关更多详细信息,请参阅ServerInterface
LimitingServer
LimitingServer
装饰器包装给定的ServerInterface
,并负责限制和跟踪此服务器实例的打开连接。
每当底层服务器发出connection
事件时,它将检查其限制,然后要么
- 通过将其添加到打开连接列表中来跟踪此连接,然后转发
connection
事件 - 在达到其限制时拒绝(关闭)连接,并将转发一个
error
事件。
每当连接关闭时,它将从打开连接列表中删除此连接。
$server = new React\Socket\LimitingServer($server, 100); $server->on('connection', function (React\Socket\ConnectionInterface $connection) { $connection->write('hello there!' . PHP_EOL); … });
有关更多详细信息,请参阅第二个示例。
您必须传递最大打开连接数,以确保服务器在超过此限制时自动拒绝(关闭)连接。在这种情况下,它将发出一个error
事件来通知此情况,并且不会发出任何connection
事件。
$server = new React\Socket\LimitingServer($server, 100); $server->on('connection', function (React\Socket\ConnectionInterface $connection) { $connection->write('hello there!' . PHP_EOL); … });
您可以选择传递一个null
限制,以不对打开连接数进行限制,并继续接受新的连接,直到操作系统资源(例如打开文件句柄)耗尽。如果您不希望应用限制但仍然想使用getConnections()
方法,这可能很有用。
您可以选择配置服务器,在达到连接限制后暂停接受新的连接。在这种情况下,它将暂停底层服务器,并且不再处理任何新的连接,因此也不再关闭任何过量的连接。底层操作系统负责在达到其限制之前保持待处理连接的队列,此时它将开始拒绝进一步的连接。一旦服务器低于连接限制,它将继续从队列中消耗连接,并处理每个连接上的任何未处理数据。这种模式可能对一些设计为等待响应消息的协议(如HTTP)很有用,但对需要即时响应的协议(如交互式聊天中的“欢迎”消息)可能不太有用。
$server = new React\Socket\LimitingServer($server, 100, true); $server->on('connection', function (React\Socket\ConnectionInterface $connection) { $connection->write('hello there!' . PHP_EOL); … });
getConnections()
可以使用 getConnections(): ConnectionInterface[]
方法来返回包含所有当前活动连接的数组。
foreach ($server->getConnection() as $connection) { $connection->write('Hi!'); }
客户端使用
ConnectorInterface
ConnectorInterface
负责提供建立流式连接的接口,例如正常的TCP/IP连接。
这是在此包中定义的主要接口,并且在React的庞大生态系统中被广泛使用。
大多数高级组件(如HTTP、数据库或其他网络服务客户端)接受实现此接口的实例以创建与底层网络服务的TCP/IP连接。这通常通过依赖注入来完成,因此实际上替换此实现与其他接口实现相对简单。
该接口只提供了一种方法
connect()
可以使用 connect(string $uri): PromiseInterface<ConnectionInterface>
方法创建到指定远程地址的流式连接。
它返回一个Promise,在成功时满足具有 ConnectionInterface
的流,如果连接不成功则拒绝并抛出 Exception
。
$connector->connect('google.com:443')->then( function (React\Socket\ConnectionInterface $connection) { // connection successfully established }, function (Exception $error) { // failed to connect due to $error } );
有关更多详细信息,请参阅ConnectionInterface
。
返回的Promise必须以这种方式实现,以便在它仍然是挂起状态时可以取消。取消挂起的Promise必须使用Exception
拒绝其值。它应根据适用性清理任何底层资源和引用。
$promise = $connector->connect($uri); $promise->cancel();
Connector
Connector
类是这个包中的主要类,它实现了 ConnectorInterface
并允许您创建流式连接。
您可以使用此连接器创建任何类型的流式连接,例如明文TCP/IP、安全TLS或本地Unix连接流。
它绑定到主事件循环,可以使用如下方式使用:
$connector = new React\Socket\Connector(); $connector->connect($uri)->then(function (React\Socket\ConnectionInterface $connection) { $connection->write('...'); $connection->end(); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
为了创建明文TCP/IP连接,您只需传递一个主机和端口号组合,如下所示:
$connector->connect('www.google.com:80')->then(function (React\Socket\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 (React\Socket\ConnectionInterface $connection) { $connection->write('...'); $connection->end(); });
为了创建本地Unix域套接字连接,您可以使用 unix://
URI方案,如下所示:
$connector->connect('unix:///tmp/demo.sock')->then(function (React\Socket\ConnectionInterface $connection) { $connection->write('...'); $connection->end(); });
getRemoteAddress()
方法将返回目标Unix域套接字(UDS)路径,该路径是作为给定的connect()
方法的参数提供的,包括unix://
方案,例如unix:///tmp/demo.sock
。大多数情况下,getLocalAddress()
方法将返回一个null
值,因为在此处此值不适用于UDS连接。
在内部,Connector
被实现为对在这个包中实现的较低级别连接器的高级门面。这意味着它也共享它们的所有功能和实现细节。如果你想在高级协议实现中做类型提示,你应该使用通用的ConnectorInterface
。
从v1.4.0
版本开始,Connector
类默认使用Happy Eyeballs算法来自动在给定主机名时通过IPv4或IPv6进行连接。这会同时尝试使用IPv4和IPv6进行连接(优先考虑IPv6),从而避免了用户在使用不完美的IPv6连接或配置时通常遇到的问题。如果你想要恢复到旧的行为,即只进行IPv4查找并只尝试单个IPv4连接,你可以这样设置Connector
$connector = new React\Socket\Connector([ 'happy_eyeballs' => false ]);
同样,你也可以按照以下方式影响默认的DNS行为。默认情况下,Connector
类将尝试检测你的系统DNS设置(如果无法确定系统设置,则使用Google的公共DNS服务器8.8.8.8
作为后备)将所有公共主机名解析为底层IP地址。如果你明确想要使用自定义DNS服务器(例如本地DNS中继或公司范围内的DNS服务器),你可以这样设置Connector
$connector = new React\Socket\Connector([ 'dns' => '127.0.1.1' ]); $connector->connect('localhost:80')->then(function (React\Socket\ConnectionInterface $connection) { $connection->write('...'); $connection->end(); });
如果你根本不想使用DNS解析器,只想连接到IP地址,你也可以这样设置你的Connector
$connector = new React\Socket\Connector([ 'dns' => false ]); $connector->connect('127.0.0.1:80')->then(function (React\Socket\ConnectionInterface $connection) { $connection->write('...'); $connection->end(); });
高级:如果你需要一个自定义的DNS React\Dns\Resolver\ResolverInterface
实例,你也可以这样设置你的Connector
$dnsResolverFactory = new React\Dns\Resolver\Factory(); $resolver = $dnsResolverFactory->createCached('127.0.1.1'); $connector = new React\Socket\Connector([ 'dns' => $resolver ]); $connector->connect('localhost:80')->then(function (React\Socket\ConnectionInterface $connection) { $connection->write('...'); $connection->end(); });
默认情况下,tcp://
和tls://
URI方案将使用尊重你的default_socket_timeout
ini设置的超时值(默认为60秒)。如果你想要一个自定义的超时值,你可以这样简单地传递
$connector = new React\Socket\Connector([ 'timeout' => 10.0 ]);
同样,如果你不想应用任何超时,并让操作系统来处理,你可以传递一个布尔标志,如下所示
$connector = new React\Socket\Connector([ 'timeout' => false ]);
默认情况下,Connector
支持tcp://
、tls://
和unix://
URI方案。如果你想明确禁止其中任何一个,你可以简单地传递布尔标志,如下所示
// only allow secure TLS connections $connector = new React\Socket\Connector([ 'tcp' => false, 'tls' => true, 'unix' => false, )); $connector->connect('tls://google.com:443')->then(function (React\Socket\ConnectionInterface $connection) { $connection->write('...'); $connection->end(); });
tcp://
和tls://
也接受传递给底层连接器的额外上下文选项。如果你想要明确传递额外的上下文选项,你可以简单地传递上下文选项数组的这种方式
// allow insecure TLS connections $connector = new React\Socket\Connector([ 'tcp' => [ 'bindto' => '192.168.0.1:0' ], 'tls' => [ 'verify_peer' => false, 'verify_peer_name' => false ], ]); $connector->connect('tls://localhost:443')->then(function (React\Socket\ConnectionInterface $connection) { $connection->write('...'); $connection->end(); });
默认情况下,此连接器支持TLSv1.0+并排除对旧版SSLv2/SSLv3的支持。你也可以明确选择你想要与远程端协商的TLS版本
$connector = new React\Socket\Connector([ 'tls' => [ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT ] ]);
有关上下文选项的更多详细信息,请参阅PHP关于socket上下文选项和SSL上下文选项的文档。
高级:默认情况下,Connector
支持tcp://
、tls://
和unix://
URI方案。为此,它会自动设置所需的连接器类。如果你想为这些中的任何一个明确传递自定义连接器,你可以简单地传递一个实现ConnectorInterface
的实例,如下所示
$dnsResolverFactory = new React\Dns\Resolver\Factory(); $resolver = $dnsResolverFactory->createCached('127.0.1.1'); $tcp = new React\Socket\HappyEyeBallsConnector(null, new React\Socket\TcpConnector(), $resolver); $tls = new React\Socket\SecureConnector($tcp); $unix = new React\Socket\UnixConnector(); $connector = new React\Socket\Connector([ 'tcp' => $tcp, 'tls' => $tls, 'unix' => $unix, 'dns' => false, 'timeout' => false, ]); $connector->connect('google.com:80')->then(function (React\Socket\ConnectionInterface $connection) { $connection->write('...'); $connection->end(); });
在内部,除非像上面的示例那样禁用DNS,否则
tcp://
连接器始终会被DNS解析器包装。在这种情况下,tcp://
连接器接收实际的域名而不是仅解析的IP地址,因此负责执行查找。内部,自动创建的tls://
连接器始终包装底层的tcp://
连接器,在启用安全的TLS模式之前建立底层的明文TCP/IP连接。如果您只想为安全的TLS连接使用自定义的底层tcp://
连接器,则可以像上面那样显式传递一个tls://
连接器。内部,除非像上面的示例那样禁用超时,否则tcp://
和tls://
连接器始终会被TimeoutConnector
包装。
此类接受一个可选的LoopInterface|null $loop
参数,可以用于传递用于此对象的事件循环实例。您可以使用null
值来使用默认循环。除非您确信要显式使用给定的事件循环实例,否则不应提供此值。
高级客户端使用
TcpConnector
TcpConnector
类实现了ConnectorInterface
接口,并允许您创建到任何IP-端口号组合的明文TCP/IP连接。
$tcpConnector = new React\Socket\TcpConnector(); $tcpConnector->connect('127.0.0.1:80')->then(function (React\Socket\ConnectionInterface $connection) { $connection->write('...'); $connection->end(); });
请参阅示例。
可以通过取消其挂起的承诺来取消挂起的连接尝试,如下所示
$promise = $tcpConnector->connect('127.0.0.1:80'); $promise->cancel();
在挂起的承诺上调用cancel()
将关闭底层的套接字资源,从而取消挂起的TCP/IP连接,并拒绝产生的承诺。
此类接受一个可选的LoopInterface|null $loop
参数,可以用于传递用于此对象的事件循环实例。您可以使用null
值来使用默认循环。除非您确信要显式使用给定的事件循环实例,否则不应提供此值。
您可以选择像这样将额外的套接字上下文选项传递给构造函数
$tcpConnector = new React\Socket\TcpConnector(null, [ 'bindto' => '192.168.0.1:0' ]);
请注意,此类仅允许您连接到IP-端口号组合。如果给定的URI无效,不包含有效的IP地址和端口号或包含任何其他方案,它将使用InvalidArgumentException
拒绝。
如果给定的URI看起来是有效的,但连接失败(例如,如果远程主机拒绝连接等),它将使用RuntimeException
拒绝。
如果您想连接到主机名-端口号组合,请参阅以下章节。
高级用法:内部,
TcpConnector
为每个流资源分配一个空的上下文资源。如果目标URI包含一个hostname
查询参数,其值将用于设置TLS对端名称。这被SecureConnector
和DnsConnector
用于验证对端名称,也可以用于您想自定义TLS对端名称的情况。
HappyEyeBallsConnector
HappyEyeBallsConnector
类实现了ConnectorInterface
接口,并允许您创建到任何主机名-端口号组合的明文TCP/IP连接。内部,它实现了来自RFC6555
和RFC8305
的happy eyeballs算法来支持IPv6和IPv4主机名。
它是通过装饰一个给定的TcpConnector
实例来实现的,这样它首先通过DNS(如果适用)查找给定的域名,然后建立到解析的目标IP地址的底层TCP/IP连接。
请确保像这样设置您的DNS解析器和底层TCP连接器
$dnsResolverFactory = new React\Dns\Resolver\Factory(); $dns = $dnsResolverFactory->createCached('8.8.8.8'); $dnsConnector = new React\Socket\HappyEyeBallsConnector(null, $tcpConnector, $dns); $dnsConnector->connect('www.google.com:80')->then(function (React\Socket\ConnectionInterface $connection) { $connection->write('...'); $connection->end(); });
请参阅示例。
可以通过取消其挂起的承诺来取消挂起的连接尝试,如下所示
$promise = $dnsConnector->connect('www.google.com:80'); $promise->cancel();
在挂起的承诺上调用cancel()
将取消底层的DNS查找和/或底层TCP/IP连接并拒绝产生的承诺。
此类接受一个可选的LoopInterface|null $loop
参数,可以用于传递用于此对象的事件循环实例。您可以使用null
值来使用默认循环。除非您确信要显式使用给定的事件循环实例,否则不应提供此值。
高级用法:内部,
HappyEyeBallsConnector
依赖于一个Resolver
来查找给定主机名的IP地址。然后,它将目标URI中的主机名替换为该IP地址,并附加一个hostname
查询参数,并将此更新后的URI传递给底层连接器。Happy Eye Balls算法描述了查找给定主机名的IPv6和IPv4地址,因此此连接器发送两个DNS查找以获取A和AAAA记录。然后,它使用所有IP地址(包括v6和v4)并尝试以50ms的间隔连接到所有这些地址。在IPv6和IPv4地址之间交替。当建立连接时,取消所有其他DNS查找和连接尝试。
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'); $dnsConnector = new React\Socket\DnsConnector($tcpConnector, $dns); $dnsConnector->connect('www.google.com:80')->then(function (React\Socket\ConnectionInterface $connection) { $connection->write('...'); $connection->end(); });
请参阅示例。
可以通过取消其挂起的承诺来取消挂起的连接尝试,如下所示
$promise = $dnsConnector->connect('www.google.com:80'); $promise->cancel();
在挂起的Promise上调用cancel()
将取消底层DNS查找和/或底层TCP/IP连接,并拒绝生成的Promise。
高级用法:内部,
DnsConnector
依赖于React\Dns\Resolver\ResolverInterface
来查找给定主机名的IP地址。然后,它将目标URI中的主机名替换为该IP地址,并附加一个hostname
查询参数,并将此更新后的URI传递给底层连接器。底层连接器因此负责连接到目标IP地址,而此查询参数可以用于检查原始主机名,并由TcpConnector
用于设置TLS对等名称。如果显式给出hostname
,则不会修改此查询参数,这在您希望自定义TLS对等名称时可能很有用。
SecureConnector
SecureConnector
类实现了ConnectorInterface
,并允许您创建任何主机名-端口号组合的安全TLS(以前称为SSL)连接。
它是通过装饰给定的DnsConnector
实例来实现的,这样它首先创建一个明文TCP/IP连接,然后在流上启用TLS加密。
$secureConnector = new React\Socket\SecureConnector($dnsConnector); $secureConnector->connect('www.google.com:443')->then(function (React\Socket\ConnectionInterface $connection) { $connection->write("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n"); ... });
请参阅示例。
可以通过取消其挂起的承诺来取消挂起的连接尝试,如下所示
$promise = $secureConnector->connect('www.google.com:443'); $promise->cancel();
在挂起的Promise上调用cancel()
将取消底层TCP/IP连接和/或SSL/TLS协商,并拒绝生成的Promise。
此类接受一个可选的LoopInterface|null $loop
参数,可以用于传递用于此对象的事件循环实例。您可以使用null
值来使用默认循环。除非您确信要显式使用给定的事件循环实例,否则不应提供此值。
您可以选择像这样将额外的SSL上下文选项传递给构造函数
$secureConnector = new React\Socket\SecureConnector($dnsConnector, null, [ 'verify_peer' => false, 'verify_peer_name' => false ]);
默认情况下,此连接器支持TLSv1.0+并排除对旧版SSLv2/SSLv3的支持。你也可以明确选择你想要与远程端协商的TLS版本
$secureConnector = new React\Socket\SecureConnector($dnsConnector, null, [ 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT ]);
高级用法:内部,
SecureConnector
依赖于在底层流资源上设置所需的上下文选项。因此,它应在连接器堆栈中的某个位置与TcpConnector
一起使用,以便为每个流资源分配一个空的上下文资源并验证对等名称。否则,不这样做可能会导致TLS对等名称不匹配错误或一些难以追踪的竞争条件,因为所有流资源都将使用单个、共享的默认上下文资源。
TimeoutConnector
TimeoutConnector
类实现了ConnectorInterface
,并允许您向任何现有连接器实例添加超时处理。
它是通过装饰任何给定的ConnectorInterface
实例并启动一个计时器来实现的,该计时器将自动拒绝和取消任何底层连接尝试,如果它花费的时间过长。
$timeoutConnector = new React\Socket\TimeoutConnector($connector, 3.0); $timeoutConnector->connect('google.com:80')->then(function (React\Socket\ConnectionInterface $connection) { // connection succeeded within 3.0 seconds });
请参阅示例中的任何示例。
此类接受一个可选的LoopInterface|null $loop
参数,可以用于传递用于此对象的事件循环实例。您可以使用null
值来使用默认循环。除非您确信要显式使用给定的事件循环实例,否则不应提供此值。
可以通过取消其挂起的承诺来取消挂起的连接尝试,如下所示
$promise = $timeoutConnector->connect('google.com:80'); $promise->cancel();
在挂起的Promise上调用cancel()
将取消底层连接尝试,取消计时器并拒绝生成的Promise。
UnixConnector
UnixConnector
类实现了ConnectorInterface
,并允许您连接到Unix域套接字(UDS)路径,如下所示
$connector = new React\Socket\UnixConnector(); $connector->connect('/tmp/demo.sock')->then(function (React\Socket\ConnectionInterface $connection) { $connection->write("HELLO\n"); });
连接到Unix域套接字是一个原子操作,即其承诺将立即解决(要么解决要么拒绝)。因此,在结果承诺上调用cancel()
没有任何效果。
getRemoteAddress()
方法将返回传递给connect()
方法的目标Unix域套接字(UDS)路径,并在前面加上unix://
方案,例如unix:///tmp/demo.sock
。大多数情况下,getLocalAddress()
方法将返回一个null
值,因为这个值不适用于这里的UDS连接。
此类接受一个可选的LoopInterface|null $loop
参数,可以用于传递用于此对象的事件循环实例。您可以使用null
值来使用默认循环。除非您确信要显式使用给定的事件循环实例,否则不应提供此值。
FixedUriConnector
FixedUriConnector
类实现了ConnectorInterface
并装饰现有的连接器,使其始终使用固定的、预配置的URI。
这可以用于不支持某些URI的消费者,例如当你想明确连接到Unix域套接字(UDS)路径而不是连接到由高级API假设的默认地址时。
$connector = new React\Socket\FixedUriConnector( 'unix:///var/run/docker.sock', new React\Socket\UnixConnector() ); // destination will be ignored, actually connects to Unix domain socket $promise = $connector->connect('localhost:80');
安装
安装此库的推荐方法是通过Composer。 你是Composer新手吗?
一旦发布,此项目将遵循SemVer。目前,这将安装最新的开发版本
composer require react/socket:^3@dev
有关版本升级的详细信息,请参阅CHANGELOG。
此项目旨在在任何平台上运行,因此不需要任何PHP扩展,并支持在PHP 7.1到当前PHP 8+上运行。强烈建议为此项目使用最新支持的PHP版本。
旧版PHP < 7.3.3(以及PHP < 7.2.15)存在一个bug,其中feof()可能会在碎片化的TLS记录上阻塞并使用100%的CPU使用率。我们通过一次消耗完整的接收缓冲区来尝试解决这个问题,以避免在TLS缓冲区中的旧数据。这已知的解决方法可以解决表现良好的对等方的CPU使用率过高,但这可能导致高吞吐量场景中的非常大的数据块。由于网络I/O缓冲区或受影响版本的恶意对等方,buggy行为仍然可能被触发,强烈建议升级。
旧版PHP < 7.1.4存在一个bug,在一次性通过TLS流写入大量数据时。我们通过仅对旧版PHP版本限制写入块大小为8192字节来尝试解决这个问题。这只是一种解决方案,并在受影响的版本上产生可感知的性能损失。
测试
要运行测试套件,您首先需要克隆此存储库,然后通过Composer安装所有依赖项通过Composer
composer install
要运行测试套件,请转到项目根目录并运行
vendor/bin/phpunit
测试套件还包含一些功能集成测试,这些测试依赖于稳定的互联网连接。如果您不想运行这些测试,可以像这样简单地跳过
vendor/bin/phpunit --exclude-group internet
许可证
MIT,请参阅LICENSE文件。