Ориентация главы

Том 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).

Расслоение движка одним взглядом

1Слой Enginexnetengine — пул Worker + жизненный цикл (Create/Start/Stop/Destroy)
2Слой событийного портаxnetport — пять бэкендов (IOCP/epoll/kqueue/io_uring/select) за одним интерфейсом
3Слой Stream/Listenerколбэковая модель соединений и прослушивания (золотой стандарт главы 67)
4Протокольный слойTCP/UDP/прокси/кадры (главы 67-70) + верхние TLS/HTTP (тома 8/9)

Ценность расслоения — свобода замены: единый интерфейс событийного порта позволяет движку автоматически выбирать лучший бэкенд (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:

examples/network/address/main.c源码 ↗
#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;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/network/address/main.c -lws2_32 -liphlpapiendpoint=[fe80::1%3]:8080family=6link-local=yes

Что только что произошло. (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 — обход списка двухстекового домена:

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;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/network/dns/main.c -lws2_32 -liphlpapi[::1]:443127.0.0.1:443

Что только что произошло. (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.

Типичные ошибки

Ловушка 1: из списка берётся только первый адрес:Ловушка 1: из списка берётся только первый адрес

Симптомы: ошибки соединений на машинах с поломанной конфигурацией 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 случается сам */
}
Ловушка 2: ручное заполнение sockaddr в обход модели адресов:Ловушка 2: ручное заполнение sockaddr в обход модели адресов

Симптомы: потерянные зоны 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

Реализуйте 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 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки