Ориентация главы
Главы 63–70 решили «как данные утекают»: адреса, порты, буферы, DNS, TCP, UDP, прокси. Эта глава добавляет вторую половину картины — увидеть себя: какие сетевые карты у этой машины, какие адреса висят на каждой, как называется петлевой интерфейс, каков предпочтительный адрес машины. Эта информация незаменима в четырёх сценариях: стартовые журналы, печатающие сетевое окружение; административные диагностические страницы с данными узла; выбор адреса привязки на многосетевых машинах; и идентификатор scope, который обязаны нести IPv6 link-local адреса. Их формы на нативных API печально фрагментированы — GUID адаптеров Windows, if_nametoindex POSIX, разные системы имён на платформах — XRT стягивает их в маленький модуль без зависимостей от движка: ни Socket, ни потоков, ни DNS; свободно сочетается с любым транспортным слоем.
71.1引入
Предположим, вы пишете стартовый поток распределённого сервиса. Эксплуатация требует, чтобы каждый стартовый журнал нёс «имя хоста + локальный адрес + аппаратный идентификатор», чтобы узел мгновенно находился среди тысяч строк; админ-эндпоинт /health показывает тот же набор; на многосетевой машине конфиг даёт имя интерфейса eth0, и вам нужен его индекс для IPv6-мультикаста; и та классическая ошибка — fe80::1 не соединяется, потому что link-local адрес полон только в форме fe80::1%3 (3 — индекс интерфейса).
По отдельности эти потребности несложны; вместе — межплатформенное болото: «имя» интерфейса Windows — GUID, отображаемое имя — «Ethernet»; каноническое имя Linux — eth0; петлевой интерфейс на двух сторонах называется совершенно по-разному. Аппаратные адреса на Windows берутся через GetAdaptersAddresses, на POSIX — через ioctl, и длина не обязательно 6 байт. Собственный сток стоит по ветке на каждую платформу, а модуль интерфейсов XRT уже сделал эту работу: один вызов даёт согласованную структуру-снимок, имя и индекс конвертируются в обе стороны, локальная тройка (адрес/аппаратура/имя хоста) — по три удобных формы на каждую.
71.2概念
Снимок интерфейсов: одно распределение, все виды заимствованы
xrtNetInterfaces создаёт согласованный снимок текущих интерфейсов и адресов системы: одно распределение XRT вмещает массив интерфейсов, массив адресов, имена и аппаратные адреса; после этого все поля — заимствованные представления.
Ценность снимка — согласованность: добавление/удаление интерфейсов в процессе перечисления не рассинхронизирует записи; цена — моментальный срез без подписки на изменения. Метаданные на интерфейс: IPv4Index/IPv6Index (Windows ведёт раздельно; POSIX обычно одинаково), битовые Flags, Mtu, виды Name (каноническое) и DisplayName (отображаемое), сырые байты HardwareAddress, массив Addresses — каждый адрес с PrefixLength (0..32 для IPv4, 0..128 для IPv6; XNET_INTERFACE_PREFIX_UNKNOWN, когда платформа не может сказать).
Две дисциплины использования: порядок интерфейсов и адресов решает ОС — не считайте порядок идентичностью; после освобождения виды умирают мгновенно — все чтения обязаны предшествовать Free.
Флаги: стабильное межплатформенное пересечение
Flags открывают только шесть флагов, стабильно наблюдаемых на всех платформах: UP (в сети), RUNNING (работает), LOOPBACK (петля), BROADCAST (широковещание), POINT_TO_POINT (точка-точка), MULTICAST (поддержка мультикаста). Флаг без надёжного источника на некоторой платформе остаётся невыставленным — невыставленный ≠ возможности нет, лишь платформа не видит. «Можно ли вступить в мультикаст-группу» читает MULTICAST; «петля ли это» — LOOPBACK; каждое — одно битовое И.
Имена и индексы: конвертация в обе стороны
У интерфейсов две системы идентичности. Индекс — ключ мира ядра (IPv6 scope и управление членством мультикаста используют его); имя — ключ мира людей (в конфигах пишут его):
xrtNetInterfaceIndex(名称, 族)возвращает индекс; нулевой индекс — не результат; промах сообщаетXERR_NOT_FOUND.xrtNetInterfaceName(索引, 族, 缓冲, 容量)выводит каноническое имя в обратную сторону, возвращая нужную длину; пустой буфер сначала меряет, недостаточный безопасно усекает, дополняет нулём и возвращает полную нужную длину — то же двухчастное соглашение, что у текстовых API главы 63.
Различия имён поглощаются модулем: на Windows каноническое имя — системное имя адаптера (вида GUID), отображаемое — псевдоним интерфейса; на POSIX оба — if_name. Index принимает и каноническое, и отображаемое имя; запрос с UNSPEC предпочитает IPv6-индекс, откатываясь на IPv4.
Локальная тройка: три уровня
Диагностический набор «имя хоста, локальный адрес, аппаратный адрес» имеет три уровня использования, и API делится по уровням:
| Уровень | Адрес | Аппаратура | Имя хоста | Сценарии |
|---|---|---|---|---|
| Структура/байты | xrtNetLocalAddress | xrtNetLocalHardware | xrtNetHostName | Продолжение в бинарной форме (соединения, сравнения) |
| Двухчастный текст | xrtNetLocalAddressText | xrtNetLocalHardwareText | — (HostName уже текст) | Фиксированные буферы, среды без распределений |
| String-удобство | xrtNetLocalAddressString | xrtNetLocalHardwareString | xrtNetHostNameString | Стартовые журналы и диагностические страницы одним махом |
Выбор xrtNetLocalAddress — детерминированный запрос предпочтения: последовательно предпочитаются UP, RUNNING, не-петля, не-link-local интерфейсы; при равенстве UNSPEC предпочитает IPv6. Он подходит стартовым журналам и отчётам узла, но не публичный исходящий адрес и не маршрут по умолчанию — для точного выбора обходите xrtNetInterfaces и решайте сами. Текст аппаратного адреса — компактный HEX в верхнем регистре без разделителей (MAC 6 байт — 12 символов); для двоеточий и иных форматов показа читайте сырые байты и форматируйте сами. Уровень String возвращает строки, освобождаемые xrtFree; проверка на пустоту и есть обработка ошибки (семантика запроса — ошибка потока не ставится).
Нежданный подарок: разбор IPv6 scope
С включённым модулем xrtNetAddrParse и xrtNetAddrParseEndpoint главы 63 принимают одновременно числовой scope и scope-имя интерфейса: разбираются и fe80::1%3, и fe80::1%eth0; можно и [fe80::1%eth0]:8080. Результат разбора хранит только стабильный числовой индекс интерфейса; текстовый вывод (xrtNetAddrText) сохраняет каноническую форму без системных запросов — имена меняются с конфигурацией системы, индекс — постоянно сравнимая идентичность.
71.3示例
Первая полная программа: перечисление интерфейсов и префиксов адресов
Следующая программа из examples/network/interface/main.c обходит все локальные интерфейсы, печатая имя, двухстековые индексы, MTU и префикс каждого адреса — разовая панорама для осмотра многосетевой машины:
#include <stdio.h> #include <xrt.h> */ /* 列出本机接口以及可用于绑定、Scope 和多播的地址元数据。 */ int main(void) { xnetinterfacelist List; size_t i; if ( !xrtNetInterfaces(&List) ) { return 1; } for ( i = 0; i < List.Count; i++ ) { const xnetinterface* pInterface = &List.Items[i]; size_t j; printf("%.*s (%.*s) index4=%u index6=%u mtu=%u\n", (int)pInterface->Name.Size, pInterface->Name.Data, (int)pInterface->DisplayName.Size, pInterface->DisplayName.Data, (unsigned int)pInterface->IPv4Index, (unsigned int)pInterface->IPv6Index, (unsigned int)pInterface->Mtu); for ( j = 0; j < pInterface->AddressCount; j++ ) { const xnetinterfaceaddress* pAddress = &pInterface->Addresses[j]; char sAddress[96]; if ( xrtNetAddrText( &pAddress->Address, sAddress, sizeof(sAddress) ) == XRT_NPOS ) { xrtNetInterfacesFree(&List); return 1; } printf(" %s/%u\n", sAddress, (unsigned int)pAddress->PrefixLength); } } xrtNetInterfacesFree(&List); return 0; }
Что только что произошло. Три пункта. (1) xrtNetInterfaces одним вызовом делает перечисление и снимок; List.Count и List.Items — все интерфейсы, без дескрипторов, которые надо закрывать по одному. (2) Имя и отображаемое имя интерфейса — xstrview, печать через %.*s с Size; адреса переиспользуют xrtNetAddrText главы 63 для текста, XRT_NPOS сигнализирует нехватку буфера. (3) PrefixLength приписывается прямо к адресу — строки в стиле CIDR 127.0.0.1/8, 192.168.1.10/24 — реальные префиксы из снимка. Вывод зависит от машины, поэтому ожидаемый вывод в заголовке примера — описание, а не фиксированный текст.
Вторая полная программа: круговорот имя-индекс и локальная тройка
Вторая программа из examples/network/interface_tour/main.c соединяет круговорот имя → индекс → имя и двухчастные запросы локального адреса, аппаратуры и имени хоста в один осмотр:
*/ #include <stdio.h> #include <string.h> #include <xrt.h> int main(void) { char sName[64]; char sText[64]; uint8 Hardware[16]; xnetaddr Address; uint32 iIndex; size_t iSize = 0; size_t iNeed = 0; int iResult = 1; /* ---- InterfaceIndex / InterfaceName:回环往返 ---- */ #if defined(_WIN32) || defined(_WIN64) iIndex = xrtNetInterfaceIndex("loopback4", XNET_FAMILY_IPV4); #elif defined(__APPLE__) iIndex = xrtNetInterfaceIndex("lo0", XNET_FAMILY_IPV4); #else iIndex = xrtNetInterfaceIndex("lo", XNET_FAMILY_IPV4); #endif if ( iIndex == 0u ) { /* Windows 命名不同:回环显示名 "Loopback Pseudo-Interface 1"。 */ iIndex = xrtNetInterfaceIndex( "Loopback Pseudo-Interface 1", XNET_FAMILY_IPV4); } if ( iIndex == 0u ) { goto Cleanup; /* 找不到回环:环境异常 */ } /* InterfaceName 两段式:查询 → 写入 → 回环索引反查一致。 */ iNeed = xrtNetInterfaceName(iIndex, XNET_FAMILY_IPV4, NULL, 0u); if ( (iNeed == 0u) || (iNeed >= sizeof(sName)) ) { goto Cleanup; } iSize = xrtNetInterfaceName(iIndex, XNET_FAMILY_IPV4, sName, sizeof(sName)); if ( (iSize == 0u) || (iSize != iNeed) ) { goto Cleanup; } /* 名字反查索引一致(roundtrip)。 */ if ( xrtNetInterfaceIndex(sName, XNET_FAMILY_IPV4) != iIndex ) { /* 显示名与规范名可能不同:跳过反查但记录。 */ printf("iface: (display-name index differs) "); } printf("iface: loopback index!=0 name=[%s] ok\n", sName); /* ---- LocalAddress / Text ---- */ if ( !xrtNetLocalAddress(&Address, XNET_FAMILY_IPV4) || (Address.Family != XNET_FAMILY_IPV4) || (Address.Port != 0u) ) { goto Cleanup; } iNeed = xrtNetLocalAddressText(XNET_FAMILY_IPV4, NULL, 0u); if ( (iNeed == 0u) || (iNeed >= sizeof(sText)) ) { goto Cleanup; } iSize = xrtNetLocalAddressText(XNET_FAMILY_IPV4, sText, sizeof(sText)); if ( (iSize == 0u) || (iSize != iNeed) || (strchr(sText, '.') == NULL) ) { goto Cleanup; /* IPv4 文本必含点 */ } printf("iface: local addr=[%s]", sText); /* ---- LocalHardware / Text:MAC 至少 6 字节。 ---- */ iNeed = xrtNetLocalHardware(NULL, 0u); if ( (iNeed < 6u) || (iNeed > sizeof(Hardware)) ) { goto Cleanup; } iSize = xrtNetLocalHardware(Hardware, sizeof(Hardware)); if ( iSize != iNeed ) { goto Cleanup; } iNeed = xrtNetLocalHardwareText(NULL, 0u); if ( (iNeed < 12u) || (iNeed >= sizeof(sText)) ) { goto Cleanup; /* 6 字节 → 12 个 HEX 字符 */ } iSize = xrtNetLocalHardwareText(sText, sizeof(sText)); if ( (iSize != iNeed) ) { goto Cleanup; } printf(" hw=%s", sText); /* ---- HostName:非空且可写入。 ---- */ iNeed = xrtNetHostName(NULL, 0u); if ( (iNeed == 0u) || (iNeed >= sizeof(sName)) ) { goto Cleanup; } iSize = xrtNetHostName(sName, sizeof(sName)); if ( (iSize == 0u) || (iSize != iNeed) ) { goto Cleanup; } printf(" host=[%s] ok\n", sName); iResult = 0; Cleanup: return iResult; }
Что только что произошло. (1) Петлевой интерфейс сначала пробуется под POSIX-именем loopback4, при неудаче — под отображаемым именем Windows Loopback Pseudo-Interface 1 — стандартная запись «межплатформенная разность имён поглощается одним откатом на стороне вызова». (2) Двухчастность InterfaceName: сначала NULL/0 меряет нужную длину, затем запись и сверка двух возвратов; получив каноническое имя, обратный запрос индекса делает контроль круговорота (платформы, где отображаемое и каноническое имена различаются, могут пропустить обратный запрос). (3) Локальная тройка проверяется по очереди: LocalAddress со структурой-выходом и проверкой семейства и нулевого порта, LocalAddressText с выходом, обязательно содержащим точку (текстовый признак IPv4), LocalHardware с сырыми байтами не меньше 6, LocalHardwareText ровно вдвое больше байт HEX-символами, HostName непуст и записываем. Любой провал идёт через единый Cleanup с ненулевым возвратом — «осмотр», записанный как цепочка «упал — дальше не идём», читается куда лучше россыпи if.
Третья полная программа: диагностическая страница одним взглядом (уровень String)
Третья программа из examples/network/local_info/main.c собирает стартовую тройку одним махом через уровень String, с текстом-заглушкой при отсутствии аппаратного адреса:
#include <stdio.h> #include <xrt.h> */ /* 一次读取适合启动日志和诊断页展示的本机网络信息。 */ int main(void) { str sAddress = xrtNetLocalAddressString(XNET_FAMILY_UNSPEC); str sHost = xrtNetHostNameString(); str sHardware = xrtNetLocalHardwareString(); if ( (sAddress == NULL) || (sHost == NULL) ) { xrtFree(sAddress); xrtFree(sHost); xrtFree(sHardware); return 1; } printf("host = %s\n", sHost); printf("address = %s\n", sAddress); printf("hardware = %s\n", sHardware != NULL ? sHardware : "unavailable"); xrtFree(sHardware); xrtFree(sHost); xrtFree(sAddress); return 0; }
Что только что произошло. Контракт уровня String проявляется здесь: возврат str (освобождение xrtFree), NULL означает недоступность без установки ошибки потока — проверка на пустоту и есть вся обработка ошибок. Отсутствие адреса или имени хоста считается аномалией среды с возвратом 1, тогда как аппаратный адрес в чисто петлевой среде (контейнеры, VM без карт) может законно отсутствовать — печатается unavailable, и работа продолжается. Освобождение трёх строк собрано в конце — даже промежуточная неудача освобождает всё через xrtFree: xrtFree(NULL) — отсутствие операции, построчные проверки не нужны.
71.4契约
- Владение снимком:
Interfacesвладеет всем хранилищем одним распределением; виды полей только заимствуют;InterfacesFreeвозвращает и обнуляет структуру, вызываем повторно на пустом списке; неудача не меняет выход. - Порядок не гарантирован: порядок интерфейсов и адресов решает ОС и не может служить стабильной идентичностью; идентичность — индекс (число) и каноническое имя.
- Имя/индекс: нулевой индекс — не результат;
Indexпринимает каноническое и отображаемое имена,UNSPECпредпочитает IPv6-индекс;Nameследует двухчастному соглашению текстовых API (мерить → писать; нехватка безопасно усекается и возвращает полную нужную длину). - Границы уровня удобства:
LocalAddress— детерминированный запрос предпочтения (UP/RUNNING/не-петля/не-link-local, UNSPEC предпочитает IPv6) — не публичный исходящий, не маршрут по умолчанию, не замена политике прослушивания и адреса-источника; точный выбор — обходInterfaces. - Текстовый уровень: текст аппаратного адреса — компактный HEX верхнего регистра; уровень String возвращает
xrtFree-освобождаемые строки,NULLзначит недоступность, ошибка потока не ставится (семантика запроса). - Ошибки: неудачи сообщаются через
xrtGetError()—XERR_NOT_FOUND(интерфейс/адрес/аппаратура отсутствуют),XERR_IO(сбой системного перечисления),XERR_RANGE+XNET_ERROR_BUFFER(нехватка буфера), доменxrt.net. - Зависимости и обрезка:
XRT_MODULE_NET_INTERFACEзависит только от базового слоя сетевых адресов — без Socket, Engine, потоков, DNS и контейнеров;XRT_MODULE_NET_INTERFACE_TEXT— отдельный тонкий слой с HEX-кодированием.
Типичные ошибки
Симптомы: сервис недостижим в многосетевых/NAT-средах — «локальный адрес» в журнале принадлежит какому-то интрасетевому интерфейсу, а не адресу, достижимому пиром; или после привязки к нему сервис недоступен извне контейнера.
Причина: xrtNetLocalAddress — выбор предпочтения (предпочитает онлайнные, работающие, не-петлевые, не-link-local unicast-адреса); он не знает таблицу маршрутизации и тем более публичный исходящий за NAT. Использовать его как «внешне достижимый адрес» или единственный адрес привязки — значит принять диагностическую информацию за решение о маршрутизации.
xnetaddr Addr; xrtNetLocalAddress(&Addr, XNET_FAMILY_UNSPEC); /* напрямую регистрируем Addr как «адрес, по которому пир ко мне подключится» */ register_service(xrtNetAddrText(&Addr, ...));
многосетевая привязка — обход снимка и собственное решение */ xnetinterfacelist List; if ( xrtNetInterfaces(&List) ) { for ( size_t i = 0; i < List.Count; i++ ) { const xnetinterface* pIf = &List.Items[i]; if ( (pIf->Flags & XNET_INTERFACE_UP) == 0 ) { continue; } /* отбор по бизнес-правилам: семейство, префикс, мультикаст... */ } xrtNetInterfacesFree(&List); }
Симптомы: редкие падения или мусорные байты — обычно в коде «кэширующем данные интерфейсов».
Причина: Items[i].Name.Data, Addresses[j], HardwareAddress.Data — всё указывает во внутреннее хранилище снимка; после xrtNetInterfacesFree все они повисают. Снимок — моментальный срез со семантикой значения, а не долго удерживаемый объект.
xnetinterfacelist List; xrtNetInterfaces(&List); const xstrview Name = List.Items[0].Name; /* заимствованный вид */ const xnetinterfaceaddress* Addrs = List.Items[0].Addresses; xrtNetInterfacesFree(&List); /* хранилище возвращено */ printf("%.*s\n", (int)Name.Size, Name.Data); /* повисшее чтение */
xnetinterfacelist List; if ( xrtNetInterfaces(&List) ) { for ( size_t i = 0; i < List.Count; i++ ) { const xnetinterface* pIf = &List.Items[i]; printf("%.*s\n", (int)pIf->Name.Size, pIf->Name.Data); /* информацию, которую надо сохранить, копируйте в собственное хранилище прямо сейчас */ } xrtNetInterfacesFree(&List); }
Симптомы: обрезанные буферы или неверные длины на платформах с 8-байтовыми EUI-64 или некоторыми виртуальными интерфейсами.
Причина: 6 байт MAC-48 — лишь «типичное значение»; контракт модуля прямо говорит, что LocalHardware возвращает требуемое число байт и не зашивает длину; Size вида HardwareAddress тоже решает система.
uint8 Mac[6]; if ( xrtNetLocalHardware(Mac, sizeof(Mac)) == 0 ) { /* предположение 6 байт: более длинные адреса молча усекаются */ }
size_t iNeed = xrtNetLocalHardware(NULL, 0u); /* сначала меряем */ if ( (iNeed == 0u) || (iNeed > 16u) ) { return 1; } uint8 Hardware[16]; size_t iSize = xrtNetLocalHardware(Hardware, iNeed); /* только iSize == iNeed — полный адрес; длина HEX-текста 2*iSize */
Практические задания
Перечислите снимок, найдите интерфейс с XNET_INTERFACE_LOOPBACK во Flags и напечатайте его имя, двухстековые индексы и все адреса. Критерий приёмки: по одному запуску на Windows и Linux (подойдёт и WSL), обе стороны корректно печатают данные петли; после освобождения снимка программа нормально завершается.
Напишите функцию uint32 pick_multicast_iface(void): обходя снимок, выберите интерфейс «в сети, с мультикастом, не петля, с максимальным MTU», верните его IPv6-индекс или 0 при отсутствии. Подсказка: проверяйте во Flags одновременно XNET_INTERFACE_UP и XNET_INTERFACE_MULTICAST; петлю исключайте по XNET_INTERFACE_LOOPBACK; не забудьте InterfacesFree перед возвратом.
Реализуйте print_diagnostics(void): одним махом печатайте имя хоста (HostName двухчастно), локальный адрес (LocalAddressText двухчастно, семейство UNSPEC), аппаратный адрес (LocalHardware сырыми байтами, самостоятельно форматируя в HEX верхнего регистра с двоеточиями, как 00:E0:4C:7D:7E:66), общее число интерфейсов и адресов (из снимка). Критерий приёмки: весь вывод без единой утечки (сверка со статистикой распределений главы 6); отсутствие аппаратного адреса печатает hardware: unavailable, а не падение; два возврата каждого двухчастного вызова сверены построчно.
Итоги
| Тема | Краткая справка |
|---|---|
| Снимок | Interfaces владеет всем одним распределением; виды заимствованы; InterfacesFree обнуляет и повторяем; порядок — не идентичность |
| Метаданные интерфейса | двухстековые индексы / Flags / Mtu / Name+DisplayName / HardwareAddress / Addresses+PrefixLength |
| Флаги | UP RUNNING LOOPBACK BROADCAST POINT_TO_POINT MULTICAST; невыставленный ≠ возможности нет |
| Имя↔индекс | InterfaceIndex (имя → индекс, 0 = неудача) / InterfaceName (индекс → имя, двухчастно); UNSPEC предпочитает IPv6 |
| Локальная тройка | адрес (запрос предпочтения, не публичный исходящий) / аппаратура (длина варьируется) / имя хоста (локальная метка, не DNS-имя) |
| Три уровня | выход-параметр (продолжить в бинарном) → Text двухчастный (без распределений) → String (журналы одним махом, xrtFree + проверка на пустоту) |
| Текст аппаратуры | HEX верхнего регистра без разделителей (6 байт = 12 символов); форматы показа — сами |
| IPv6 scope | с включённым модулем разбираются и fe80::1%3, и fe80::1%eth0; хранится стабильный числовой индекс |
| Ошибки | NOT_FOUND (интерфейс/адрес/аппаратура) / IO (перечисление) / RANGE+BUFFER (нехватка буфера), домен xrt.net |
| Обрезка | XRT_MODULE_NET_INTERFACE без зависимостей от движка; слой TEXT отдельно добавляет HEX |
📖 Справочники API в этой главе:net_interface (16 записей)net (247 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки