brunonatali/socket

为 ReactPHP 提供异步、流式纯文本 TCP/IP 和安全 TLS 套接字服务器和客户端连接

dev-master 2019-09-25 03:21 UTC

This package is auto-updated.

Last update: 2024-09-25 15:16:40 UTC


README

  • 从 'ReactPHP' 原始复制,对一些测试函数和特定使用实现进行了更改。
  • 删除了 'tests' 和 'examples' 原始文件夹。

Build Status

ReactPHP 提供 Async、流式纯文本 TCP/IP 和安全 TLS 套接字服务器和客户端连接。

套接字库提供了基于 EventLoopStream 组件的套接字层服务器和客户端的可重用接口。其服务器组件允许您构建网络服务器,接受来自网络客户端(如 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:8080tcp://[::1]:80tls://127.0.0.1:443unix://example.sockunix:///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:8080tcp://[::1]:80tls://127.0.0.1:443unix://example.sockunix:///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:8080tcp://[::1]:80tls://127.0.0.1:443unix://example.sockunix:///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.sockgetLocalAddress()方法可能返回一个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对端名称。这被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\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.sockgetLocalAddress() 方法很可能会返回一个 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

许可证

麻省理工学院,参见许可文件