本章导读

事件端口(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 章)间接使用端口——本章是理解引擎 internals 与直用端口的参考层。

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 唤醒等待中的端口(跨线程安全——第 61 章骨架的 Wake 通道落点);Cancel 取消在途操作(以 CANCELLED 终结——第 54 章取消树的端口方言)。

readiness vs 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 不可达——RecvError 按平台能力门控)。等待形态:阻塞等(下一事件)、带超时等(引擎泵的节拍——第 61 章 PollFor 的底层)。事件消费循环就是引擎 Worker 的主循环骨架——要把小件任务直接排到这个循环上执行,第 61 章的网络任务组(task_net)就是那座桥。

端口与 Stream 的关系

第 67 章 Stream 在端口之上: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

刚才发生了什么。 ① Backend/Capabilities 的直接证言:iocp=completion select=readiness——Windows 上两个端口各一种形态(能力声明防止假设)。② readiness 路:Watch 注册 → 事件 READABLE → Unwatch 注销——三步完成一次就绪观察。③ completion 路全家族:TCP 的 Connect/Accept/ReadProbe/Recv/Send(含 Vec 向量变体——多缓冲一次提交)与 UDP 的 RecvFrom/SendTo 族(数据报面向报文——每个操作对应一个数据报)。④ 控制三门各验证:Cancel 在途接收以 CANCELLED 终结(第 54 章方言)、Post/Wake 的自定义与唤醒事件到达。⑤ 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 后端的就绪观察输出——跨平台兜底形态)

刚才发生了什么。 ① 单后端范例聚焦 readiness 循环:Watch → 等待事件 → 处理 → 注销——最简端口使用骨架。② select 的价值是兜底(任何平台可用)——开发调试(不支持专用后端的环境)与教学的落点;生产首选 IOCP/epoll/uring(引擎自动选择)。③ 五个 port_X 范例(epoll/iocp/kqueue/select/uring)结构相同——同一套调用跨五后端是统一接口的价值实证:换后端零改代码(或只改创建参数)。

64.4契约

  • 能力声明:直用端口先查 Capabilities——不假设双形态;能力外调用被拒绝(声明式执法)。
  • 缓冲冻结:completion 提交到完成期间缓冲不可触碰(第 48 章纪律的端口版)。
  • 控制三门:Post 注入 USER/Wake 跨线程唤醒/Cancel 取消在途——与骨架三件(外挂注入/泵唤醒/取消树)一一对应。
  • Vec 变体:RecvVec/SendVec 多缓冲一次提交——批量 IO 的摊还入口(第 21 章批量思想的 IO 版)。
  • 层选择:标准连接上移 Stream(第 67 章);自定义底座/UDP 批量直用端口。
  • 事件循环:端口等待即引擎 Worker 主循环——事件消费是骨架泵的底层形态。

常见错误

坑 1:completion 后端上用 Watch

症状:调用被拒("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:提交后动缓冲

症状:收到脏数据或偶发完成错误——与第 48 章文件异步坑 2 同族;时序依赖难复现。

原因:completion 形态下缓冲在提交到完成期间被端口锁定使用——提前复写就是数据竞争。

错误写法
xrtNetPortRecv(Port, Fd, Buffer, Size);   /* 提交接收 */
memset(Buffer, 0, Size);                    /* 立刻清——端口正在往里写 */
正确写法
xrtNetPortRecv(Port, Fd, Buffer, Size);
/* 缓冲冻结直到完成事件到达;要提前复用换缓冲池(第 22 章)——每块绑一次 IO */

编程练习

基础:能力报告器

创建端口 → 打印 Backend/Capabilities/GetConfig——在你能接触的每个平台跑一遍,记录五后端能力矩阵。

进阶:readiness 回声

select 端口 + Watch:监听一个 UDP socket,收到就绪后 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——骨架三件的落点
Vec 族RecvVec/SendVec 多缓冲一次提交——批量 IO
缓冲纪律completion 冻结期不可碰——池化每块绑一次
层判据标准 TCP → Stream 层;自定义底座/UDP 批量 → 直用端口
平台范例port_epoll/iocp/kqueue/select/uring 五范例同构直用

📖 本章涉及的 API 参考手册:net(247 个条目)tcp(81 个条目)

点击查看完整签名、参数约束、返回值与错误说明