Ориентация главы
DNS превращает доменные имена в списки адресов (глава 63 уже встретила синхронный xrtNetResolve). Эта глава разворачивает сродную движку асинхронную форму: объект операции xnetresolveop подаётся в движок — колбэк исполняется на Worker (тот же поток, что и последующие операции соединения — естественно без блокировок; в этом весь смысл «сродства с движком»); результат — всё тот же список адресов (вход happy-eyeballs не меняется); тайм-аут и отмена управляются через объект операции. Кэш и стратегия резолва (hosts-приоритет, TTL) согласованы на уровне контрактов модуля.
66.1引入
Ловушка синхронного резолва — вне стартового пути: резолв на каждый запрос (сценарии прямого соединения) блокирует поток запроса на миллисекунды или секунды (медленный DNS — самый частый источник хвостовых задержек сетевого сервиса); после резолва идёт соединение — и сшивка двух асинхронных шагов (инициирование соединения в колбэке резолва) в обычной модели пула потоков требует межпоточной передачи данных (резолв на потоке пула A, соединение нужно на потоке движка B — блокировки или очереди).
Ответ сродного движка: операция резолва подаётся в движок — колбэк завершения работает на Worker, в том же потоке, что соединения и приём/передача — «разрешил — сразу connect», ноль передач, ноль блокировок. Правильное заполнение «слота DNS-выноса» скелета главы 61: не вынос в отдельный пул потоков, а подача в движок (вынос вычислений в пул задач главы 59 против резолва внутри движка этой главы — критерий выбора: «где живут последующие операции»).
66.2概念
Разделение синхронного и асинхронного
| Форма | Вход | Подходит |
|---|---|---|
| Синхронная | xrtNetResolve | Разбор стартовой конфигурации, инструменты |
| Асинхронная (движок) | resolveop подаётся в движок | Резолв на запрос, сшивка с сервисными соединениями |
Объект асинхронной операции xnetresolveop: подача (движок + домен + порт + колбэк + данные) → завершение (колбэк на Worker, xrtNetResolveOpResult берёт список). Дисциплина колбэка по всей библиотеке (лёгкая работа — результаты резолва обычно сразу потребляются/передаются).
Смысл сродства с движком
Суть «сродства»: место исполнения последующих операций решает, где исполняется резолв — чтобы соединять соединения, которыми управляет движок, отдайте резолв тому же движку. Логика та же, что у «привязки потока планировщика» главы 56: данные и операции в одном потоке — и цена синхронизации исчезает.
Тайм-аут, отмена и кэш
Асинхронные операции несут тайм-ауты (семейство параметров deadline — соглашения главы 42) и отмену (объект операции отменяем — DNS-диалект дерева отмен главы 54). Семантика кэша — в контракте модуля: результаты резолва решает системный путь (hosts-приоритет, системный кэш) — XRT не делает прикладного DNS-кэша (наблюдение главы 59: вверх по течению кэш уже есть; повторный — сложность, а не выгода; если прикладной кэш действительно нужен — стройте дерево значений + TTL — хозяин кэша главы 31). Пример resolver_future — сестра в форме Future (модель доставки главы 58, надетая на резолв).
Разделение семейства примеров resolver
dns (синхронный список, глава 63), resolver (колбэк движка, главный пример этой главы), resolver_future (форма Future), resolve_tour (тур по всем интерфейсам) — четыре примера, четыре лица одной возможности. Выбор: инструменты — синхронно, сервисы — колбэк движка, композиция (многодоменная конкурентность + First) — Future.
66.3示例
Полная программа: сродный движку резолв
Из примера репозитория examples/network/resolver/main.c — колбэк получает список адресов на Worker:
*/ #include <stdio.h> #include <string.h> #include <xrt.h> #include <math.h> static inline double exampleTimerLimit(int64 Timeout) { return Timeout == XRT_WAIT_FOREVER ? INFINITY : xrtTimer() + (double)Timeout / 1000.0; } static inline bool exampleTimerExpired(double Limit) { return xrtTimer() >= Limit; } static inline int64 exampleTimerRemaining(double Limit) { double Ms; if (Limit == INFINITY) return XRT_WAIT_FOREVER; Ms = ceil((Limit - xrtTimer()) * 1000.0); return Ms <= 0 ? 0 : Ms >= 0x1p63 ? INT64_MAX : (int64)Ms; } typedef struct exampleresolver { xatomic32 Done; } exampleresolver; /* 输出异步查询得到的全部地址。 */ static void exampleResolverDone(xnetresolveop* pOperation, ptr pData) { exampleresolver* pState = (exampleresolver*)pData; xnetaddrlist* pAddresses = xrtNetResolveOpResult(pOperation); if ( pAddresses != NULL ) { for ( size_t i = 0; i < xrtNetAddrListCount(pAddresses); i++ ) { str sAddress = xrtNetAddrString( xrtNetAddrListGet(pAddresses, i) ); if ( sAddress != NULL ) { printf("%s\n", sAddress); xrtFree(sAddress); } } xrtNetAddrListDestroy(pAddresses); } else { const xerror* pError = xrtNetResolveOpError(pOperation); fprintf(stderr, "%s\n", pError != NULL ? xrtErrorMessage(pError) : "resolve failed"); } (void)xrtAtomic32FetchAdd(&pState->Done, 1, XMEMORY_RELEASE); } /* 使用独立 Resolver 异步查询并等待一次完成回调。 */ int main(void) { exampleresolver State; xnetresolver* pResolver; xnetresolveop* pOperation; double iDeadline; memset(&State, 0, sizeof(State)); pResolver = xrtNetResolverCreate(NULL); if ( pResolver == NULL ) { return 1; } pOperation = xrtNetResolverResolve( pResolver, "localhost", XNET_FAMILY_UNSPEC, exampleResolverDone, &State ); if ( pOperation == NULL ) { (void)xrtNetResolverDestroy(pResolver); return 2; } iDeadline = exampleTimerLimit(5000); while ( xrtAtomic32Load(&State.Done, XMEMORY_ACQUIRE) == 0 ) { if ( exampleTimerExpired(iDeadline) ) { (void)xrtNetResolveOpCancel(pOperation); break; } xrtThreadYield(); } xrtNetResolveOpDestroy(pOperation); return xrtNetResolverDestroy(pResolver) ? 0 : 3; }
Что только что произошло. (1) Операция резолва подана в движок (домен localhost + порт + колбэк завершения + данные состояния) — главный поток не блокируется. (2) Колбэк завершения исполняется на Worker: xrtNetResolveOpResult берёт список адресов и печатает построчно — ::1 и 127.0.0.1 (порядок может смениться — двухстековая списочная семантика идентична синхронной версии). (3) Вывод в колбэке прямо через printf — он работает в обычном контексте потока (не ограниченная среда вроде обработчика сигнала — контраст главы 49). (4) Главный поток ждёт атомарный счётчик (флаг Done) для подтверждения завершения — простейшее рандеву; в сервисной форме колбэк сразу Connect (ноль передач).
Полная программа: форма Future
Из examples/network/resolver_future/main.c — модель доставки главы 58, надетая на резолв:
#include <stdio.h> #include <xrt.h> */ /* 使用 Future 等待异步 DNS,并读取 Future 持有的完整地址列表。 */ int main(void) { xnetresolver* pResolver = xrtNetResolverCreate(NULL); xfuture* pFuture; xnetaddrlist* pAddresses; if ( pResolver == NULL ) { return 1; } pFuture = xrtNetResolveAsync( pResolver, "localhost", XNET_FAMILY_UNSPEC ); if ( (pFuture == NULL) || (xrtFutureWaitFor(pFuture, 5000) != XWAIT_OK) ) { xrtFutureDestroy(pFuture); (void)xrtNetResolverDestroy(pResolver); return 2; } pAddresses = (xnetaddrlist*)xrtFutureValue(pFuture); if ( pAddresses == NULL ) { const xerror* pError = xrtFutureError(pFuture); fprintf(stderr, "%s\n", pError != NULL ? xrtErrorMessage(pError) : "resolve failed"); } else { for ( size_t i = 0; i < xrtNetAddrListCount(pAddresses); i++ ) { str sAddress = xrtNetAddrString( xrtNetAddrListGet(pAddresses, i) ); if ( sAddress != NULL ) { printf("%s\n", sAddress); xrtFree(sAddress); } } } xrtFutureDestroy(pFuture); return xrtNetResolverDestroy(pResolver) ? 0 : 3; }
Что только что произошло. (1) Подача резолва возвращает Future — результат (список адресов) доставляется через Future. (2) Wait/комбинаторы/продолжения (полный набор главы 58) применимы напрямую: многодоменный конкурентный резолв + First (какой домен разрешился первым — тот и работает), гонка с тайм-аутом (резолв против таймера) — форма Future разблокирует композицию. (3) Колбэковая форма примера resolver подходит прямолинейному потоку «разрешил — сразу connect»; форма Future подходит случаю «резолв — звено большей оркестрации» — выбирайте по потребностям оркестрации.
66.4契约
- Сродство с движком: колбэки резолва на Worker — тот же поток, что последующие соединения, ноль передач и ноль блокировок; если продолжение на движке — подайте резолв в движок.
- Списочная семантика: асинхронные результаты тоже списки адресов — happy-eyeballs пробует по порядку (контракт главы 63 продолжается).
- Лёгкие колбэки: колбэки резолва потребляют/передают результаты сразу — тяжёлая обработка переезжает на следующее звено.
- Тайм-аут и отмена: объекты операций несут семейство deadline и отмену — диалект главы 54.
- Граница кэша: XRT не делает прикладного DNS-кэша; при действительной нужде стройте сами (дерево значений + TTL — хозяин кэша главы 31).
- Выбор формы: инструменты синхронно / сервисы колбэк движка / оркестрация Future — четыре примера, четыре лица.
От примеров к инженерии: три хозяина резолва
Стартовая конфигурация: сервис при старте читает из конфига домены верхнего уровня — один синхронный резолв, таблица адресов в хранилище (дерево значений главы 31); синхронность стартового пути приемлема (однократно, с защитой тайм-аутом).Прогрев пула соединений: на старте или периодически асинхронно резолвить домены верхнего уровня + пробные соединения — прогрев снимает DNS-хвост первой просьбы; испытательное упражнение — ровно это. Сценарии прямого соединения на каждый запрос (прокси-пересылка, краулеры): у каждого запроса свой домен — резолв обязан быть асинхронным и сродным движку (сцена принуждения ловушки 1); в колбэке резолва прямое соединение (цикл happy-eyeballs разворачивается в колбэке). Три хозяина выбираются по «отношению частоты резолва к пути запроса» — однократная конфигурация при старте синхронно, периодический прогрев асинхронно в движке, на каждый запрос — сродный движку асинхронный.
Полная позиция реализации happy-eyeballs
Глава 63 установила семантику «пробовать по порядку»; эта глава добавляет её полную позицию реализации как асинхронной оркестрации: резолв завершён (список в руках) → конкурентно инициируется предпочтительное соединение (обычно v6) + таймер (~250 мс) → если таймер сработал первым, конкурентно запускается второй элемент (v4) → любое успешное отменяет остальные. Это прямое применение комбинаторов Future (First + гонка тайм-аута главы 58) — форма resolver_future существует ради этого. Принимает ли вход Connect в XRT список адресов? Нет — один адрес; цикл разворачивает вызывающий (по одному Connect на движок) — и это разворачивание и есть слой «стратегии группировки сети» (библиотека расширений xhttp несёт полную реализацию — том 10).
Пост наблюдения: задержка и доля неудач резолва
Резолв — первый прыжок сетевого сервиса, и наблюдение (конвейер главы 50) занимает первый пост тома 7: длительность резолва (интервал подачи-до-колбэка — главный источник хвостовых задержек); доля неудач и классификация ошибок (NXDOMAIN против тайм-аута против системной ошибки — обработка разная: ошибка домена — проблема конфигурации, тайм-аут — проблема сети); распределение размера списка (запас отката многодоменных адресов). Три метрики — в структурированный журнал: проблемы DNS (самый частый и самый скрытый источник сбоев сервиса) из «кажется медленным» превращаются в видимые кривые. Статистическая схема примера resolve_tour — образец снятия этих трёх метрик.
Развязка одного исторического узла: загадка потокобезопасности getaddrinfo
У DNS традиционного C-программирования есть знаменитая тёмная яма: поведение getaddrinfo непоследовательно между платформами (ранние glibc не потокобезопасны, некоторым платформам нужна особая инициализация) — редкие падения многопоточных сервисов на DNS были классикой. Слой резолва XRT обрабатывает эти платформенные различия внутри (обёртка системного пути резолва — пользователю нуль заметности). Ценность понимания изоморфна API сигналов главы 49: дисциплину ограниченной среды несёт модуль, а бизнес-код сохраняет нормальную форму. О пределах тоже надо знать: XRT не несёт собственного стека протокола DNS (идёт через системный резолвер) — сценарии, требующие DoH/кастомного резолва, реализуются на уровне xhttp/хоста (политика системного резолвера прозрачна и управляема для корпоративных сетей).
Уточнение отношений с главой 62
Разделение этой главы и главы 63 легко размывается — обе про «домен в адрес». Уточнено: глава 63 — глава модели адресов — тип xnetaddr и операции есть валюта тома, где xrtNetResolve (синхронный список) появляется попутно как удобный вход получения адресов; эта глава — глава стратегии резолва — асинхронная форма, сродство движку, тайм-аут/отмена, граница кэша, оркестрация happy-eyeballs. Одна глава говорит «что такое адрес», другая — «откуда адреса берутся» — противопоставление примера dns (глава 63) и примера resolver (эта глава) и есть противопоставление точек зрения двух глав. Последующие главы (соединения TCP, дозвон прокси) зависят только от типа адресов главы 63 — стратегия резолва этой главы используется на слое оркестрации соединений сервиса.
Типичные ошибки
Симптомы: высокие хвостовые задержки запросов — P99 растягивается на сотни миллисекунд; редкий медленный DNS подвешивает весь запрос; пропускная способность дрожит вместе с DNS.
Причина: xrtNetResolve (синхронный) вошёл в путь запроса — каждый запрос блокируется на системном резолве (даже с системным кэшем есть переключения контекста и ожидание).
void handle_request(void) { xnetaddrlist* List = xrtNetResolve(Host, Port, XNET_FAMILY_UNSPEC); /* синхронно — запрос блокируется */ ConnectVia(List); }
/* Резолв на старте / прогреве пула соединений, или асинхронная форма в пути запроса */ SubmitResolveOp(Engine, Host, Port, onResolved, Data); /* сродство движку — в колбэке сразу соединение */
Симптомы: замок между колбэком резолва и кодом соединения — пропускная способность затыкается на этом замке; редкие тупики (порядок замка конфликтует с другими замками движка).
Причина: резолв работает в отдельном пуле потоков (не сродный движку) — результат надо межпоточно передать в поток движка, и передача вносит замок или очередь.
/* резолв в отдельном пуле */ xrtTaskSubmit(Pool, resolveAndHandoff, Host, NULL); /* внутри resolveAndHandoff: Lock(gAddrMutex); сохранить список; Unlock; Post разбудить движок — цена передачи + замок */
/* сродство движку — колбэк потребляет прямо на Worker, передача исчезает */ SubmitResolveOp(Engine, Host, Port, onResolved, Data); /* onResolved работает на Worker: взять список, сразу Connect — тот же поток, ноль замков */
Практические задания
Один и тот же домен резолвится синхронно и колбэком движка — списки результатов совпадают; напечатайте исполняющий поток обеих версий (CurrentId), доказав, что колбэк на Worker.
Три домена конкурентно резолвятся движком (три объекта операций) — по завершении всех сводятся размеры списков; сравнение самый быстрый/самый медленный (таймер главы 42).
Реализуйте линию прогрева при старте сервиса: N сконфигурированных доменов верхнего уровня резолвятся конкурентно → по каждому берётся первый адрес и инициируется пробное соединение → отчёт по таблице доступных верхних. Критерий приёмки: прогрев нигде не блокирует главный поток; частичные неудачи доменов не влияют на остальные (список неудач с цепочками причин в журнал); общий тайм-аут ограничивает всё (deadline); таблица доступных — в дерево значений (глава 31) для дальнейшего использования.
Итоги
| Тема | Краткая справка |
|---|---|
| Принцип сродства | продолжение на движке → резолв подаётся в движок (колбэк в том же потоке, ноль замков) |
| Выбор формы | инструменты синхронно / сервисы колбэк движка / оркестрация Future (четыре примера, четыре лица) |
| Списочная семантика | асинхронные результаты — тоже списки; контракт happy-eyeballs продолжается |
| Тайм-аут/отмена | объекты операций несут deadline и отмену — диалект главы 54 |
| Граница кэша | XRT не добавляет прикладного кэша; при нужде — своё дерево значений + TTL |
| Хвостовые задержки | резолв на каждый запрос — только асинхронно; синхронный — источник хвостов номер один |
📖 Справочники API в этой главе:net (247 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки