amphp/socket

基于 Amp 和 Revolt 的非阻塞套接字连接/服务器实现。

维护者

详细信息

github.com/amphp/socket

主页

源码

问题

赞助包维护!
amphp

安装次数: 10,019,815

依赖者: 89

建议者: 1

安全: 0

星标: 226

关注者: 14

分支: 38

开放问题: 5


README

AMPHP 是一套专为 PHP 设计的事件驱动库集合,注重纤程和并发。 amphp/socket 是一个用于建立和加密非阻塞套接字的库。它为客户端和服务器提供套接字抽象。它抽象了 PHP 中非常低层的非阻塞流。

Latest Release MIT License

安装

此包可以作为 Composer 依赖项安装。

composer require amphp/socket

需求

amphp/socket 严重依赖于 amphp/byte-stream,特别是其 ReadableStreamWritableStream 接口。

连接到服务器

amphp/socket 允许客户端通过 TCP、UDP 或 Unix 域套接字连接到服务器。您可以使用 Amp\Socket\connect() 建立套接字连接。如果连接失败且存在多个 IP,它会自动解析 DNS 名称并重试其他 IP。

// You can customize connect() options using ConnectContext
$connectContext = (new Amp\Socket\ConnectContext)
        ->withConnectTimeout(5);

// You can optionally pass a Cancellation object to cancel a pending connect() operation
$deferredCancellation = new Amp\DeferredCancellation();

$socket = connect('amphp.org:80', $connectContext, $deferredCancellation->getCancellation());

加密连接 / TLS

如果您想通过 TLS 连接,请使用 Amp\Socket\connectTls() 或在返回的套接字上调用 $socket->setupTls()

处理连接

Socket 实现 ReadableStreamWritableStream,因此所有来自 amphp/byte-stream 的内容都适用于接收和发送数据。

#!/usr/bin/env php
<?php // basic (and dumb) HTTP client

require __DIR__ . '/../vendor/autoload.php';

// This is a very simple HTTP client that just prints the response without parsing.
// league/uri required for this example.

use Amp\ByteStream;
use Amp\Socket\ClientTlsContext;
use Amp\Socket\ConnectContext;
use League\Uri\Http;
use function Amp\Socket\connect;
use function Amp\Socket\connectTls;

$stdout = ByteStream\getStdout();

if (\count($argv) !== 2) {
    $stdout->write('Usage: examples/simple-http-client.php <url>' . PHP_EOL);
    exit(1);
}

$uri = Http::createFromString($argv[1]);
$host = $uri->getHost();
$port = $uri->getPort() ?? ($uri->getScheme() === 'https' ? 443 : 80);
$path = $uri->getPath() ?: '/';

$connectContext = (new ConnectContext)
        ->withTlsContext(new ClientTlsContext($host));

$socket = $uri->getScheme() === 'http'
        ? connect($host . ':' . $port, $connectContext)
        : connectTls($host . ':' . $port, $connectContext);

$socket->write("GET {$path} HTTP/1.1\r\nHost: $host\r\nConnection: close\r\n\r\n");

ByteStream\pipe($socket, $stdout);

服务器

amphp/socket 允许监听传入的 TCP 连接以及通过 Unix 域套接字的连接。如果您选择启用 TLS,它默认使用安全的 TLS 设置。

监听和接受连接

使用 Amp\Socket\Socket\listen() 在端口或 Unix 域套接字上监听。它是对 stream_socket_server 的包装,在失败时通过异常提供有用的错误信息。

一旦开始监听,请使用 Server::accept() 接受客户端。它返回一个在接收新客户端时返回的 Socket。它通常在 while 循环中调用。

$server = Socket\listen("tcp://127.0.0.1:1337");

while ($client = $server->accept()) {
    // You shouldn't spend too much time here, because that blocks accepting another client, so we use async():
    async(function () use ($client) {
        // Handle client connection here
    });
}

处理连接

Socket 实现 ReadableStreamWritableStream,因此所有来自 amphp/byte-stream 的内容都适用于接收和发送数据。最好在各自的协程中处理客户端,同时让服务器尽快接受所有客户端。

#!/usr/bin/env php
<?php // basic (and dumb) HTTP server

require __DIR__ . '/../vendor/autoload.php';

// This is a very simple HTTP server that just prints a message to each client that connects.
// It doesn't check whether the client sent an HTTP request.

// You might notice that your browser opens several connections instead of just one,
// even when only making one request.

use Amp\Socket;
use function Amp\async;

$server = Socket\listen('127.0.0.1:0');

echo 'Listening for new connections on ' . $server->getAddress() . ' ...' . PHP_EOL;
echo 'Open your browser and visit http://' . $server->getAddress() . '/' . PHP_EOL;

while ($socket = $server->accept()) {
    async(function () use ($socket) {
        $address = $socket->getRemoteAddress();
        $ip = $address->getHost();
        $port = $address->getPort();

        echo "Accepted connection from {$address}." . PHP_EOL;

        $body = "Hey, your IP is {$ip} and your local port used is {$port}.";
        $bodyLength = \strlen($body);

        $socket->write("HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: {$bodyLength}\r\n\r\n{$body}");
        $socket->end();
    });
}

关闭连接

一旦完成与客户端的通信,请使用 Socket::close() 关闭连接。如果您想在关闭连接之前等待所有数据成功写入,请使用 Socket::end()。请参阅上面的示例。

服务器地址

有时您不知道服务器正在监听的地址,例如,因为您监听了 tcp://127.0.0.1:0,它分配了一个随机空闲端口。您可以使用 Server::getAddress() 获取服务器绑定到的地址。

服务器关闭

一旦完成服务器套接字的通信,请关闭套接字。这意味着服务器将不再在指定位置监听。使用 Server::close() 关闭服务器套接字。

加密连接 / TLS

Amp\Socket\Socket\listen() 的文档所述,在接收连接后需要手动启用 TLS。对于 TLS 服务器套接字,您在指定地址上监听 tcp:// 协议。接受客户端后,请调用 $socket->setupTls(),其中 $socket 是来自 SocketServer::accept() 的套接字。

警告 在调用 Socket::setupTls() 完成之前传输的所有数据将以明文形式传输。不要尝试从套接字读取或手动写入它。这样做将会读取本应由 OpenSSL 读取的原始 TLS 握手数据。

自签名证书

ClientTlsContext 中没有选项允许使用自签名证书,因为它并不比禁用对等方验证更安全。要安全地使用自签名证书,请禁用对等方验证,并使用 ClientTlsContext::withPeerFingerprint() 要求对证书进行指纹验证。

安全

如果您发现任何与安全相关的问题,请通过电子邮件发送到 [email protected] 而不是使用问题跟踪器。

许可证

MIT 许可证(MIT)。有关更多信息,请参阅 LICENSE