Chapter Orientation
The event port (xnetport) is the network engine's heart: five platform backends (IOCP/epoll/kqueue/io_uring/select) unified into one interface, xrtNetPortBackend queries the current backend, and Capabilities declares the capability set. Two programming shapes (previewed in Chapter 63): readiness (Watch readable/writable notifications — epoll/select/kqueue) and completion (Connect/Accept/Recv/Send delivering results directly — IOCP/io_uring); plus control events (Post custom events, Wake, Cancel of in-flight operations — the three doors that link with schedulers and channels). Most business code uses the port indirectly through the Stream layer (Chapter 67) — this chapter is the reference layer for understanding engine internals and for using the port directly.
64.1引入
Each of the five platforms has its own native event mechanism: Windows IOCP (completion ports — the completion model), Linux epoll (readiness notifications) and io_uring (completion queues — completion), macOS kqueue (readiness), and the cross-platform select fallback. Writing five bare adapters is a network library's classic workload — and the capability differences (IOCP has no readiness, select has no completion) make "one codebase, five platforms" require capability branching.
The XRT port's answer: one interface + capability declaration — the interface covers all operations of both shapes (Watch/Unwatch for readiness; the Connect/Accept/Recv/Send family for completion), Capabilities (returning bit flags) queries what the current backend supports, and cross-platform code branches on capabilities or moves up to the Stream layer (which branches internally already). The port_tour example's output shows it directly: on Windows iocp=completion select=readiness — two ports, one shape each; on Linux epoll and uring fall on either side — platform differences turn from "traps" into "declarations".
64.2概念
The full landscape of port operations
| Family | Entry points | Shape | Notes |
|---|---|---|---|
| Introspection | Backend/Capabilities/GetConfig | — | Backend and capability queries |
| readiness | Watch/Unwatch | Readable/writable notifications | Callback when an fd is ready |
| Stream completion | Connect/Accept/ReadProbe/Recv/RecvVec/Send/SendVec | completion | Results delivered directly |
| Datagram completion | RecvFromVec/RecvMsg(Vec)/SendToVec/SendMsgVec | completion | UDP, message-oriented |
| Control | Post/Wake/Cancel/RecvError | — | Custom events / wake / cancel / error pickup |
The three control doors' uses: Post delivers USER events (external logic injecting into the event stream — timer expiry, configuration changes); Wake wakes a waiting port (thread-safe — the landing point of Chapter 61's skeleton Wake channel); Cancel cancels an in-flight operation (ending in CANCELLED — the port dialect of Chapter 54's cancellation tree).
readiness vs completion in depth
The most notable difference between the shapes is buffer ownership: under readiness the buffer is yours (when ready, you fill/read it); under completion the buffer is frozen from submission to completion (the same discipline as Chapter 48's async files — the port edition). On Windows, IOCP is completion-only — Watch is unavailable; select is readiness-only — submitted operations are unavailable; on Linux, epoll and uring sit on opposite sides. The pattern for using the port directly across platforms: capability branching (if Capabilities.completion → submit style, else → Watch style) or moving uniformly up to the Stream layer.
Event types and waiting
The port's waits return events (structs carrying a type + associated data): READABLE/WRITABLE (readiness), CONNECT completed / ACCEPT arrived / read-write results (per completion operation), USER (Post), WAKE (Wake), ERROR (platform errors like ICMP unreachable — RecvError gated by platform capability). Waiting forms: blocking wait (next event) and timeout wait (the engine pump's cadence — the underlayer of Chapter 61's PollFor). The event-consumption loop is exactly the skeleton of the engine Worker's main loop — and to schedule small tasks directly onto this loop, Chapter 61's network task groups (task_net) are that bridge.
The port and the Stream
Chapter 67's Stream sits on top of the port: the Stream's callbacks (Accept/Read/Close) are driven by port events; the Stream internally does dual-shape adaptation (completion backends submit directly; readiness backends read/write after readiness) + the state machine (Chapter 67's four states) + reference counting. The layer-selection criterion: need fine control (custom protocol foundation, UDP batch send/receive — the udp_batch example uses the port directly) → this chapter's port; standard connection handling → the Stream layer. The five examples port_epoll/port_iocp/port_kqueue/port_select/port_uring each demonstrate one backend used directly.
64.3示例
Complete program: dual shape and capability declaration
From the repository example examples/network/port_tour/main.c — two ports demonstrating one shape each:
*/ #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; }
What just happened. (1) The direct testimony of Backend/Capabilities: iocp=completion select=readiness — on Windows, one shape per port (capability declaration prevents assumptions). (2) The readiness route: Watch registers → event READABLE → Unwatch deregisters — three steps of a readiness observation. (3) The completion route, whole family: TCP's Connect/Accept/ReadProbe/Recv/Send (with Vec vector variants — multiple buffers in one submission) and UDP's RecvFrom/SendTo family (datagram-oriented — one datagram per operation). (4) The three control doors verified: an in-flight receive Cancel ends in CANCELLED (Chapter 54's dialect); Post/Wake's custom and wake events arrive. (5) recv-error gated by platform — RecvError's platform capability gate (calls outside the capability are refused rather than crashing — declarative design's enforcement).
Complete program: using a single backend directly
From examples/network/port_select/main.c — direct use of a readiness backend:
#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; }
What just happened. (1) The single-backend example focuses on the readiness loop: Watch → wait for event → handle → deregister — the minimal port-usage skeleton. (2) select's value is fallback (works on any platform) — the landing point for development debugging (environments without a dedicated backend) and teaching; production prefers IOCP/epoll/uring (engine auto-selection). (3) The five port_X examples (epoll/iocp/kqueue/select/uring) share one structure — the same calls across five backends is the living proof of the unified interface's value: switching backends costs zero code changes (or only a creation parameter).
64.4契约
- Capability declaration: before using the port directly, query Capabilities — never assume both shapes; calls outside capabilities are refused (declarative enforcement).
- Buffer freeze: from submission to completion under completion, the buffer must not be touched (the port edition of Chapter 48's discipline).
- The three control doors: Post injects USER / Wake wakes cross-thread / Cancel cancels in-flight — mapping one-to-one onto the skeleton's three (offload injection / pump wake / cancellation tree).
- Vec variants: RecvVec/SendVec submit multiple buffers at once — the amortizing entry of batched IO (the IO edition of Chapter 21's batching idea).
- Layer choice: standard connections move up to the Stream (Chapter 67); custom foundations / UDP batching use the port directly.
- Event loop: the port's wait is the engine Worker's main loop — event consumption is the skeleton pump's underlying form.
Common Mistakes
Symptoms: the call is refused ("no readiness capability") or looks fine at compile time and fails at runtime — there is no readiness notification on Windows IOCP.
Cause: assuming the backend has both shapes — IOCP is completion-only, select readiness-only; calling without checking capabilities.
xnetport* Port = xrtNetPortCreate(...); /* Windows defaults to IOCP */ xrtNetPortWatch(Port, Fd, EVENTS, Callback, Data); /* refused — no readiness capability */
uint32 Caps = xrtNetPortCapabilities(Port); /* bit flags */ if ( Caps & XNET_PORT_CAP_READINESS ) { xrtNetPortWatch(Port, Fd, EVENTS, Callback, Data); } else if ( Caps & XNET_PORT_CAP_COMPLETION ) { xrtNetPortRecv(Port, Fd, Buffer, Size); /* submit style — capability branching */ }
Symptoms: dirty data received or occasional completion errors — the same family as Chapter 48's async-file Pitfall 2; timing-dependent and hard to reproduce.
Cause: under the completion shape the buffer is pinned by the port from submission to completion — overwriting it early is a data race.
xrtNetPortRecv(Port, Fd, Buffer, Size); /* submit a receive */ memset(Buffer, 0, Size); /* zeroed immediately — the port is writing into it */
xrtNetPortRecv(Port, Fd, Buffer, Size); /* buffer frozen until the completion event arrives; to reuse early, switch to a buffer pool (Chapter 22) — each block bound to one IO */
Programming Exercises
Create a port → print Backend/Capabilities/GetConfig — run it on every platform you can reach and record the five-backend capability matrix.
A select port + Watch: listen on a UDP socket, recvfrom after readiness and echo back — the minimal complete loop of the readiness model (compare the port_select example).
Write the same TCP connection logic twice: a completion edition (IOCP/uring — submit style) and a readiness edition (select/epoll — ready style) — compare code shapes and buffer ownership, and produce a comparison report. Acceptance: both editions run on supporting backends; the buffer-freeze discipline is honored in the completion edition (pooled buffers); the report compares code volume and complexity of the two models.
Summary
| Topic | Quick reference |
|---|---|
| Five backends | IOCP/epoll/kqueue/select/uring — one interface, capability declaration |
| Dual shape | readiness (ready notification, you read/write) / completion (submit and await results); branch on Capabilities |
| Three control doors | Post USER / Wake wakes / Cancel → CANCELLED — the skeleton trio's landing points |
| Vec family | RecvVec/SendVec submit multiple buffers at once — batched IO |
| Buffer discipline | untouchable during the completion freeze — pool with one IO per block |
| Layer criterion | standard TCP → Stream layer; custom foundation / UDP batching → use the port directly |
| Platform examples | port_epoll/iocp/kqueue/select/uring — five isomorphic direct-use examples |
📖 API references in this chapter:net (247 entries)tcp (81 entries)
Click for full signatures, parameter constraints, return values and error notes