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

Событийный порт (xnetport) — сердце сетевого движка: пять платформенных бэкендов (IOCP/epoll/kqueue/io_uring/select) унифицированы в один интерфейс, xrtNetPortBackend запрашивает текущий бэкенд, Capabilities объявляет набор возможностей. Две формы программирования (анонс главы 63): readiness (уведомления Watch читаемо/записываемо — epoll/select/kqueue) и completion (Connect/Accept/Recv/Send отдают результат напрямую — IOCP/io_uring); плюс управляющие события (Post пользовательские события, Wake пробуждение, Cancel отмена операций в полёте — три двери, связывающие с планировщиками и каналами). Большинство бизнес-кода использует порт косвенно через слой Stream (глава 67) — эта глава — справочный слой для понимания внутренностей движка и прямого использования порта.

64.1引入

У каждой из пяти платформ свой нативный событийный механизм: Windows IOCP (порты завершения — модель completion), Linux epoll (уведомления готовности — readiness) и io_uring (очереди завершения — completion), macOS kqueue (readiness), кроссплатформенная подстраховка select. Писать пять голых адаптеров — классическая нагрузка сетевой библиотеки, а различия возможностей (у IOCP нет readiness, у select нет completion) заставляют «один код на пять платформ» ветвиться по возможностям.

Ответ порта XRT: единый интерфейс + объявление возможностей — интерфейс покрывает все операции обеих форм (Watch/Unwatch — readiness; семейство Connect/Accept/Recv/Send — completion), Capabilities (возвращает битовые флаги) запрашивает, что поддерживает текущий бэкенд, а межплатформенный код ветвится по возможностям или поднимается на слой Stream (который уже проделал ветвление внутри). Вывод примера port_tour показывает напрямую: на Windows iocp=completion select=readiness — два порта, по одной форме; на Linux epoll и uring по разные стороны — платформенные различия из «ловушек» превращаются в «объявления».

64.2概念

Полная панорама операций порта

СемействоВходыФормаПримечания
СамоописаниеBackend/Capabilities/GetConfig—Запросы бэкенда и возможностей
readinessWatch/Unwatchуведомления читаемости/записываемостиКолбэк, когда fd готов
Завершение потоковConnect/Accept/ReadProbe/Recv/RecvVec/Send/SendVeccompletionРезультат доставляется напрямую
Завершение датаграммRecvFromVec/RecvMsg(Vec)/SendToVec/SendMsgVeccompletionUDP, ориентировано на сообщения
УправлениеPost/Wake/Cancel/RecvError—пользовательские события / пробуждение / отмена / приём ошибок

Назначение трёх управляющих дверей: Post доставляет события USER (внешняя логика внедряется в поток событий — истечение таймера, смена конфигурации); Wake пробуждает ожидающий порт (межпоточно безопасно — точка приземления Wake-канала скелета главы 61); Cancel отменяет операцию в полёте (завершается CANCELLED — портовый диалект дерева отмен главы 54).

readiness против completion подробно

1readiness (epoll/select/kqueue)зарегистрировать интерес -> уведомление о готовности -> вы читаете/пишете
2completion (IOCP/io_uring)подать операцию + буфер -> результат доставляется при завершении
3Объявление возможностейзапрос Capabilities — не предполагайте у бэкенда обе формы

Самое примечательное различие форм — принадлежность буферов: при readiness буфер ваш (когда готово — вы заполняете/читаете); при completion буфер заморожен от подачи до завершения (та же дисциплина, что у асинхронных файлов главы 48 — портовая версия). На Windows IOCP только completion — Watch недоступен; select только readiness — подача операций недоступна; на Linux epoll и uring по разные стороны. Паттерн прямого межплатформенного использования порта: ветвление по возможностям (if Capabilities.completion → подача, else → Watch) или единый подъём на слой Stream.

Типы событий и ожидание

Ожидания порта возвращают события (структуры с типом + ассоциированными данными): READABLE/WRITABLE (readiness), завершение CONNECT / приход ACCEPT / результаты чтения-записи (по операциям completion), USER (Post), WAKE (Wake), ERROR (платформенные ошибки вроде ICMP unreachable — RecvError гейтится по возможностям платформы). Формы ожидания: блокирующее ожидание (следующее событие) и ожидание с тайм-аутом (такт насоса движка — нижний слой PollFor главы 61). Цикл потребления событий — точно скелет главного цикла Worker движка.

Порт и Stream

Stream главы 67 стоит над портом: колбэки Stream (Accept/Read/Close) управляются событиями порта; Stream внутри делает адаптацию двойственности (бэкенды completion подают напрямую; бэкенды readiness читают/пишут после готовности) + машина состояний (четыре состояния главы 67) + подсчёт ссылок. Критерий выбора слоя: нужен точный контроль (фундамент собственного протокола, пакетный приём/отправка UDP — пример udp_batch использует порт напрямую) → порт этой главы; стандартная обработка соединений → слой Stream. Пять примеров port_epoll/port_iocp/port_kqueue/port_select/port_uring демонстрируют по одному бэкенду в прямом использовании.

64.3示例

Полная программа: двойственность и объявление возможностей

Из примера репозитория examples/network/port_tour/main.c — два порта демонстрируют по форме:

examples/network/port_tour/main.c源码 ↗































 */

#include <stdio.h>
#include <string.h>
#include <xrt.h>

/* 端口事件暂存项与等待辅助共用的事件类型一致。 */
typedef xnetportevent xnetportexample;


 * 终态(如 CONNECT 与 ACCEPT),逐个等取时其余事件必须留存。 */
static xnetportexample g_Pending[16];
static size_t g_iPending = 0;

/* 等待一个匹配 (Type, Id) 的端口事件;成功返回 true 并写出事件。 */
static bool exampleWaitFor(xnetport* pPort, xnetporteventtype Type,
	uint64 Id, xnetportevent* pEvent, int64 iTimeoutMs)
{
	xnetportevent Events[8];
	size_t iCount = 0;

	for ( int i = 0; i < 100; i++ ) {
		/* 先消费先前批次中暂存的事件。 */
		for ( size_t j = 0; j < g_iPending; j++ ) {
			if ( (g_Pending[j].Type == Type) &&
				(g_Pending[j].Id == Id) ) {
				*pEvent = g_Pending[j];
				memmove(g_Pending + j, g_Pending + j + 1u,
					(g_iPending - j - 1u) *
						sizeof(g_Pending[0]));
				--g_iPending;
				return true;
			}
		}
		if ( xrtNetPortWait(pPort, Events, 8u,iTimeoutMs / 100u,
				&iCount) != XNET_RESULT_OK ) {
			continue;
		}
		{
			

			 * 丢掉同批后续事件。 */
			const xnetportexample* pMatch = NULL;

			for ( size_t j = 0; j < iCount; j++ ) {
				if ( (pMatch == NULL) &&
					(Events[j].Type == Type) &&
					(Events[j].Id == Id) ) {
					pMatch = &Events[j];
				} else if ( g_iPending < 16u ) {
					g_Pending[g_iPending++] = Events[j];
				}
			}
			if ( pMatch != NULL ) {
				*pEvent = *pMatch;
				return true;
			}
		}
	}
	return false;
}

int main(void)
{
	xnetportconfig Config;
	xnetport* pIocp = NULL;
	xnetport* pSelect = NULL;
	xnetsocket UdpA = 0;
	xnetsocket UdpB = 0;
	xnetsocket Listener = 0;
	xnetsocket Client = 0;
	xnetsocket Server = 0;
	xnetaddr AddrUdpA;
	xnetaddr AddrUdpB;
	xnetaddr DestUdpA;
	xnetaddr AddrListen;
	xnetportevent Event;
	xnetdgramcontrol Control;
	xnetspan Out[2];
	xnetwspan In[2];
	uint8 arrBuf[64];
	size_t iSent = 0;
	uint32 iCaps = 0;
	int iResult = 1;

	/* ---- 双后端创建与能力自省。 ---- */
	xrtNetPortConfigInit(&Config);
	Config.Backend = XNET_PORT_IOCP;
	pIocp = xrtNetPortCreate(&Config);
	xrtNetPortConfigInit(&Config);
	Config.Backend = XNET_PORT_SELECT;
	pSelect = xrtNetPortCreate(&Config);
	if ( (pIocp == NULL) || (pSelect == NULL) ||
		(xrtNetPortBackend(pIocp) != XNET_PORT_IOCP) ||
		(xrtNetPortBackend(pSelect) != XNET_PORT_SELECT) ) {
		goto Cleanup;
	}
	iCaps = xrtNetPortCapabilities(pIocp);
	if ( ((iCaps & XNET_PORT_CAP_COMPLETION) == 0u) ||
		((iCaps & XNET_PORT_CAP_READINESS) != 0u) ) {
		goto Cleanup;
	}
	iCaps = xrtNetPortCapabilities(pSelect);
	if ( ((iCaps & XNET_PORT_CAP_READINESS) == 0u) ||
		((iCaps & XNET_PORT_CAP_COMPLETION) != 0u) ) {
		goto Cleanup;
	}
	{
		xnetportconfig Resolved;

		if ( !xrtNetPortGetConfig(pIocp, &Resolved) ||
			(Resolved.Backend != XNET_PORT_IOCP) ) {
			goto Cleanup;
		}
	}
	/* 两个 UDP 套接字:A 收、B 发。 */
	UdpA = xrtNetSocketOpen(XNET_FAMILY_IPV4, XNET_SOCKET_DGRAM,
		XNET_SOCKET_NONBLOCK);
	UdpB = xrtNetSocketOpen(XNET_FAMILY_IPV4, XNET_SOCKET_DGRAM,
		XNET_SOCKET_NONBLOCK);
	if ( (UdpA == 0) || (UdpB == 0) ||
		!xrtNetAddrAny(&AddrUdpA, XNET_FAMILY_IPV4, 0u) ||
		!xrtNetSocketBind(UdpA, &AddrUdpA) ||
		!xrtNetSocketLocal(UdpA, &AddrUdpA) ||
		!xrtNetAddrAny(&AddrUdpB, XNET_FAMILY_IPV4, 0u) ||
		!xrtNetSocketBind(UdpB, &AddrUdpB) ||
		!xrtNetSocketLocal(UdpB, &AddrUdpB) ||
		!xrtNetAddrLoopback(&DestUdpA, XNET_FAMILY_IPV4,
			AddrUdpA.Port) ) {
		goto Cleanup;
	}
	printf("port: backends iocp=completion select=readiness ok\n");

	/* ---- readiness:SELECT 端口上 Watch → READY → Unwatch。 ---- */
	if ( !xrtNetPortWatch(pSelect, UdpA, 100u, XNET_PORT_EVENT_READ,
			NULL) ||
		(xrtNetSocketSendTo(UdpB, "r", 1u, &iSent, &DestUdpA) !=
			XNET_RESULT_OK) ||
		!exampleWaitFor(pSelect, XNET_PORT_EVENT_READY, 100u,
			&Event, INT64_C(2000)) ||
		!xrtNetPortUnwatch(pSelect, UdpA) ) {
		goto Cleanup;
	}
	/* 消费掉触发的数据报,保持后续段干净。 */
	(void)xrtNetSocketRecv(UdpA, arrBuf, sizeof(arrBuf), &iSent);
	printf("port: watch -> READY -> unwatch ok\n");

	/* ---- TCP:Connect + Accept + ReadProbe + Recv/Send。 ---- */
	Listener = xrtNetSocketOpen(XNET_FAMILY_IPV4, XNET_SOCKET_STREAM,
		XNET_SOCKET_NONBLOCK);
	Client = xrtNetSocketOpen(XNET_FAMILY_IPV4, XNET_SOCKET_STREAM,
		XNET_SOCKET_NONBLOCK);
	if ( (Listener == 0) || (Client == 0) ||
		!xrtNetAddrLoopback(&AddrListen, XNET_FAMILY_IPV4, 0u) ||
		!xrtNetSocketBind(Listener, &AddrListen) ||
		!xrtNetSocketListen(Listener, 1) ||
		!xrtNetSocketLocal(Listener, &AddrListen) ||
		!xrtNetPortConnect(pIocp, Client, &AddrListen, 201u, NULL) ||
		!xrtNetPortAccept(pIocp, Listener, 202u, NULL) ||
		!exampleWaitFor(pIocp, XNET_PORT_EVENT_CONNECT, 201u,
			&Event, INT64_C(2000)) ||
		(Event.Result != XNET_RESULT_OK) ||
		!exampleWaitFor(pIocp, XNET_PORT_EVENT_ACCEPT, 202u,
			&Event, INT64_C(2000)) ||
		(Event.Accepted == 0) ) {
		goto Cleanup;
	}
	Server = Event.Accepted;
	/* ReadProbe 等待可读,不借数据缓冲;终态后再提交 Recv。 */
	if ( !xrtNetPortReadProbe(pIocp, Server, 203u, NULL) ||
		(xrtNetSocketSend(Client, "hi", 2u, &iSent) !=
			XNET_RESULT_OK) ||
		!exampleWaitFor(pIocp, XNET_PORT_EVENT_READ_PROBE, 203u,
			&Event, INT64_C(2000)) ) {
		goto Cleanup;
	}
	if ( !xrtNetPortRecv(pIocp, Server, arrBuf, 8u, 204u, NULL) ||
		!exampleWaitFor(pIocp, XNET_PORT_EVENT_RECV, 204u,
			&Event, INT64_C(2000)) ||
		(Event.Bytes != 2u) ||
		(memcmp(arrBuf, "hi", 2u) != 0) ||
		!xrtNetPortSend(pIocp, Server, "ok", 2u, 205u, NULL) ||
		!exampleWaitFor(pIocp, XNET_PORT_EVENT_SEND, 205u,
			&Event, INT64_C(2000)) ) {
		goto Cleanup;
	}
	(void)xrtNetSocketRecv(Client, arrBuf, sizeof(arrBuf), &iSent);
	printf("port: tcp connect + accept + readprobe + recv/send ok\n");

	/* ---- 流向量:RecvVec 两段分散 + SendVec 两段聚集。 ---- */
	Out[0].Data = (cbytes)"v1";
	Out[0].Size = 2u;
	Out[1].Data = (cbytes)"v2";
	Out[1].Size = 2u;
	In[0].Data = arrBuf;
	In[0].Size = 2u;
	In[1].Data = arrBuf + 2;
	In[1].Size = 2u;
	if ( !xrtNetPortRecvVec(pIocp, Server, In, 2u, 210u, NULL) ||
		(xrtNetSocketSendVec(Client, Out, 2u, &iSent) !=
			XNET_RESULT_OK) ||
		!exampleWaitFor(pIocp, XNET_PORT_EVENT_RECV, 210u,
			&Event, INT64_C(2000)) ||
		(Event.Bytes != 4u) ||
		(memcmp(arrBuf, "v1v2", 4u) != 0) ||
		!xrtNetPortSendVec(pIocp, Server, Out, 2u, 211u, NULL) ||
		!exampleWaitFor(pIocp, XNET_PORT_EVENT_SEND, 211u,
			&Event, INT64_C(2000)) ) {
		goto Cleanup;
	}
	(void)xrtNetSocketRecv(Client, arrBuf, sizeof(arrBuf), &iSent);
	printf("port: stream vec recv/send ok\n");

	

	 * 未启用时提交被拒("metadata is not enabled")。 ---- */
	if ( !xrtNetSocketDgramMetaSet(UdpA, XNET_DGRAM_META_HOP_LIMIT) ) {
		goto Cleanup;
	}
	if ( !xrtNetPortRecvFromVec(pIocp, UdpA, In, 2u, 220u, NULL) ||
		(xrtNetSocketSendTo(UdpB, "d1", 2u, &iSent, &DestUdpA) !=
			XNET_RESULT_OK) ||
		!exampleWaitFor(pIocp, XNET_PORT_EVENT_RECV_FROM, 220u,
			&Event, INT64_C(2000)) ||
		(Event.Bytes != 2u) ||
		(memcmp(arrBuf, "d1", 2u) != 0) ||
		(Event.Address.Port != AddrUdpB.Port) ) {
		goto Cleanup;
	}
	if ( !xrtNetPortRecvMsg(pIocp, UdpA, arrBuf, 16u, 221u, NULL) ||
		(xrtNetSocketSendTo(UdpB, "d2", 2u, &iSent, &DestUdpA) !=
			XNET_RESULT_OK) ||
		!exampleWaitFor(pIocp, XNET_PORT_EVENT_RECV_MSG, 221u,
			&Event, INT64_C(2000)) ||
		(Event.Bytes != 2u) ||
		(memcmp(arrBuf, "d2", 2u) != 0) ||
		/* 终态事件携带已启用的元数据位。 */
		((Event.Meta.Flags & XNET_DGRAM_META_HOP_LIMIT) == 0u) ) {
		goto Cleanup;
	}
	if ( !xrtNetPortRecvMsgVec(pIocp, UdpA, In, 2u, 222u, NULL) ||
		(xrtNetSocketSendTo(UdpB, "d3", 2u, &iSent, &DestUdpA) !=
			XNET_RESULT_OK) ||
		!exampleWaitFor(pIocp, XNET_PORT_EVENT_RECV_MSG, 222u,
			&Event, INT64_C(2000)) ||
		(Event.Bytes != 2u) ||
		(memcmp(arrBuf, "d3", 2u) != 0) ) {
		goto Cleanup;
	}
	printf("port: dgram recvfromvec/recvmsg/recvmsgvec ok\n");

	

	 * 无远端地址为 SEND;仅非零 Flags 的控制发送为 SEND_MSG。 ---- */
	memset(&Control, 0, sizeof(Control));
	Out[0].Data = (cbytes)"t1";
	Out[0].Size = 2u;
	Out[1].Data = (cbytes)"t2";
	Out[1].Size = 2u;
	if ( !xrtNetPortSendToVec(pIocp, UdpB, Out, 2u, &DestUdpA,
			230u, NULL) ||
		!exampleWaitFor(pIocp, XNET_PORT_EVENT_SEND_TO, 230u,
			&Event, INT64_C(2000)) ||
		(Event.Result != XNET_RESULT_OK) ||
		!xrtNetPortSendMsgVec(pIocp, UdpB, Out, 2u, &DestUdpA,
			&Control, 231u, NULL) ||
		!exampleWaitFor(pIocp, XNET_PORT_EVENT_SEND_TO, 231u,
			&Event, INT64_C(2000)) ||
		(Event.Result != XNET_RESULT_OK) ) {
		goto Cleanup;
	}
	/* A 侧清掉两份发出的数据报。 */
	while ( xrtNetSocketRecv(UdpA, arrBuf, sizeof(arrBuf), &iSent) ==
		XNET_RESULT_OK ) {
	}
	printf("port: dgram sendtovec/sendmsgvec ok\n");

	/* ---- Cancel:提交一个等不到的 Recv 再取消 → CANCELLED。 ---- */
	if ( !xrtNetPortRecv(pIocp, UdpA, arrBuf, 8u, 600u, NULL) ||
		!xrtNetPortCancel(pIocp, 600u) ||
		!exampleWaitFor(pIocp, XNET_PORT_EVENT_RECV, 600u,
			&Event, INT64_C(2000)) ||
		(Event.Result != XNET_RESULT_CANCELLED) ) {
		goto Cleanup;
	}
	printf("port: cancel in-flight recv -> CANCELLED ok\n");

	/* ---- Post 与 Wake:USER 事件携带 Id,WAKE 事件可合并。 ---- */
	if ( !xrtNetPortPost(pIocp, 777u, NULL) ||
		!exampleWaitFor(pIocp, XNET_PORT_EVENT_USER, 777u,
			&Event, INT64_C(2000)) ||
		!xrtNetPortWake(pIocp) ||
		!exampleWaitFor(pIocp, XNET_PORT_EVENT_WAKE, 0u,
			&Event, INT64_C(2000)) ) {
		goto Cleanup;
	}
	printf("port: post USER + wake WAKE ok\n");

	
	 * 提交被拒并给出明确错误;Linux URING 上可受理。 ---- */
	if ( xrtNetPortRecvError(pIocp, UdpA, arrBuf, 16u, 300u, NULL) ) {
		goto Cleanup; /* 本平台不应受理。 */
	}
	printf("port: recv-error gated by platform ok\n");
	iResult = 0;

Cleanup:
	xrtNetSocketClose(Server);
	xrtNetSocketClose(Client);
	xrtNetSocketClose(Listener);
	xrtNetSocketClose(UdpB);
	xrtNetSocketClose(UdpA);
	if ( pSelect != NULL ) {
		xrtNetPortDestroy(pSelect);
	}
	if ( pIocp != NULL ) {
		xrtNetPortDestroy(pIocp);
	}
	return iResult;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/network/port_tour/main.c -lws2_32 -liphlpapiport: backends iocp=completion select=readiness okport: watch -> READY -> unwatch okport: tcp connect + accept + readprobe + recv/send okport: stream vec recv/send okport: dgram recvfromvec/recvmsg/recvmsgvec okport: dgram sendtovec/sendmsgvec okport: cancel in-flight recv -> CANCELLED okport: post USER + wake WAKE okport: recv-error gated by platform ok

Что только что произошло. (1) Прямое свидетельство Backend/Capabilities: iocp=completion select=readiness — на Windows по форме на порт (объявление возможностей предотвращает предположения). (2) Маршрут readiness: Watch регистрирует → событие READABLE → Unwatch снимает — три шага одного наблюдения готовности. (3) Маршрут completion, всё семейство: TCP Connect/Accept/ReadProbe/Recv/Send (с векторными вариантами Vec — несколько буферов одной подачей) и UDP-семейство RecvFrom/SendTo (датаграммы — одна датаграмма на операцию). (4) Три управляющие двери проверены: Cancel приёма в полёте завершается CANCELLED (диалект главы 54); события Post/Wake — пользовательское и пробуждение — приходят. (5) recv-error gated by platform — платформенный гейт возможностей RecvError (вызовы вне возможностей отвергаются, а не рушатся — принуждение декларативного дизайна).

Полная программа: прямое использование одного бэкенда

Из examples/network/port_select/main.c — прямое использование бэкенда readiness:

examples/network/port_select/main.c源码 ↗
#include <stdio.h>

#include <xrt.h>















 */


/* 展示 select fallback 的 one-shot 可读观察和显式重新观察模型。 */
int main(void)
{
	xnetportconfig Config;
	xnetportevent Event;
	xnetport* pPort = NULL;
	xnetsocket Server = NULL;
	xnetsocket Client = NULL;
	xnetaddr Address;
	char sData[16] = { 0 };
	size_t iSize;
	size_t iCount;
	int iResult = 1;

	xrtNetPortConfigInit(&Config);
	Config.Backend = XNET_PORT_SELECT;
	pPort = xrtNetPortCreate(&Config);
	Server = xrtNetSocketOpen(XNET_FAMILY_IPV4,
		XNET_SOCKET_DGRAM, XNET_SOCKET_NONBLOCK);
	Client = xrtNetSocketOpen(XNET_FAMILY_IPV4,
		XNET_SOCKET_DGRAM, XNET_SOCKET_NONBLOCK);
	if ( (pPort == NULL) || (Server == NULL) || (Client == NULL) ||
		 !xrtNetAddrLoopback(&Address, XNET_FAMILY_IPV4, 0) ||
		 !xrtNetSocketBind(Server, &Address) ||
		 !xrtNetSocketLocal(Server, &Address) ||
		 !xrtNetPortWatch(pPort, Server, 1, XNET_POLL_READ, NULL) ||
		 (xrtNetSocketSendTo(Client, "event", 5,
			&iSize, &Address) != XNET_RESULT_OK) ||
		 (xrtNetPortWait(pPort, &Event, 1,1000, &iCount) != XNET_RESULT_OK) ||
		 (iCount != 1) ||
		 ((Event.Flags & XNET_PORT_EVENT_READ) == 0) ||
		 (xrtNetSocketRecvFrom(Server, sData, sizeof(sData) - 1,
			&iSize, NULL) != XNET_RESULT_OK) ) {
		goto Cleanup;
	}

	sData[iSize] = 0;
	printf("backend=%s data=%s\n", xrtNetPortName(pPort), sData);
	iResult = 0;

Cleanup:
	if ( Client != NULL ) { (void)xrtNetSocketClose(Client); }
	if ( Server != NULL ) { (void)xrtNetSocketClose(Server); }
	if ( pPort != NULL ) { (void)xrtNetPortDestroy(pPort); }
	return iResult;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/network/port_select/main.c -lws2_32 -liphlpapi(select 后端的就绪观察输出——跨平台兜底形态)

Что только что произошло. (1) Пример одного бэкенда сфокусирован на цикле readiness: Watch → ожидание события → обработка → снятие — минимальный скелет использования порта. (2) Ценность select — подстраховка (работает на любой платформе) — точка приземления отладки разработки (среды без выделенного бэкенда) и обучения; продакшн предпочитает IOCP/epoll/uring (автовыбор движка). (3) Пять примеров port_X (epoll/iocp/kqueue/select/uring) одинаковы по структуре — одни и те же вызовы на пяти бэкендах — живое доказательство ценности единого интерфейса: смена бэкенда стоит нулёвых правок кода (или только параметра создания).

64.4契约

  • Объявление возможностей: перед прямым использованием порта запросите Capabilities — не предполагайте обе формы; вызовы вне возможностей отвергаются (декларативное принуждение).
  • Заморозка буферов: от подачи до завершения при completion буфер неприкосновенен (портовая версия дисциплины главы 48).
  • Три управляющие двери: Post внедряет USER / Wake пробуждает межпоточно / Cancel отменяет в полёте — взаимно однозначно тройке скелета (внедрение выноса / пробуждение насоса / дерево отмен).
  • Векторные варианты: RecvVec/SendVec подают несколько буферов сразу — амортизирующий вход пакетного IO (IO-версия идеи батчинга главы 21).
  • Выбор слоя: стандартные соединения поднимаются на Stream (глава 67); собственные фундаменты / пакетный UDP используют порт напрямую.
  • Событийный цикл: ожидание порта — главный цикл Worker движка — потребление событий есть нижняя форма насоса скелета — а чтобы планировать мелкие задачи прямо на этот цикл, есть мост: сетевые группы задач (task_net) главы 61.

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

Ловушка 1: Watch на бэкенде completion:Ловушка 1: Watch на бэкенде completion

Симптомы: вызов отвергнут («no readiness capability») или на компиляции выглядит нормальным, а в рантайме падает — на Windows IOCP нет уведомлений готовности.

Причина: предположение двойственности у бэкенда — IOCP только completion, select только readiness; вызов без проверки возможностей.

错误写法
xnetport* Port = xrtNetPortCreate(...);   /* Windows по умолчанию IOCP */
xrtNetPortWatch(Port, Fd, EVENTS, Callback, Data);   /* отвергнуто — нет возможности readiness */
正确写法
uint32 Caps = xrtNetPortCapabilities(Port);   /* битовые флаги */
if ( Caps & XNET_PORT_CAP_READINESS ) {
	xrtNetPortWatch(Port, Fd, EVENTS, Callback, Data);
} else if ( Caps & XNET_PORT_CAP_COMPLETION ) {
	xrtNetPortRecv(Port, Fd, Buffer, Size);   /* подача — ветвление по возможностям */
}
Ловушка 2: трогать буфер после подачи:Ловушка 2: трогать буфер после подачи

Симптомы: приходит грязь или редкие ошибки завершения — одно семейство с ловушкой 2 асинхронных файлов главы 48; зависит от таймингов, трудно воспроизвести.

Причина: при форме completion буфер закреплён портом от подачи до завершения — ранняя перезапись есть гонка данных.

错误写法
xrtNetPortRecv(Port, Fd, Buffer, Size);   /* подать приём */
memset(Buffer, 0, Size);                    /* сразу обнулить — порт в это время пишет внутрь */
正确写法
xrtNetPortRecv(Port, Fd, Buffer, Size);
/* буфер заморожен до прибытия события завершения; чтобы переиспользовать раньше — пул буферов (глава 22), блок за одним IO */

Практические задания

Базовое: репортер возможностей

Создайте порт → напечатайте Backend/Capabilities/GetConfig — прогоните на каждой доступной платформе и зафиксируйте матрицу возможностей пяти бэкендов.

Продвинутое: readiness-эхо

Порт select + Watch: слушайте UDP-сокет, после готовности recvfrom и отошлите обратно — минимальный полный контур модели readiness (сверьте с примером port_select).

Вызов: TCP-клиент в двух формах

Одну и ту же логику TCP-соединения напишите дважды: версия completion (IOCP/uring — подача) и версия readiness (select/epoll — готовность) — сравните формы кода и принадлежность буферов, соберите отчёт. Критерий приёмки: обе версии работают на поддерживающих бэкендах; дисциплина заморозки буферов соблюдена в версии completion (пул буферов); отчёт сравнивает объём кода и сложность двух моделей.

Итоги

ТемаКраткая справка
Пять бэкендовIOCP/epoll/kqueue/select/uring — один интерфейс, объявление возможностей
Двойственностьreadiness (уведомление готовности, вы читаете) / completion (подал и ждёшь результат); ветвление по Capabilities
Три управляющие двериPost USER / Wake пробуждает / Cancel → CANCELLED — точки приземления тройки скелета
Семейство VecRecvVec/SendVec подают несколько буферов сразу — пакетный IO
Дисциплина буферовв периоде заморозки completion неприкосновенны — пул, блок за одним IO
Критерий слоястандартный TCP → слой Stream; собственный фундамент / пакетный UDP → порт напрямую
Платформенные примерыport_epoll/iocp/kqueue/select/uring — пять изоморфных примеров прямого использования

📖 Справочники API в этой главе:net (247 записей)tcp (81 записей)

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