Icicle 的异步 DNS。

v0.6.0 2015-12-07 05:30 UTC

This package is auto-updated.

Last update: 2024-09-14 03:08:25 UTC


README

此库是 Icicle 的组件之一,提供异步 DNS 查询执行器、解析器和客户端连接器。目前异步 DNS 服务器正在开发中,将来将被添加到该组件中。像其他 Icicle 组件一样,此库使用从 AwaitablesGenerators 构建的自定义协程来使异步代码的编写更类似于同步代码。

Build Status Coverage Status Semantic Version MIT License @icicleio on Twitter

要求
  • PHP 5.5+ 用于 v0.6.x 分支(当前稳定)和 v1.x 分支(镜像当前稳定)
  • PHP 7 用于 v2.0 分支(开发中)支持生成器委托和返回表达式
安装

推荐的安装方法是使用 Composer 包管理器。(有关安装和使用 Composer 的信息,请参阅 Composer 安装指南。)

运行以下命令以在您的项目中使用此库

composer require icicleio/dns

您还可以手动编辑 composer.json 以将此库添加为项目需求。

// composer.json
{
    "require": {
        "icicleio/dns": "^0.6"
    }
}

示例

下面的示例使用 Icicle\Dns\resolver() 函数异步查找域名 icicle.io 的 IP 地址。

use Icicle\Coroutine\Coroutine;
use Icicle\Dns;
use Icicle\Loop;

$generator = function ($domain) {
    try {
        $ips = (yield Dns\resolve($domain));
        
        foreach ($ips as $ip) {
            echo "IP: {$ip}\n";
        }
    } catch (Exception $exception) {
        echo "Error when executing query: {$exception->getMessage()}\n";
    }
};

$coroutine = new Coroutine($generator('icicle.io'));
$coroutine->done();

Loop\run();

文档

返回 Generator 的方法可用于创建 Coroutine(例如,new Coroutine($executor->execute(...)))或在其中另一个 Coroutine 中产生(在 PHP 7 中使用 yield from 以获得更好的性能)。

此库使用 LibDNS 创建和解析 DNS 消息。遗憾的是,此库的文档目前仅限于源代码中的 DocComments。如果只在此库中使用解析器和连接器,则无需担心 LibDNS 的工作原理。Executors 返回使用 LibDNS\Messages\Message 实例解析的协程,这些实例表示 DNS 服务器的响应。使用这些对象很简单,将在以下执行器部分中描述。

函数原型

以下语法用于描述对象实例方法的原型

ClassOrInterfaceName::methodName(ArgumentType $arg): ReturnType

执行器

执行器是 DNS 组件的基础,执行任何 DNS 查询并返回该查询的完整结果。解析器和连接器依赖于执行器来执行其操作所需的 DNS 查询。

每个执行器都实现了 Icicle\Dns\Executor\Executor,它定义了一个方法,即 execute()

Executor::execute(
    string $domain,
    string|int $type,
    array $options = []
): \Generator

如果执行器在 timeout 秒内没有收到响应,它将重试查询多次。查询失败前重试的次数由 retries 定义,每次查询尝试之间都会间隔 timeout 秒。

execute() 函数

执行 DNS 查询的最简单方法是使用 Icicle\Dns\execute() 函数。此函数使用一个 Icicle\Dns\Executor\Executor 对象,该对象可以通过 Icicle\Dns\executor() 函数设置或检索。

`Icicle\Dns\execute(
    string $domain,
    string|int $type,
    array $options = []
): \Generator

使用 execute() 函数查找域的 A 记录的示例

use Icicle\Coroutine\Coroutine;
use Icicle\Dns;
use Icicle\Loop;
use LibDNS\Messages\Message;

$coroutine = new Coroutine(Dns\execute('google.com', 'A'));

$coroutine->done(
    function (Message $message) {
        foreach ($message->getAnswerRecords() as $resource) {
            echo "TTL: {$resource->getTTL()} Value: {$resource->getData()}\n";
        }
    },
    function (Exception $exception) {
        echo "Query failed: {$exception->getMessage()}\n";
    }
);

Loop\run();

创建执行器

最简单的执行器是 Icicle\Dns\Executor\BasicExecutor,通过将 DNS 服务器的 IP 地址提供给构造函数来创建。建议使用离您最近的 DNS 服务器,例如本地路由器。如果不可能,可以使用 Google 运营的两个 DNS 服务器,地址为 8.8.8.88.8.4.4

use Icicle\Dns\Executor\BasicExecutor;

$executor = new BasicExecutor('8.8.8.8');

如果需要自定义连接到名称服务器的行为,则 Icicle\Dns\Executor\BasicExecutor 构造函数还接受一个 Icicle\Socket\Connector\Connector 实例作为第二个参数。如果没有提供实例,则使用默认的全局连接器(见 Icicle\Socket\connector())。

使用执行器

创建后,可以通过调用带有要执行的域名和类型的 execute() 方法来使用执行器。类型可以是命名记录类型的无大小写字符串(例如,'A''MX''NS''PTR''AAAA')或对应于记录类型的整数值(LibDNS\Records\ResourceQTypes 定义了与类型整数值对应的常量)。execute() 返回一个协程,该协程完成一个代表名称服务器响应的 LibDNS\Messages\Message 实例。LibDNS\Messages\Message 对象有几个方法需要用于检索响应中的数据。

  • getAnswerRecords():返回一个 LibDNS\Records\RecordCollection 实例,该实例是包含响应答案记录的可遍历集合。
  • getAuthorityRecords():返回一个包含响应授权记录的 LibDNS\Records\RecordCollection 实例。
  • getAdditionalRecords():返回一个包含响应附加记录的 LibDNS\Records\RecordCollection 实例。
  • getAuthorityRecords():返回一个包含响应授权记录的 LibDNS\Records\RecordCollection 实例。
  • isAuthoritative():确定响应是否为返回的记录授权。

可遍历的 LibDNS\Records\RecordCollection 对象中的 DNS 记录表示为 LibDNS\Records\Resource 实例。这些对象有多个方法可以用来访问与记录关联的数据。

  • getType():返回记录类型作为 integer
  • getName():获取与记录关联的域名作为 string
  • getData():返回一个代表记录数据的 LibDNS\Records\RData 实例。此对象可以被转换为 string,或者可以通过 LibDNS\Records\RData::getField(int $index) 方法访问每个字段。资源中的字段数量取决于资源类型(例如,MX 记录包含两个字段,一个优先级和一个主机名)。
  • getTTL():获取 TTL(生存时间)作为 integer

以下是一个如何使用执行器查找域的 NS 记录的示例。

use Icicle\Coroutine\Coroutine;
use Icicle\Dns\Executor\BasicExecutor;
use Icicle\Loop;
use LibDNS\Messages\Message;

$executor = new BasicExecutor('8.8.8.8');

$coroutine = new Coroutine($executor->execute('google.com', 'NS'));

$coroutine->done(
    function (Message $message) {
        foreach ($message->getAnswerRecords() as $resource) {
            echo "TTL: {$resource->getTTL()} Value: {$resource->getData()}\n";
        }
    },
    function (Exception $exception) {
        echo "Query failed: {$exception->getMessage()}\n";
    }
);

Loop\run();

MultiExecutor

可以使用 Icicle\Dns\Executor\MultiExecutor 类将多个执行器组合起来,向多个名称服务器发送查询,以便即使某些名称服务器停止响应,查询也能得到解决。

use Icicle\Coroutine\Coroutine;
use Icicle\Dns\Executor\BasicExecutor;
use Icicle\Dns\Executor\MultiExecutor;
use Icicle\Loop;
use LibDNS\Messages\Message;

$executor = new MultiExecutor();

$executor->add(new BasicExecutor('8.8.8.8'));
$executor->add(new BasicExecutor('8.8.4.4'));

// Executor will send query to 8.8.4.4 if 8.8.8.8 does not respond.
$coroutine = new Coroutine($executor->execute('google.com', 'MX'));

$coroutine->done(
    function (Message $message) {
        foreach ($message->getAnswerRecords() as $resource) {
            echo "TTL: {$resource->getTTL()} Value: {$resource->getData()}\n";
        }
    },
    function (Exception $exception) {
        echo "Query failed: {$exception->getMessage()}\n";
    }
);

Loop\run();

使用上述执行器的查询将自动向第二个名称服务器发送请求,如果第一个没有响应。后续查询最初发送到最后一个成功响应查询的服务器。

解析器

解析器用于查找给定域的IP地址。Icicle\Dns\Resolver\BasicResolver实现了Icicle\Dns\Resolver\Resolver接口,该接口定义了一个单一的方法,即resolve()。解析器本质上是一个专门用于执行仅A查询的执行器,通过一个IP地址数组(即使只找到一个或零个IP地址,协程仍然通过数组来解析)来满足从resolve()返回的协程。

Resolver::resolve(
    string $domain,
    array $options = []
): \Generator

resolve() 函数

找到域IP地址的最简单方法就是使用Icicle\Dns\resolve()函数。这个函数使用一个Icicle\Dns\Resolver\Resolver对象,该对象可以通过Icicle\Dns\resolver()函数进行设置或获取。

`Icicle\Dns\resolve(
    string $domain,
    array $options = []
): \Generator

使用resolve()函数解析域IP地址的示例

use Icicle\Coroutine\Coroutine;
use Icicle\Dns;
use Icicle\Loop;

$coroutine = new Coroutine(Dns\resolve('google.com'));

$coroutine->done(
    function (array $ips) {
        foreach ($ips as $ip) {
            echo "IP: {$ip}\n";
        }
    },
    function (Exception $exception) {
        echo "Query failed: {$exception->getMessage()}\n";
    }
);

Loop\run();

与执行器类似,如果域名服务器在timeout秒内没有响应,解析器将重试查询retries次。

Icicle\Resolver\BasicResolver类通过传递一个用于执行解析域名查询的Icicle\Executor\Executor实例来构建。如果没有提供执行器,则默认创建一个,使用8.8.8.88.8.4.4作为执行器的DNS服务器。

示例
use Icicle\Coroutine\Coroutine;
use Icicle\Dns\Resolver\BasicResolver;
use Icicle\Loop;

$resolver = new BasicResolver();

$coroutine = new Coroutine($resolver->resolve('google.com'));

$coroutine->done(
    function (array $ips) {
        foreach ($ips as $ip) {
            echo "IP: {$ip}\n";
        }
    },
    function (Exception $exception) {
        echo "Query failed: {$exception->getMessage()}\n";
    }
);

Loop\run();

连接器

连接器组件首先解析提供的主机名,然后建立连接,并使用Icicle\Socket\Socket实例解析返回的协程。

Icicle\Dns\Connector\Connector定义了一个单一的方法connect(),该方法应解析主机名并连接到解析出的服务器之一,并通过连接的客户机解析协程。

Connector::connect(
    string $domain,
    int $port,
    array $options = [],
): \Generator

connect() 函数

解析域名并连接到解析主机上端口的简单方法就是使用Icicle\Dns\connect()函数。这个函数使用一个Icicle\Dns\Connector\Connector对象,该对象可以通过Icicle\Dns\connector()函数进行设置或获取。

`Icicle\Dns\connect(
    string $domain,
    int $port,
    array $options = []
): \Generator

使用connect()函数解析域IP地址并连接到端口443的示例

use Icicle\Dns;
use Icicle\Loop;
use Icicle\Socket\Socket;

$connector = new DefaultConnector();

$coroutine = new Coroutine(Dns\connect('google.com', 443));

$coroutine->done(
    function (Socket $client) {
        echo "IP: {$client->getRemoteAddress()}\n";
        echo "Port: {$client->getRemotePort()}\n";
    },
    function (Exception $exception) {
        echo "Connecting failed: {$exception->getMessage()}\n";
    }
);

Loop\run();

Icicle\Dns\Connector\DefaultConnector将尝试连接到给定主机名找到的IP地址之一。如果该IP的服务器无响应,连接器将尝试连接到列表中的下一个IP,直到服务器接受连接。只有当连接器无法连接到所有IP时,才会拒绝connect()返回的协程。构造函数还可选地接受一个Icicle\Socket\Connector\Connector实例,如果需要在连接到解析的主机时实现自定义行为。

此外,还可以使用Icicle\Socket\Connector\Connector::connect()的所有其他选项。

示例
use Icicle\Dns\Connector\DefaultConnector;
use Icicle\Loop;
use Icicle\Socket\Socket;

$connector = new DefaultConnector();

$coroutine = new Coroutine($connector->connect('google.com', 80));

$coroutine->done(
    function (Socket $client) {
        echo "IP: {$client->getRemoteAddress()}\n";
        echo "Port: {$client->getRemotePort()}\n";
    },
    function (Exception $exception) {
        echo "Connecting failed: {$exception->getMessage()}\n";
    }
);

Loop\run();