Ориентация главы
Том 7 открывается двумя вещами: обзором сетевого движка (xnetengine — архитектурный вид с высоты птичьего полёта событийного движка с пятью бэкендами, индустриальный носитель скелета главы 61) и моделью адресов (xnetaddr — унифицированная абстракция адресов IPv4/IPv6: разбор, сериализация конечных точек, классификация, zone-ID, круговорот sockaddr). Адреса — фундамент всего тома: TCP/UDP/DNS/прокси все используют xnetaddr как валюту; обзор движка говорит каждой следующей главе, на каком слое движка она живёт.
63.1引入
Первый порог сетевого программирования — не socket, а адрес: строка вроде "[fe80::1%3]:8080" — IPv6 требует скобок, %3 — zone-ID (адреса link-local обязаны указывать, на какой сетевой карте висят — Windows использует числа, Linux — имена интерфейсов), представления на платформах различаются. Ручное заполнение и разбор sockaddr_in6 — кошмар инициации каждого сетевого программиста и причина номер один неудачных миграций IPv6. xnetaddr стягивает всё это: один тип вмещает IPv4/IPv6 + порт + зону, разбор и сериализация одинаковы на всех платформах, классификационные проверки (loopback/private/multicast/link-local) — по одной на строку.
Вторая вещь — обзор движка: расслоение сетевого движка XRT (Engine → событийный порт → Stream/Listener → протокольный слой) и пять бэкендов (IOCP/epoll/kqueue/io_uring/select). Скелет главы 61 говорил о «насосе планировщика + выносах» — эта глава разворачивает нижний слой насоса как событийный порт (глава 64), а Stream — как engine-форму «корутины соединения» скелета (золотой стандарт главы 67).
63.2概念
Четыре части модели адресов
| Операция | Входы | Примечания |
|---|---|---|
| Разбор | xrtNetAddrParse / ParseEndpoint | Текст → адрес; форма конечной точки несёт порт |
| Сериализация | xrtNetAddrString / EndpointString | Адрес → текст; конечные точки получают скобки автоматически |
| Классификация | IsLoopback/IsPrivate/IsMulticast/IsLinkLocal/... | По одной проверке на каждый класс |
| Нативный круговорот | xrtNetAddrToNative / FromNative | Преобразование в/из sockaddr |
Zone-ID — твёрдый орех модели адресов: адреса link-local (fe80::) обязаны указывать сетевую карту, иначе один и тот же адрес двусмысленен на разных картах — разбор сохраняет зону, сериализация пишет её обратно дословно ([fe80::1%3]:8080), IsLinkLocal зонно-чувствителен — межплатформенные различия стянуты. Семантика порта 0: прослушивание с 0 предоставляет системе назначить порт, а xrtNetListenerLocal возвращает реальный (стандартная поза золотого стандарта главы 67).
Расслоение движка одним взглядом
Ценность расслоения — свобода замены: единый интерфейс событийного порта позволяет движку автоматически выбирать лучший бэкенд (Windows IOCP, Linux epoll/io_uring, macOS kqueue, select-подстраховка); бизнес видит только колбэковую модель Stream — нулевая осведомлённость о смене бэкенда. Это продолжает идею сборки главы 61 о «компоненты скелета заменяемы поодиночке».
Двойственность readiness и completion
У событийного порта две модели программирования (разворачивается в главе 63): readiness (уведомление «читаемо/записываемо» — вы идёте читать; естественная форма epoll/select/kqueue) и completion («дочитано» — результат вручается вам; естественная форма IOCP/io_uring). XRT принимает обе в один интерфейс — возможности объявляются по бэкенду (xrtNetPortCapabilities): Windows IOCP — только completion, Linux epoll — только readiness — межплатформенный сетевой код обязан ветвиться по возможностям или подниматься на слой Stream (он сглаживает различие).
happy-eyeballs: как потребляются списки адресов
xrtNetResolve возвращает список адресов (один домен — много адресов: двухстековость/многосетевые карты/балансировка) — стратегии соединения пробуют по порядку (IPv6 вперёд, откат на IPv4 — happy-eyeballs). Списковая модель с обходом xrtNetAddrListCount/Get — стандартная форма потребления; одноадресный удобный вход — первосортный выбор списка. Асинхронная версия резолвера (форма Future) — в примере resolver_future — слот DNS-выноса скелета главы 61.
63.3示例
Полная программа: разбор конечной точки и классификация
Из примера репозитория examples/network/address/main.c — полная обработка zone-ID:
#include <stdio.h> #include <xrt.h> */ /* 展示数字地址、端点、分类和拥有文本的常用路径。 */ int main(void) { xnetaddr Addr; str sEndpoint; if ( !xrtNetAddrParseEndpoint(&Addr, "[fe80::1%3]:8080", 0) ) { return 1; } sEndpoint = xrtNetAddrEndpointString(&Addr); if ( sEndpoint == NULL ) { return 1; } printf("endpoint=%s\nfamily=%u\nlink-local=%s\n", sEndpoint, (unsigned int)Addr.Family, xrtNetAddrIsLinkLocal(&Addr) ? "yes" : "no"); xrtFree(sEndpoint); return 0; }
Что только что произошло. (1) xrtNetAddrParseEndpoint принимает [fe80::1%3]:8080 — IPv6 в скобках + zone-ID %3 + порт, всё разбирается одним вызовом. (2) xrtNetAddrEndpointString сериализует обратно дословно (скобки и зона дополняются автоматически — круговоротная согласованность). (3) family=6 — запрос семейства адресов (IPv6); (4) IsLinkLocal даёт зонно-чувственный вердикт — полный замкнутый цикл link-local + zone-ID есть доказательство стягивания моделью межплатформенных различий. Пример addr_tour — тур по всем интерфейсам: равенство/сравнение, всё семейство классификации, разметка IPv6-mapped, sockaddr-круговорот — по одному набору ok.
Полная программа: разбор списка адресов
Из examples/network/dns/main.c — обход списка двухстекового домена:
#include <stdio.h> #include <xrt.h> */ /* 展示完整地址列表与单地址便捷入口。 */ int main(void) { xnetaddrlist* pList = xrtNetResolve( "localhost", 443, XNET_FAMILY_UNSPEC ); if ( pList == NULL ) { return 1; } for ( size_t i = 0; i < xrtNetAddrListCount(pList); i++ ) { str sEndpoint = xrtNetAddrEndpointString( xrtNetAddrListGet(pList, i) ); if ( sEndpoint == NULL ) { xrtNetAddrListDestroy(pList); return 1; } printf("%s\n", sEndpoint); xrtFree(sEndpoint); } xrtNetAddrListDestroy(pList); return 0; }
Что только что произошло. (1) xrtNetResolve("localhost", 443, UNSPEC) — домен + порт + семейство не ограничено → список адресов (владеющий — освобождается xrtNetAddrListDestroy). (2) Обход ListCount/ListGet — localhost двухстековый (::1 и 127.0.0.1, порядок может смениться); каждая запись сериализуется и печатается EndpointString. (3) Списковая модель — ровно вход happy-eyeballs: соединяющийся пробует по порядку, пока одно не удастся — не предположение «первый адрес всегда работает». Пример принадлежит модулю net (не отдельный DNS-модуль) — глава 66 разворачивает асинхронный резолвер.
63.4契约
- Унификация типа: один
xnetaddrвмещает IPv4/IPv6 + порт + зону; API всего тома используют его как валюту. - Верность зоны: разбор сохраняет зону, сериализация пишет её обратно дословно; проверки link-local зонно-чувствительны.
- Порт 0: прослушивание с 0 назначает система, ListenerLocal возвращает — стандартная поза для тестов конкурентности (глава 67).
- Списочная семантика: разбор возвращает список; потребляйте пробой по порядку (happy-eyeballs), никогда не предполагая один адрес.
- Расслоение движка: Engine → порт → Stream → протокол; бизнес живёт на слое колбэков Stream, бэкенды выбираются автоматически.
- Объявление двойственности: readiness/completion по возможностям бэкенда; межплатформенный прямой код сначала проверяет Capabilities.
От примеров к инженерии: три хозяина модели адресов
Разбор конфигурации: "0.0.0.0:8080" и "[::]:443" в конфигах сервисов — ParseEndpoint — фиксированное звено загрузки конфигурации (конвейер сборки конфигурации главы 43 цепляется сюда); тестовым средам с портом 0 реальный порт возвращает и reports ListenerLocal. Инициирование соединений: resolve в список → happy-eyeballs пробует по порядку — фиксированное начало главы TCP-соединений главы 67; неудавшиеся адреса пишутся в журнал (какой стек, какая ошибка) для сетевой диагностики. Инструменты сетевой диагностики: семейство классификации + три формы сериализации — пример iface и перечисление интерфейсов net-misc главы тома построены на ней. Три хозяина делят одно: текстовая форма адреса появляется только на границах (конфиг/журнал/командная строка); внутри — всегда xnetaddr, передаваемый по значению — преобразования текст↔адрес заперты в двух граничных точках (Parse на входе, String на выходе), ноль преобразований посередине.
Отображение на скелет главы 61
Глава 61 говорила: поставь на скелет TCP/TLS/HTTP — получишь том 7 — обзор движка этой главы завершает первый кусок мозаики: насос планировщика → событийный порт (PollFor скелета ↔ интерфейс ожидания порта); корутина соединения → колбэки Stream (соединительная корутина xrtCoGo скелета ↔ таблица колбэков Accept/Read/Close Stream — выражение разное (прямолинейная корутина против таблицы колбэков), но ответственность та же); пул Worker → Engine Workers (пул задач скелета ↔ конфигурация Worker движка). Ценность отображения: пятипунктовый чек-лист скелета (насос без блокировок / ожидания отменяемы / финальные состояния тройные / жизненные циклы согласованы / наблюдение повсюду) применяется по пункту на каждом слое движка — Cancel порта главы 64, машина состояний Stream главы 67, остановка сервера главы 68 — всё это engine-версии исполнения чек-листа. Скелет — карта тома 7.
Инженерный чек-лист миграции IPv6
Модель адресов XRT делает готовность IPv6 (dual-stack ready) умолчанием, а не заплаткой — но путь в продакшн всё равно проходит чек-лист. Представление адресов: всё через xnetaddr (принуждение ловушки 2); шаблоны конфигурации слушают двухстеково через [::] (а не 0.0.0.0 в v4-only). Потребление списков: happy-eyeballs пробует по порядку (ловушка 1) — v6 вперёд, v4 откат. Zone-ID: у link-local адресов обязана быть зона — документация конфигурации фиксирует правила идентификации сетевых карт данной среды (числа/имена интерфейсов по платформе). Mapped-адреса: v4-клиент к двухстековому серверу может прийти как ::ffff:x.y.z.w — Unmap до журналирования и суждений (иначе v4-клиенты ошибочно считаются v6). Пройдя четыре пункта, v6 — не миграция, а переключатель фичи. Этот чек-лист соберут ещё раз в финале тома (глава net-misc) и в инженерных главах тома 12.
Типичные ошибки
Симптомы: ошибки соединений на машинах с поломанной конфигурацией IPv6 — первый адрес ::1, но v6-маршрутизация мертва; на другой машине снова работает — зависит от среды, трудно воспроизвести.
Причина: предположение «домен = один адрес» — список может содержать несколько в нестабильном порядке; проба только первого лишает шанса отката.
xnetaddrlist* List = xrtNetResolve(Host, Port, XNET_FAMILY_UNSPEC); xnetaddr First; xrtNetAddrListGet(List, 0, &First); /* берём только первый */ Connect(&First); /* v6 поломан — падает всё соединение */
xnetaddrlist* List = xrtNetResolve(Host, Port, XNET_FAMILY_UNSPEC); for ( size_t i = 0; i < xrtNetAddrListCount(List); ++i ) { xnetaddr Addr; xrtNetAddrListGet(List, i, &Addr); if ( TryConnect(&Addr) ) { break; } /* пробуем по порядку — откат на v4 случается сам */ }
Симптомы: потерянные зоны IPv6, перепутанный порядок байт, ifdefs повсюду — классическая тройка ручного заполнения sockaddr_in6.
Причина: обход xnetaddr ради прямой работы с нативными структурами — всё, что стянула модель, переделывается руками.
struct sockaddr_in6 Sin; memset(&Sin, 0, sizeof(Sin)); Sin.sin6_family = AF_INET6; Sin.sin6_port = htons(8080); inet_pton(AF_INET6, "fe80::1", &Sin.sin6_addr); Sin.sin6_scope_id = 3; /* зона вручную — платформенные различия все ваши */ bind(Fd, (struct sockaddr*)&Sin, sizeof(Sin));
xnetaddr Addr; xrtNetAddrParseEndpoint(&Addr, "[fe80::1%3]:8080", 0); uint8 Native[64]; /* хранилище нативного sockaddr (непрозрачный буфер) */ size_t iSize = 0; xrtNetAddrToNative(&Addr, Native, &iSize); /* нативный круговорот — зона/порядок байт на совести модели */ BindVia(Native, iSize);
Практические задания
Для пяти форм конечных точек (v4/v6/v6+zone/только адрес/с портом) сделайте разбор → сериализация → разбор снова — утверждайте равенство адресов двух разборов (закон круговоротной согласованности).
Постройте по одному адресу каждого класса (unspec/loopback/private/multicast/linklocal/mapped) и печатайте вердикты; для mapped-адреса (::ffff:1.2.3.4) убедитесь, что Unmap превращает его в v4.
Реализуйте addrinfo 工具 (инструмент адресной информации): на вход любая конечная точка, на выходе семейство/классификация/публичность и три формы сериализации (адрес/конечная точка/с зоной); отчёт по десяти реальным адресам (локальные карты/публичные DNS/зарезервированные мультикаст-диапазоны). Критерий приёмки: классификации совпадают с системным getaddrinfo; zone-ID сохранены; все семейства вердиктов проверены тестами.
Итоги
| Тема | Краткая справка |
|---|---|
| Четыре части | Parse/ParseEndpoint, String/EndpointString, семейство классификации, ToNative/FromNative |
| Zone-ID | обязателен для v6 link-local; разбор сохраняет, сериализация пишет обратно, проверки в курсе |
| Порт 0 | прослушивание получает системный порт + ListenerLocal возвращает его |
| Список | Resolve возвращает список; пробуйте по порядку (happy-eyeballs), не предполагая один адрес |
| Расслоение движка | Engine → порт → Stream → протокол; бизнес на слое Stream |
| Двойственность | readiness («читаемо») / completion («дочитано»); Capabilities объявляют, Stream сглаживает |
📖 Справочники API в этой главе:net (247 записей)tcp (81 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки