raimx/socket

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

维护者

详细信息

github.com/RaiMX/socket

源代码

v1.3.0 2019-07-10 10:11 UTC

README

Build Status

ReactPHP 提供异步、流式纯文本 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 负责提供一个接口,用于接受传入的流式连接,例如普通的 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:443 unix://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;

暂停()

暂停(): void 方法可用于暂停接受新的传入连接。

将套接字资源从EventLoop中移除,从而停止接受新的连接。请注意,监听套接字保持活跃状态,不会被关闭。

这意味着新的传入连接将保留在操作系统的backlog中,直到其可配置的backlog填满。一旦backlog填满,操作系统可能会拒绝进一步的传入连接,直到backlog通过重新开始接受新连接而被清空。

一旦服务器被暂停,不应再发出任何 connection 事件。

$server->pause();

$server->on('connection', assertShouldNeverCalled());

尽管通常不推荐,但该方法仅提供建议,服务器可能会继续发出 connection 事件。

除非另有说明,成功打开的服务器不应以暂停状态启动。

您可以通过再次调用 恢复() 来继续处理事件。

请注意,这两个方法可以多次调用,特别是多次调用 暂停() 应该没有效果。同样,在 关闭() 之后调用此方法是无效操作。

恢复()

恢复(): void 方法可用于恢复接受新的传入连接。

在之前的 暂停() 之后重新将套接字资源附加到EventLoop。

$server->pause();

$loop->addTimer(1.0, function () use ($server) {
    $server->resume();
});

请注意,这两个方法可以多次调用,特别是如果没有先前的 暂停() 而调用 恢复() 应该没有效果。同样,在 关闭() 之后调用此方法是无效操作。

关闭()

关闭(): 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套接字上下文选项作为简单数组传递。

您可以通过简单地将tls:// URI方案加在前面来启动一个安全的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<ConnectionInterface,Exception>方法可以用来创建到指定远程地址的流连接。

它返回一个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();
});

高级:如果您需要自定义DNS React\Dns\Resolver\ResolverInterface实例,您也可以像这样设置您的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支持tcp://tls://unix:// URI方案。如果您想明确禁止其中任何一个,您可以简单地传递布尔标志如下

// 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文档中关于套接字上下文选项SSL上下文选项的说明。

高级:默认情况下,Connector支持tcp://tls://unix:// URI方案。为此,它自动设置所需的连接器类。如果您想显式传递任何自定义连接器,您可以简单地传递实现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 连接,并拒绝生成的承诺。

您可以选择将额外的 套接字上下文选项 传递给构造函数,如下所示:

$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 靠一个 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, $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();

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

您可以可选地向构造函数传递额外的 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();

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

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 域套接字是一个原子操作,即其 Promise 将立即解决(解决或拒绝)。因此,在产生的 Promise 上调用 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.3

有关版本升级的详细信息,请参阅 CHANGELOG

该项目旨在在任何平台上运行,因此不需要任何 PHP 扩展,并支持在从 PHP 5.3 到当前 PHP 7+ 和 HHVM 上运行。强烈建议使用 PHP 7+,部分原因是其性能有了显著提升,部分原因是旧版本的 PHP 需要一些如以下所述的解决方案。

从 PHP 5.6 开始,安全的 TLS 连接获得了一些重大升级,默认情况下更加安全,而旧版本则需要显式上下文选项。此库不负责这些上下文选项,因此消费者需要负责设置适当的上下文选项,如上述所述。

PHP < 7.3.3(以及 PHP < 7.2.15)存在一个 bug,其中 feof() 可能会由于片段化的 TLS 记录而占用 100% 的 CPU 使用率。我们通过一次消耗完整的接收缓冲区来解决这个问题,以避免在 TLS 缓冲区中存在旧数据。这已被证明可以解决良好行为的对等方的 CPU 使用率问题,但在高吞吐量场景中可能会导致非常大的数据块。由于网络 I/O 缓冲区或受影响版本上的恶意对等方,可能仍然会触发有问题的行为,强烈建议升级。

PHP < 7.1.4(以及 PHP < 7.0.18)在通过 TLS 流一次性写入大量数据时存在一个 bug。我们通过仅对旧版本的 PHP 限制写入块大小为 8192 字节来解决这个问题。这仅是一个解决方案,并且会在受影响的版本上产生可感知的性能惩罚。

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

测试

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

$ composer install

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

$ php vendor/bin/phpunit

测试套件还包含一些依赖稳定互联网连接的功能集成测试。如果您不想运行这些测试,可以像这样简单地跳过它们

$ php vendor/bin/phpunit --exclude-group internet

许可证

MIT,请参阅 LICENSE 文件