brunonatali / socket
为 ReactPHP 提供异步、流式纯文本 TCP/IP 和安全 TLS 套接字服务器和客户端连接
Requires
- php: >=5.3.0
- brunonatali/event-loop: dev-master
- brunonatali/stream: dev-master
- evenement/evenement: ^3.0 || ^2.0 || ^1.0
- react/dns: ^0.4.13
- react/promise: ^2.6.0 || ^1.2.1
- react/promise-timer: ^1.4.0
Requires (Dev)
- clue/block-react: ^1.2
- phpunit/phpunit: ^6.4 || ^5.7 || ^4.8.35
This package is auto-updated.
Last update: 2024-09-25 15:16:40 UTC
README
- 从 'ReactPHP' 原始复制,对一些测试函数和特定使用实现进行了更改。
- 删除了 'tests' 和 'examples' 原始文件夹。
为 ReactPHP 提供 Async、流式纯文本 TCP/IP 和安全 TLS 套接字服务器和客户端连接。
套接字库提供了基于 EventLoop
和 Stream
组件的套接字层服务器和客户端的可重用接口。其服务器组件允许您构建网络服务器,接受来自网络客户端(如 HTTP 服务器)的传入连接。其客户端组件允许您构建网络客户端,建立到网络服务器(如 HTTP 或数据库客户端)的传出连接。此库提供了所有这些的异步、流式方法,因此您可以在不阻塞的情况下处理多个并发连接。
目录
快速入门示例
以下是一个服务器,如果发送任何内容,则会关闭连接
$loop = React\EventLoop\Factory::create(); $socket = new React\Socket\Server('127.0.0.1:8080', $loop); $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(); }); }); $loop->run();
另请参阅 示例。
以下是一个客户端,它会输出上述服务器的输出,然后尝试发送一个字符串
$loop = React\EventLoop\Factory::create(); $connector = new React\Socket\Connector($loop); $connector->connect('127.0.0.1:8080')->then(function (React\Socket\ConnectionInterface $connection) use ($loop) { $connection->pipe(new React\Stream\WritableResourceStream(STDOUT, $loop)); $connection->write("Hello World!\n"); }); $loop->run();
连接使用
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
事件。
$server->on('connection', function (React\Socket\ConnectionInterface $connection) { echo 'new connection' . PHP_EOL; });
有关处理传入连接的更多详细信息,请参阅ConnectionInterface
。
错误事件
每当从客户端接受新的连接时出现错误,都会发出error
事件。
$server->on('error', function (Exception $e) { echo 'error: ' . $e->getMessage() . PHP_EOL; });
请注意,这不是一个致命的错误事件,即服务器在发生此事件后仍然会监听新的连接。
getAddress()
getAddress(): ?string
方法可用于返回服务器当前正在监听的完整地址(URI)。
$address = $server->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 = $server->getAddress(); $port = parse_url($address, PHP_URL_PORT); echo 'Server listening on port ' . $port . PHP_EOL;
pause()
pause(): void
方法可用于暂停接受新的传入连接。
从EventLoop中移除套接字资源,从而停止接受新的连接。请注意,监听套接字保持活动状态,不会被关闭。
这意味着新的传入连接将在操作系统的队列中保持挂起状态,直到可配置的队列被填满。一旦队列被填满,操作系统可能会拒绝进一步的传入连接,直到队列再次被清空,即重新开始接受新的连接。
一旦服务器被暂停,不应再发出任何connection
事件。
$server->pause(); $server->on('connection', assertShouldNeverCalled());
此方法仅为建议性,尽管通常不推荐,服务器仍然可以继续发出connection
事件。
除非另有说明,成功启动的服务器不应处于暂停状态。
可以通过再次调用resume()
来继续处理事件。
请注意,这两种方法可以多次调用,特别是多次调用pause()
不应有任何效果。同样,在close()
之后调用此方法也是无操作的。
resume()
resume(): void
方法可用于重新开始接受新的传入连接。
在之前的pause()
之后,将套接字资源重新附加到EventLoop。
$server->pause(); $loop->addTimer(1.0, function () use ($server) { $server->resume(); });
请注意,这两种方法可以多次调用,特别是如果没有先调用pause()
而调用resume()
不应有任何效果。同样,在close()
之后调用此方法也是无操作的。
close()
close(): void
方法可用于关闭此监听套接字。
这将停止在此套接字上监听新的传入连接。
echo 'Shutting down server socket' . PHP_EOL; $server->close();
在同一个实例上多次调用此方法是无操作的。
服务器
Server
类是本包中的主类,实现了ServerInterface
,允许您接受传入的流连接,如明文TCP/IP或安全TLS连接流。也可以在Unix域套接字上接受连接。
$server = new React\Socket\Server(8080, $loop);
如上所述,$uri
参数可以只包含端口号,在这种情况下,服务器将默认监听本地主机地址127.0.0.1
,这意味着它将无法从系统外部访问。
为了使用随机端口分配,可以使用端口号0
。
$server = new React\Socket\Server(0, $loop); $address = $server->getAddress();
为了更改套接字监听的宿主机,可以通过构造函数提供的第一个参数提供一个IP地址,该参数可以由tcp://
方案前缀。
$server = new React\Socket\Server('192.168.0.1:8080', $loop);
如果您想监听IPv6地址,必须将宿主机用方括号括起来。
$server = new React\Socket\Server('[::1]:8080', $loop);
为了监听Unix域套接字(UDS)路径,必须用unix://
方案前缀URI。
$server = new React\Socket\Server('unix:///tmp/server.sock', $loop);
如果给定的URI无效,不包含端口号,包含其他方案或包含宿主机名,将抛出InvalidArgumentException
异常。
// throws InvalidArgumentException due to missing port $server = new React\Socket\Server('127.0.0.1', $loop);
如果给定的URI看起来是有效的,但监听它失败(例如,如果端口已被使用或端口低于1024可能需要root权限等),将抛出RuntimeException
异常。
$first = new React\Socket\Server(8080, $loop); // throws RuntimeException because port is already in use $second = new React\Socket\Server(8080, $loop);
请注意,这些错误条件可能因您的系统和/或配置而异。有关实际错误条件的更多详细信息,请参阅异常消息和代码。
可选地,您可以指定如这样底层的流套接字资源的TCP套接字上下文选项
$server = new React\Socket\Server('[::1]:8080', $loop, array( 'tcp' => array( 'backlog' => 200, 'so_reuseport' => true, 'ipv6_v6only' => true ) ));
请注意,可用的套接字上下文选项、它们的默认值和更改这些选项的效果可能因您的系统和/或PHP版本而异。传递未知上下文选项不会产生影响。为了向后兼容,您也可以不将TCP套接字上下文选项包装在另一个数组中,直接在
tcp
键下作为简单数组传递。
您可以通过简单地在URI方案前添加tls://
来启动一个安全的TLS(以前称为SSL)服务器。内部,它将等待明文TCP/IP连接,然后对每个连接执行TLS握手。因此,它需要有效的TLS上下文选项,如果您使用PEM编码的证书文件,其最基本的形式可能看起来像这样:
$server = new React\Socket\Server('tls://127.0.0.1:8080', $loop, array( 'tls' => array( 'local_cert' => 'server.pem' ) ));
请注意,证书文件不会在实例化时加载,而是在传入连接初始化其TLS上下文时加载。这意味着任何无效的证书文件路径或内容都只会导致稍后发生
error
事件。
如果您的私钥使用密码加密,您必须像这样指定它:
$server = new React\Socket\Server('tls://127.0.0.1:8000', $loop, array( 'tls' => array( 'local_cert' => 'server.pem', 'passphrase' => 'secret' ) ));
默认情况下,此服务器支持TLSv1.0+,并排除对遗留SSLv2/SSLv3的支持。从PHP 5.6+开始,您还可以显式选择与远程端协商的TLS版本。
$server = new React\Socket\Server('tls://127.0.0.1:8000', $loop, array( 'tls' => array( 'local_cert' => 'server.pem', 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER ) ));
请注意,可用的TLS上下文选项、它们的默认值以及更改这些值的影响可能因您的系统/PHP版本而异。外部上下文数组允许您同时使用
tcp
(以及可能更多的)上下文选项。传递未知上下文选项没有效果。如果您不使用tls://
方案,则传递tls
上下文选项没有效果。
每当客户端连接时,它都会发出一个包含实现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
。
请注意,
Server
类是TCP/IP套接字的具体实现。如果您想在您的更高级协议实现中进行类型提示,您应该使用通用的ServerInterface
。
高级服务器使用
TcpServer
TcpServer
类实现了ServerInterface
,并负责接受明文TCP/IP连接。
$server = new React\Socket\TcpServer(8080, $loop);
如上所述,$uri
参数可以只包含端口号,在这种情况下,服务器将默认监听本地主机地址127.0.0.1
,这意味着它将无法从系统外部访问。
为了使用随机端口分配,可以使用端口号0
。
$server = new React\Socket\TcpServer(0, $loop); $address = $server->getAddress();
为了更改套接字监听的宿主机,可以通过构造函数提供的第一个参数提供一个IP地址,该参数可以由tcp://
方案前缀。
$server = new React\Socket\TcpServer('192.168.0.1:8080', $loop);
如果您想监听IPv6地址,必须将宿主机用方括号括起来。
$server = new React\Socket\TcpServer('[::1]:8080', $loop);
如果给定的URI无效,不包含端口号,包含其他方案或包含宿主机名,将抛出InvalidArgumentException
异常。
// throws InvalidArgumentException due to missing port $server = new React\Socket\TcpServer('127.0.0.1', $loop);
如果给定的URI看起来是有效的,但监听它失败(例如,如果端口已被使用或端口低于1024可能需要root权限等),将抛出RuntimeException
异常。
$first = new React\Socket\TcpServer(8080, $loop); // throws RuntimeException because port is already in use $second = new React\Socket\TcpServer(8080, $loop);
请注意,这些错误条件可能因您的系统和/或配置而异。有关实际错误条件的更多详细信息,请参阅异常消息和代码。
您可以选择指定套接字上下文选项,如下所示:
$server = new React\Socket\TcpServer('[::1]:8080', $loop, array( 'backlog' => 200, 'so_reuseport' => true, 'ipv6_v6only' => true ));
请注意,可用的套接字上下文选项、它们的默认值以及更改这些值的影响可能因您的系统/PHP版本而异。传递未知上下文选项没有效果。
每当客户端连接时,它都会发出一个包含实现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, $loop); $server = new React\Socket\SecureServer($server, $loop, array( 'local_cert' => 'server.pem' ));
请注意,证书文件不会在实例化时加载,而是在传入连接初始化其TLS上下文时加载。这意味着任何无效的证书文件路径或内容都只会导致稍后发生
error
事件。
如果您的私钥使用密码加密,您必须像这样指定它:
$server = new React\Socket\TcpServer(8000, $loop); $server = new React\Socket\SecureServer($server, $loop, array( 'local_cert' => 'server.pem', 'passphrase' => 'secret' ));
默认情况下,此服务器支持TLSv1.0+,并排除对遗留SSLv2/SSLv3的支持。从PHP 5.6+开始,您还可以显式选择与远程端协商的TLS版本。
$server = new React\Socket\TcpServer(8000, $loop); $server = new React\Socket\SecureServer($server, $loop, array( '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
。
高级用法:尽管允许任何
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', $loop);
如上所述,$uri
参数可以仅包含套接字路径,或者以 unix://
方案前缀的套接字路径。
如果给定的 URI 似乎有效,但监听它失败(例如,如果套接字已被使用或文件不可访问等),它将抛出 RuntimeException
。
$first = new React\Socket\UnixServer('/tmp/same.sock', $loop); // throws RuntimeException because socket is already in use $second = new React\Socket\UnixServer('/tmp/same.sock', $loop);
请注意,这些错误条件可能因您的系统或/和配置而异。特别是,当 UDS 路径已存在且无法绑定时,Zend PHP 仅报告 "未知错误"。在这种情况下,您可能想检查给定 UDS 路径上的
is_file()
以报告更友好的错误消息。有关实际错误条件的更多详细信息,请参阅异常消息和代码。
每当客户端连接时,它都会发出一个包含实现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
方法创建到给定远程地址的流式连接。
它返回一个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连接流。
它绑定到主事件循环,可以像这样使用
$loop = React\EventLoop\Factory::create(); $connector = new React\Socket\Connector($loop); $connector->connect($uri)->then(function (React\Socket\ConnectionInterface $connection) { $connection->write('...'); $connection->end(); }); $loop->run();
为了创建纯文本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()
方法将返回传递给connect()
方法的Unix域套接字(UDS)路径,包括unix://
方案,例如unix:///tmp/demo.sock
。getLocalAddress()
方法可能返回一个null
值,因为此值在这里不适用于UDS连接。
在底层,Connector
被实现为对在此包中实现的较低级别连接器的 高级门面。这意味着它也共享它们的所有特性和实现细节。如果您想在高级协议实现中添加类型提示,您应该使用通用的 ConnectorInterface
。
Connector
类将尝试检测您的系统 DNS 设置(如果无法确定系统设置,则默认使用 Google 的公共 DNS 服务器 8.8.8.8
)以将所有公共主机名解析为底层 IP 地址。如果您明确想要使用自定义 DNS 服务器(例如本地 DNS 代理或公司范围内的 DNS 服务器),您可以按以下方式设置 Connector
$connector = new React\Socket\Connector($loop, array( '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($loop, array( 'dns' => false )); $connector->connect('127.0.0.1:80')->then(function (React\Socket\ConnectionInterface $connection) { $connection->write('...'); $connection->end(); });
高级:如果您需要自定义 Resolver
实例,您也可以按以下方式设置您的 Connector
$dnsResolverFactory = new React\Dns\Resolver\Factory(); $resolver = $dnsResolverFactory->createCached('127.0.1.1', $loop); $connector = new React\Socket\Connector($loop, array( '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($loop, array( 'timeout' => 10.0 ));
类似地,如果您根本不想应用超时并让操作系统处理,您可以传递一个布尔标志如下
$connector = new React\Socket\Connector($loop, array( 'timeout' => false ));
默认情况下,Connector
支持以下 URI 方案:tcp://
、tls://
和 unix://
。如果您想明确禁止这些方案中的任何一个,可以简单传递布尔标志如下
// only allow secure TLS connections $connector = new React\Socket\Connector($loop, array( '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($loop, array( 'tcp' => array( 'bindto' => '192.168.0.1:0' ), 'tls' => array( 'verify_peer' => false, 'verify_peer_name' => false ), )); $connector->connect('tls://:443')->then(function (React\Socket\ConnectionInterface $connection) { $connection->write('...'); $connection->end(); });
默认情况下,此连接器支持 TLSv1.0+ 并排除对旧版 SSLv2/SSLv3 的支持。从 PHP 5.6+ 开始,您还可以明确选择与远程端协商的 TLS 版本
$connector = new React\Socket\Connector($loop, array( 'tls' => array( 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT ) ));
有关上下文选项的更多详细信息,请参阅 PHP 关于 socket 上下文选项 和 SSL 上下文选项 的文档。
高级:默认情况下,Connector
支持以下 URI 方案:tcp://
、tls://
和 unix://
。为此,它会自动设置所需的连接器类。如果您想为这些方案中的任何一个明确传递自定义连接器,可以简单传递实现 ConnectorInterface
的实例如下
$dnsResolverFactory = new React\Dns\Resolver\Factory(); $resolver = $dnsResolverFactory->createCached('127.0.1.1', $loop); $tcp = new React\Socket\DnsConnector(new React\Socket\TcpConnector($loop), $resolver); $tls = new React\Socket\SecureConnector($tcp, $loop); $unix = new React\Socket\UnixConnector($loop); $connector = new React\Socket\Connector($loop, array( '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(); });
内部,
tcp://
连接器将始终由 DNS 解析器包装,除非您像上面的例子那样禁用 DNS。在这种情况下,tcp://
连接器接收实际的域名而不是仅解析的 IP 地址,因此负责执行查找。内部创建的自动tls://
连接器将始终包装底层的tcp://
连接器,在启用安全的 TLS 模式之前建立底层的明文 TCP/IP 连接。如果您只想为安全的 TLS 连接使用自定义底层的tcp://
连接器,您可以明确传递上面的tls://
连接器。内部,除非您像上面的例子那样禁用超时,否则tcp://
和tls://
连接器始终由TimeoutConnector
包装。
高级客户端使用
TcpConnector
TcpConnector
类实现了 ConnectorInterface
并允许您创建到任何 IP-端口号组合的明文 TCP/IP 连接
$tcpConnector = new React\Socket\TcpConnector($loop); $tcpConnector->connect('127.0.0.1:80')->then(function (React\Socket\ConnectionInterface $connection) { $connection->write('...'); $connection->end(); }); $loop->run();
另请参阅 示例。
可以通过取消其挂起的承诺来取消挂起的连接尝试
$promise = $tcpConnector->connect('127.0.0.1:80'); $promise->cancel();
在挂起的承诺上调用 cancel()
将关闭底层套接字资源,从而取消挂起的 TCP/IP 连接,并拒绝产生的承诺。
您可以选择将额外的 socket 上下文选项 传递给构造函数如下
$tcpConnector = new React\Socket\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\Socket\DnsConnector($tcpConnector, $dns); $dnsConnector->connect('www.google.com:80')->then(function (React\Socket\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\Socket\SecureConnector($dnsConnector, $loop); $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"); ... }); $loop->run();
另请参阅 示例。
可以通过取消其挂起的承诺来取消挂起的连接尝试
$promise = $secureConnector->connect('www.google.com:443'); $promise->cancel();
在待定的承诺上调用cancel()
将取消底层的TCP/IP连接和/或SSL/TLS协商,并拒绝生成的承诺。
您可以选择以这种方式将额外的SSL上下文选项传递给构造函数
$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop, array( 'verify_peer' => false, 'verify_peer_name' => false ));
默认情况下,此连接器支持 TLSv1.0+ 并排除对旧版 SSLv2/SSLv3 的支持。从 PHP 5.6+ 开始,您还可以明确选择与远程端协商的 TLS 版本
$secureConnector = new React\Socket\SecureConnector($dnsConnector, $loop, array( 'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT ));
高级使用:在内部,
SecureConnector
依赖于在底层的流资源上设置所需的上下文选项。因此,它应该在连接器堆栈中的某个位置与TcpConnector
一起使用,以便它可以分配每个流资源的一个空的上下文资源并验证对端名称。未能这样做可能会导致TLS对端名称不匹配错误或一些难以追踪的竞争条件,因为否则所有流资源都将使用单个共享的默认上下文资源。
TimeoutConnector
TimeoutConnector
类实现了ConnectorInterface
,并允许您向任何现有的连接器实例添加超时处理。
它通过装饰任何给定的 ConnectorInterface
实例并启动一个计时器来实现,如果连接尝试耗时过长,则会自动拒绝和终止任何底层连接尝试。
$timeoutConnector = new React\Socket\TimeoutConnector($connector, 3.0, $loop); $timeoutConnector->connect('google.com:80')->then(function (React\Socket\ConnectionInterface $connection) { // connection succeeded within 3.0 seconds });
请参阅示例。
可以通过取消其挂起的承诺来取消挂起的连接尝试
$promise = $timeoutConnector->connect('google.com:80'); $promise->cancel();
在挂起的承诺上调用 cancel()
将取消底层连接尝试,终止计时器并拒绝结果承诺。
UnixConnector
UnixConnector
类实现了 ConnectorInterface
,并允许您以这种方式连接到 Unix 域套接字 (UDS) 路径:
$connector = new React\Socket\UnixConnector($loop); $connector->connect('/tmp/demo.sock')->then(function (React\Socket\ConnectionInterface $connection) { $connection->write("HELLO\n"); }); $loop->run();
连接到 Unix 域套接字是一个原子操作,即它的承诺将立即解决(解决或拒绝)。因此,在结果承诺上调用 cancel()
没有任何效果。
getRemoteAddress()
方法将返回传递给connect()
方法的目标 Unix 域套接字 (UDS) 路径,并在前面加上unix://
方案,例如unix:///tmp/demo.sock
。getLocalAddress()
方法很可能会返回一个null
值,因为在这里此值不适用于 UDS 连接。
FixedUriConnector
FixedUriConnector
类实现了 ConnectorInterface
并装饰现有的连接器,始终使用固定、预配置的 URI。
对于不支持某些 URI 的消费者,这可能很有用,例如,当您想显式连接到 Unix 域套接字 (UDS) 路径而不是连接到高级 API 假定的默认地址时。
$connector = new React\Socket\FixedUriConnector( 'unix:///var/run/docker.sock', new React\Socket\UnixConnector($loop) ); // destination will be ignored, actually connects to Unix domain socket $promise = $connector->connect('localhost:80');
安装
安装此库的推荐方法是通过 Composer。 对 Composer 不熟悉?
此项目遵循SemVer。这将安装最新支持的版本。
$ composer require react/socket:^1.2
有关版本升级的详细信息,请参阅变更日志。
此项目旨在在任意平台上运行,因此不需要任何 PHP 扩展,并支持在从 PHP 5.3 到当前 PHP 7+ 和 HHVM 上运行。强烈建议为此项目使用 PHP 7+,部分原因是其巨大的性能改进,部分原因是旧版本的 PHP 需要几个以下所述的解决方案。
从 PHP 5.6 开始,安全 TLS 连接得到了一些重大升级,默认值现在更加安全,而旧版本则需要显式上下文选项。此库不对这些上下文选项负责,因此需要使用此库的消费者根据上述说明设置适当的上下文选项。
PHP < 7.1.4(以及 PHP < 7.0.18)在通过 TLS 流一次写入大量数据时存在一个错误。我们通过将旧版本 PHP 的写入块大小限制为 8192 字节来尝试解决这个问题。这只是一个解决方案,并且在受影响的版本上会带来可感知的性能惩罚。
此项目还支持在 HHVM 上运行。请注意,非常旧的 HHVM < 3.8 不支持安全 TLS 连接,因为它缺少所需的 stream_socket_enable_crypto()
函数。因此,在受影响的版本上尝试创建安全 TLS 连接将返回一个被拒绝的承诺。此问题也包含在我们的测试套件中,该测试套件将在受影响的版本上跳过相关测试。
测试
要运行测试套件,您首先需要克隆此存储库,然后通过 Composer 安装所有依赖项通过 Composer
$ composer install
要运行测试套件,请转到项目根目录并运行:
$ php vendor/bin/phpunit
测试套件还包含一些功能集成测试,它们依赖于稳定的互联网连接。如果您不想运行这些测试,可以简单地按以下方式跳过它们:
$ php vendor/bin/phpunit --exclude-group internet
许可证
麻省理工学院,参见许可文件。