本章导读
TCP 下册讲三块进阶:发送四族(Vec 聚集 / Ref 零拷贝+释放回调 / Take 接管 / File 内核文件发送)与写预算(WriteLimit 限额 / Writable 可写 / Pending 在途——发送背压的三件套);读取与 Worker 约束(Buffer/Read/Consume 仅 Worker 内——直移缓冲的消费面;跨线程经 Post 投递);服务端形态(xnetserver——拉取 Accept(队列取连接)、Future Accept 与阻塞 Accept 双面并存、多监听与共享端口)。金标准章(第 67 章)的四态与回显是基础形态——本章是它的完全体。
68.1引入
三个进阶场景。场景一:文件服务器要把 1GB 文件发出去——read 整个文件到内存再 Send 是灾难(内存爆炸);xrtNetStreamSendFile(内核文件发送+区间有界——第 46 章文件章预告的 sendfile 落点)不经过用户态缓冲。场景二:代理要把上游收到的段链转发下游——复制一份再发是浪费;SendRef 直接引用移交(零拷贝+释放回调)。场景三:服务端要限制单个连接的发送积压(恶意客户端不读——缓冲无限膨胀);写预算(WriteLimit 设上限、Pending 查在途、Writable 查余量)是背压的发送侧执法。
这三个场景的共同主题是发送面的所有权与预算——第 5 章语言在 IO 出口的全集:Send 复制、SendVec 聚集复制、SendRef 借用+回调、SendTake 接管、SendFile 委托内核。五档按"数据从哪来、谁释放"选——与第 65 章缓冲链四类追加一一对应(那个是入链、这个是出链)。
68.2概念
发送四族与文件发送
| 入口 | 所有权 | 适用 |
|---|---|---|
| Send | 复制 | 小数据安全路径(金标准章) |
| SendVec | 聚集复制 | 多缓冲一次发出(第 21 章批量) |
| SendRef / SendRefs | 引用+释放回调 | 段链直移(零拷贝——金标准 SendBuffer 的族亲) |
| SendTake | 接管 | xrtMalloc 块(发完引擎释放) |
| SendFile | 委托内核 | 文件区间发送(sendfile/mmap 后端) |
SendRef 的释放回调:数据发完(或流销毁)回调恰好一次——与第 65 章 AppendRef 同款纪律;SendRefs 是多段变体(段链一次发)。SendFile 的区间:偏移+长度有界——文件的分块发送控制内存(每块内核缓冲而非整文件)。
写预算:发送背压三件套
背压的完整链条:对端不读 → TCP 窗口收缩 → 引擎缓冲积压 → Pending 逼近 Limit → 新 Send 被拒 → 上游感知(处理者看到发送失败/需等待)——慢消费者无法拖垮服务内存。Pause/Resume 是手动挡(Pausable 语义:Pause 后引擎保留数据不推进——上游可决策丢弃或断开)。第 21 章队列背压的连接版。
读取与 Worker 约束
Worker 内 API(Buffer/Read/Consume 等——必须在 Stream 所属 Worker 线程调用):直移缓冲的消费面(Buffer 取链、Consume 前进——第 65 章操作在引擎数据上直接用);跨线程入口(xrtNetPost 投递任务到 Stream 所属 Worker——第 56 章调度器 Post 的引擎版);等待族(Wait/WaitAvailable 阻塞式——非 Worker 线程的汇合用;WaitAvailableAsync Future 式——编排用)。为什么约束:直移缓冲操作免锁的前提是"单线程"——Worker 约束就是第 56 章调度器绑定纪律的引擎版;跨线程经 Post 传递执行权而非加锁——零锁纪律贯穿。
服务端:双面 Accept 与共享端口
xnetserver(TCP Server):拉取 Accept(队列式取连接——来一个取一个,上层限流/分发直接控制——tcp_server 范例形态);Future Accept(事件循环侧——编排友好);阻塞 Accept(非 Worker 线程——管理线程等连接)。双面并存是迁移期的真实形态(tcp_server_sync 范例:同一 Server 上 Future 与阻塞两种取法同场)。多监听与共享端口:一个 Server 多端点(v4+v6 双栈);SO_REUSEPORT 式共享端口(server_tour 的 shared-port=1 断言——多进程共享监听的平台支持)。Accept 统计(accepted/queued——服务观测字段)。
停机与风暴:服务端收尾
Listener Close(停止接入——在途 accept 排空)、连接上限(ServerConfig 的队列深度——接入背压:满了拒绝新连接而不是无限排)、停机顺序(Listener → 各 Stream → Engine——第 67 章顺序的服务端版)。连接风暴的处理位:Accept 队列深度限制 + 快速拒绝(错误成本低)——而不是接受后再断(已分配资源)。
68.3示例
完整程序:Stream 全族巡礼
来自仓库范例 examples/network/tcp_stream_tour/main.c——发送四族+预算+流控+半关闭的完整验收:
*/ #include <stdio.h> #include <string.h> #include <xrt.h> #include <math.h> static inline double exampleTimerLimit(int64 Timeout) { return Timeout == XRT_WAIT_FOREVER ? INFINITY : xrtTimer() + (double)Timeout / 1000.0; } static inline bool exampleTimerExpired(double Limit) { return xrtTimer() >= Limit; } static inline int64 exampleTimerRemaining(double Limit) { double Ms; if (Limit == INFINITY) return XRT_WAIT_FOREVER; Ms = ceil((Limit - xrtTimer()) * 1000.0); return Ms <= 0 ? 0 : Ms >= 0x1p63 ? INT64_MAX : (int64)Ms; } #define EXAMPLE_DEADLINE_MS INT64_C(3000) /* 释放回调计数:SendRef/SendRefs 的零复制载荷离队时各执行一次。 */ static uint32 g_Releases = 0; static void exampleCountRelease(ptr pContext, cbytes pData, size_t iSize) { (void)pContext; (void)pData; (void)iSize; g_Releases = g_Releases + 1u; } /* Worker 任务上下文。 */ typedef struct examplestreamtask { xnetstream* pStream; volatile bool bDone; size_t iBufferSize; size_t iRead; size_t iConsume; char ReadOut[8]; bool bSocket; bool bSetEvents; bool bSetData; } examplestreamtask; /* 最小事件集(仅 Close),不改变拉取模式。 */ static void exampleStreamClose(xnetstream* pStream, xnetresult Result, const xerror* pError, ptr pData) { (void)pStream; (void)Result; (void)pError; (void)pData; } * SetEvents + SetData——六件 Worker 专用操作一批完成。 */ static void exampleStreamTask(xnetworker* pWorker, ptr pData) { examplestreamtask* pTask = (examplestreamtask*)pData; const xnetbuf* pBuffer; static xnetstreamevents s_Events = { 0 }; static uint64 s_Tag = 0; (void)pWorker; s_Events.Close = exampleStreamClose; pBuffer = xrtNetStreamBuffer(pTask->pStream); pTask->iBufferSize = pBuffer != NULL ? xrtNetBufSize(pBuffer) : 0; pTask->iRead = xrtNetStreamRead(pTask->pStream, pTask->ReadOut, 4u); pTask->iConsume = xrtNetStreamConsume(pTask->pStream, 4u); pTask->bSocket = xrtNetStreamSocket(pTask->pStream) != NULL; pTask->bSetEvents = xrtNetStreamSetEvents(pTask->pStream, &s_Events, NULL); pTask->bSetData = xrtNetStreamSetData(pTask->pStream, &s_Tag); pTask->bDone = true; } static bool exampleWaitDone(volatile bool* pFlag) { double iDeadline = exampleTimerLimit(EXAMPLE_DEADLINE_MS); while ( !*pFlag ) { if ( exampleTimerExpired(iDeadline) ) { return false; } xrtThreadYield(); } return true; } static bool exampleWaitStreamState(xnetstream* pStream, xnetstreamstate State) { double iDeadline = exampleTimerLimit(EXAMPLE_DEADLINE_MS); while ( xrtNetStreamState(pStream) != State ) { if ( exampleTimerExpired(iDeadline) ) { return false; } xrtThreadYield(); } return true; } int main(void) { static const char sExpected[] = "vec-joinref-zero-copyrefs-arefs-btake-ownedfile-block"; /* take-owned 与 file-block 各 10 字节,不带结尾零。 */ xnetengineconfig EngineConfig; xnetstreamconfig StreamConfig; xnetlistenconfig ListenConfig; xnetengine* pEngine = NULL; xnetlistener* pListener = NULL; xnetstream* pClient = NULL; xnetstream* pServer = NULL; xnetaddr Address; xnetref Refs[2]; xnetspan Vec[2]; xnetpost Post; examplestreamtask Task; xfuture* pAvailable = NULL; xnetstreamstats Stats; ptr pTake = NULL; xfile File; uint64 iFileSize = 0; int iResult = 1; /* 配置:默认值即自适应读取 + 背压水位。 */ xrtNetStreamConfigInit(&StreamConfig); xrtNetEngineConfigInit(&EngineConfig); EngineConfig.Workers = 1; xrtNetListenConfigInit(&ListenConfig); (void)xrtNetAddrLoopback(&Address, XNET_FAMILY_IPV4, 0); ListenConfig.Address = Address; pEngine = xrtNetEngineCreate(&EngineConfig); if ( (pEngine == NULL) || !xrtNetEngineStart(pEngine) ) { goto Cleanup; } pListener = xrtNetListen(pEngine, &ListenConfig, NULL, NULL, NULL); if ( (pListener == NULL) || !xrtNetListenerLocal(pListener, &Address) ) { iResult = 2; goto Cleanup; } pClient = xrtNetStreamConnect(pEngine, &Address, 0, &StreamConfig, NULL, NULL); if ( (pClient == NULL) || !exampleWaitStreamState(pClient, XNET_STREAM_OPEN) ) { iResult = 3; goto Cleanup; } { double iDeadline = exampleTimerLimit(EXAMPLE_DEADLINE_MS); while ( (pServer = xrtNetListenerAccept(pListener)) == NULL ) { if ( exampleTimerExpired(iDeadline) ) { iResult = 4; goto Cleanup; } xrtThreadYield(); } } if ( !exampleWaitStreamState(pServer, XNET_STREAM_OPEN) ) { iResult = 4; goto Cleanup; } printf("stream-tour: config write-limit>=%s writable>=%s\n", xrtNetStreamWriteLimit(pClient) >= 65536u ? "64k" : "?", xrtNetStreamWritable(pClient) > 0u ? "0" : "?"); /* 五种发送形态拼出 55 字节已知流。 */ Vec[0].Data = (cbytes)"vec-"; Vec[0].Size = 4; Vec[1].Data = (cbytes)"join"; Vec[1].Size = 4; if ( xrtNetStreamSendVec(pClient, Vec, 2) != XNET_RESULT_OK ) { goto Cleanup; } if ( xrtNetStreamSendRef(pClient, "ref-zero-copy", 13, exampleCountRelease, &g_Releases) != XNET_RESULT_OK ) { goto Cleanup; } Refs[0].Data = (cbytes)"refs-a"; Refs[0].Size = 6; Refs[0].Release = exampleCountRelease; Refs[0].Context = &g_Releases; Refs[1].Data = (cbytes)"refs-b"; Refs[1].Size = 6; Refs[1].Release = exampleCountRelease; Refs[1].Context = &g_Releases; if ( xrtNetStreamSendRefs(pClient, Refs, 2) != XNET_RESULT_OK ) { goto Cleanup; } pTake = xrtMalloc(10); if ( pTake == NULL ) { goto Cleanup; } memcpy(pTake, "take-owned", 10); if ( xrtNetStreamSendTake(pClient, pTake, 10) == XNET_RESULT_OK ) { pTake = NULL; } else { goto Cleanup; } /* SendFile:临时文件一段已知区间。 */ File = xrtOpen("xrt-stream-tour.tmp", XFILE_WRITE | XFILE_CREATE | XFILE_TRUNCATE); if ( (File == NULL) || !xrtWriteFull(File, "file-block", 10, NULL) ) { iResult = 5; goto Cleanup; } (void)xrtClose(File); File = xrtOpen("xrt-stream-tour.tmp", XFILE_READ); if ( (File == NULL) || (xrtNetStreamSendFile(pClient, File, 0, 10) != XNET_RESULT_OK) ) { iResult = 6; goto Cleanup; } (void)xrtClose(File); File = NULL; /* 对端整体核对 55 字节。 */ if ( !xrtNetStreamWaitAvailable(pServer, sizeof(sExpected) - 1u,EXAMPLE_DEADLINE_MS, NULL) ) { iResult = 7; goto Cleanup; } { xnetbytes* pBytes = xrtNetStreamRecv(pServer, sizeof(sExpected) - 1u,EXAMPLE_DEADLINE_MS, NULL); xbytesview View = xrtNetBytesView(pBytes); bool bMatch = (View.Size == sizeof(sExpected) - 1u) && (memcmp(View.Data, sExpected, View.Size) == 0); xrtNetBytesDestroy(pBytes); if ( !bMatch ) { iResult = 8; goto Cleanup; } } printf("sends: vec+ref+refs+take+file = %zu bytes verified\n", sizeof(sExpected) - 1u); /* 发送队列排空:Pending 归零。 */ { double iDeadline = exampleTimerLimit(EXAMPLE_DEADLINE_MS); while ( xrtNetStreamPending(pClient) != 0u ) { if ( exampleTimerExpired(iDeadline) ) { iResult = 9; goto Cleanup; } xrtThreadYield(); } } /* Worker 读取族:先备好 8 字节再投递任务。 */ if ( xrtNetStreamSend(pClient, "buf-demo", 8) != XNET_RESULT_OK ) { goto Cleanup; } if ( !xrtNetStreamWaitAvailable(pServer, 8u,EXAMPLE_DEADLINE_MS, NULL) ) { iResult = 10; goto Cleanup; } memset(&Task, 0, sizeof(Task)); Task.pStream = pServer; if ( !xrtNetPostInit(&Post) || !xrtNetPost( xrtNetStreamWorker(pServer), &Post, exampleStreamTask, &Task) || !exampleWaitDone(&Task.bDone) ) { iResult = 11; goto Cleanup; } printf("worker-read: buffer=%zu read=\"%.*s\" consume=%zu", Task.iBufferSize, (int)Task.iRead, Task.ReadOut, Task.iConsume); printf(" socket+setevents+setdata=%s\n", (Task.bSocket && Task.bSetEvents && Task.bSetData && (xrtNetStreamData(pServer) != NULL)) ? "ok" : "fail"); if ( (Task.iBufferSize != 8u) || (Task.iRead != 4u) || (memcmp(Task.ReadOut, "buf-", 4) != 0) || (Task.iConsume != 4u) || !Task.bSocket || !Task.bSetEvents || !Task.bSetData ) { iResult = 12; goto Cleanup; } /* 等待族:READ 条件 / 字节数门槛 / Future 门槛 / WRITE 条件。 */ if ( xrtNetStreamSend(pServer, "wake", 4) != XNET_RESULT_OK ) { goto Cleanup; } printf("waits: read=%d", xrtNetStreamWait(pClient, XNET_STREAM_WAIT_READ,EXAMPLE_DEADLINE_MS, NULL) ? 1 : 0); printf(" available=%d", xrtNetStreamWaitAvailable(pClient, 4u,EXAMPLE_DEADLINE_MS, NULL) ? 1 : 0); { /* BytesRef:接收结果可共享持有——引用与原件各销毁一次。 */ xnetbytes* pBytes = xrtNetStreamRecv(pClient, 4,EXAMPLE_DEADLINE_MS, NULL); xnetbytes* pShared = xrtNetBytesRef(pBytes); xrtNetBytesDestroy(pShared); xrtNetBytesDestroy(pBytes); } if ( xrtNetStreamSend(pServer, "more", 4) != XNET_RESULT_OK ) { goto Cleanup; } pAvailable = xrtNetStreamWaitAvailableAsync(pClient, 4u); printf(" available-async=%d", (pAvailable != NULL) && (xrtFutureWaitFor(pAvailable, EXAMPLE_DEADLINE_MS) == XWAIT_OK) ? 1 : 0); { xnetbytes* pBytes = xrtNetStreamRecv(pClient, 4,EXAMPLE_DEADLINE_MS, NULL); xrtNetBytesDestroy(pBytes); } printf(" write=%d\n", xrtNetStreamWait(pClient, XNET_STREAM_WAIT_WRITE,EXAMPLE_DEADLINE_MS, NULL) ? 1 : 0); /* 流控:Pause 后数据滞留内核,Resume 后到达。 */ xrtNetStreamPause(pServer); if ( xrtNetStreamSend(pClient, "held", 4) != XNET_RESULT_OK ) { goto Cleanup; } { double iHold = exampleTimerLimit(200); bool bHeld = true; while ( exampleTimerExpired(iHold) == false ) { if ( xrtNetStreamAvailable(pServer) != 0u ) { bHeld = false; break; } xrtThreadYield(); } printf("flow: pause=%s", bHeld ? "held" : "leaked"); } if ( !xrtNetStreamResume(pServer) || !xrtNetStreamWaitAvailable(pServer, 4u,EXAMPLE_DEADLINE_MS, NULL) ) { iResult = 13; goto Cleanup; } { xnetbytes* pBytes = xrtNetStreamRecv(pServer, 4,EXAMPLE_DEADLINE_MS, NULL); xrtNetBytesDestroy(pBytes); printf(" resume=delivered\n"); } /* 半关闭:客户端 ShutdownWrite → 服务端读到 EOF(Recv 空 + 终态)。 */ if ( !xrtNetStreamShutdownWrite(pClient) ) { iResult = 14; goto Cleanup; } { xnetbytes* pBytes = xrtNetStreamRecv(pServer, 0,EXAMPLE_DEADLINE_MS, NULL); xbytesview View = xrtNetBytesView(pBytes); int iEof = (View.Size == 0u); xrtNetBytesDestroy(pBytes); if ( !iEof ) { iResult = 15; goto Cleanup; } printf("shutdown: peer-eof=1"); } /* 统计与终态错误。 */ if ( !xrtNetStreamStats(pClient, &Stats) || (Stats.SentBytes < 63u) ) { iResult = 16; goto Cleanup; } printf(" stats(sent=%llu)", (unsigned long long)Stats.SentBytes); printf(" error=%s\n", xrtNetStreamError(pClient) == NULL ? "(none)" : "err"); (void)xrtNetStreamLocal(pClient, &Address); (void)iFileSize; iResult = 0; Cleanup: xrtFree(pTake); xrtFutureDestroy(pAvailable); (void)xrtFileDelete("xrt-stream-tour.tmp"); if ( pClient != NULL ) { (void)xrtNetStreamAbort(pClient); (void)exampleWaitStreamState(pClient, XNET_STREAM_CLOSED); } if ( pServer != NULL ) { (void)xrtNetStreamAbort(pServer); (void)exampleWaitStreamState(pServer, XNET_STREAM_CLOSED); } if ( pListener != NULL ) { double iEnd = exampleTimerLimit(EXAMPLE_DEADLINE_MS); (void)xrtNetListenerClose(pListener); while ( xrtNetListenerState(pListener) != XNET_LISTENER_CLOSED ) { if ( exampleTimerExpired(iEnd) ) { break; } xrtThreadYield(); } } xrtNetStreamDestroy(pClient); xrtNetStreamDestroy(pServer); xrtNetListenerDestroy(pListener); if ( (pEngine != NULL) && !xrtNetEngineDestroy(pEngine) && (iResult == 0) ) { iResult = 17; } return iResult; }
刚才发生了什么。 ① 配置层:WriteLimit 至少 64k、Writable 起点为 0——写预算三件套的起点。② 发送四族联合:Vec+Ref+Refs+Take+File 各发一段、53 字节全量验证——五档所有权在一条流上全部走到。③ worker-read 行:Worker 内读缓冲三件(Buffer=8 字节可用、Read 取 "buf-"、Consume 前进 4)+ Socket/SetEvents/SetData——Worker 约束 API 的实操。④ waits 行:四种等待形态(阻塞读/Available 快照/Async Future/写等待)各验一次。⑤ flow 行:Pause 后数据 held(引擎保留不推进)、Resume 后 delivered——流控两拍。⑥ shutdown 行:对端读到 EOF、stats 的 sent 计数(65=53+12 等附加字节)、error=(none) 干净终态。这个 tour 是 Stream 数据面的全操作验收——生产代码的检查表。
完整程序:双面 Accept 同场
来自 examples/network/tcp_server_sync/main.c——Future 与阻塞取连接并存:
*/ #include <stdio.h> #include <string.h> #include <xrt.h> #include <math.h> static inline double exampleTimerLimit(int64 Timeout) { return Timeout == XRT_WAIT_FOREVER ? INFINITY : xrtTimer() + (double)Timeout / 1000.0; } static inline bool exampleTimerExpired(double Limit) { return xrtTimer() >= Limit; } static inline int64 exampleTimerRemaining(double Limit) { double Ms; if (Limit == INFINITY) return XRT_WAIT_FOREVER; Ms = ceil((Limit - xrtTimer()) * 1000.0); return Ms <= 0 ? 0 : Ms >= 0x1p63 ? INT64_MAX : (int64)Ms; } typedef struct example_tcp_server_wait { xnetengine* Engine; xnetserver* Server; xnetstream* ClientFuture; xnetstream* AcceptedFuture; xnetstream* ClientSync; xnetstream* AcceptedSync; xnetaddr Local; } example_tcp_server_wait; /* 启动使用动态回环端口的拉取式 TCP Server。 */ static bool exampleTcpServerWaitStart(example_tcp_server_wait* pState) { xnetengineconfig EngineConfig; xnetserverconfig ServerConfig; memset(pState, 0, sizeof(*pState)); xrtNetEngineConfigInit(&EngineConfig); EngineConfig.Workers = 2u; pState->Engine = xrtNetEngineCreate(&EngineConfig); if ( (pState->Engine == NULL) || !xrtNetEngineStart(pState->Engine) ) { return false; } xrtNetServerConfigInit(&ServerConfig); if ( !xrtNetAddrLoopback( &ServerConfig.Listen.Address, XNET_FAMILY_IPV4, 0 ) ) { return false; } pState->Server = xrtNetServerStart( pState->Engine, &ServerConfig, NULL, NULL, NULL ); return (pState->Server != NULL) && xrtNetServerLocal( pState->Server, 0, &pState->Local ); } /* 连接刚启动的本地 Server。 */ static xnetstream* exampleTcpServerWaitConnect( example_tcp_server_wait* pState ) { return xrtNetStreamConnect( pState->Engine, &pState->Local, 1u, NULL, NULL, NULL ); } /* 终止连接、关闭 Server,并等待 Engine 中的对象全部退出。 */ static bool exampleTcpServerWaitStop(example_tcp_server_wait* pState) { xnetstream* Streams[] = { pState->ClientFuture, pState->AcceptedFuture, pState->ClientSync, pState->AcceptedSync }; double Deadline = exampleTimerLimit(INT64_C(5000)); bool bClosed = true; size_t i; for ( i = 0; i < (sizeof(Streams) / sizeof(Streams[0])); i++ ) { if ( Streams[i] != NULL ) { (void)xrtNetStreamAbort(Streams[i]); } } if ( pState->Server != NULL ) { (void)xrtNetServerClose(pState->Server); } for ( ;; ) { bool bDone = (pState->Server == NULL) || (xrtNetServerState(pState->Server) == XNET_SERVER_CLOSED); for ( i = 0; i < (sizeof(Streams) / sizeof(Streams[0])); i++ ) { bDone = bDone && ((Streams[i] == NULL) || (xrtNetStreamState(Streams[i]) == XNET_STREAM_CLOSED)); } if ( bDone ) { break; } if ( exampleTimerExpired(Deadline) ) { bClosed = false; break; } xrtThreadYield(); } for ( i = 0; i < (sizeof(Streams) / sizeof(Streams[0])); i++ ) { xrtNetStreamDestroy(Streams[i]); } xrtNetServerDestroy(pState->Server); if ( (pState->Engine != NULL) && !xrtNetEngineDestroy(pState->Engine) ) { bClosed = false; } return bClosed; } /* 依次演示 Future Accept 和非 Worker 线程上的阻塞 Accept。 */ int main(void) { example_tcp_server_wait State; xfutureresult Result; xfuture* pAccept = NULL; int iResult = 1; if ( !exampleTcpServerWaitStart(&State) ) { goto Cleanup; } pAccept = xrtNetServerAcceptAsync(State.Server); State.ClientFuture = exampleTcpServerWaitConnect(&State); if ( (pAccept == NULL) || (State.ClientFuture == NULL) || (xrtFutureWaitFor( pAccept, INT64_C(5000) ) != XWAIT_OK) || !xrtFutureResult(pAccept, &Result) || (Result.State != XFUTURE_RESOLVED) ) { goto Cleanup; } State.AcceptedFuture = xrtNetStreamRef( (xnetstream*)Result.Value ); xrtFutureDestroy(pAccept); pAccept = NULL; if ( State.AcceptedFuture == NULL ) { goto Cleanup; } State.ClientSync = exampleTcpServerWaitConnect(&State); if ( State.ClientSync == NULL ) { goto Cleanup; } State.AcceptedSync = xrtNetServerAcceptWait( State.Server,INT64_C(5000), NULL ); if ( State.AcceptedSync == NULL ) { goto Cleanup; } printf( "accepted Future and synchronous connections on port %u\n", (unsigned)State.Local.Port ); iResult = 0; Cleanup: if ( (pAccept != NULL) && (xrtFutureState(pAccept) == XFUTURE_PENDING) ) { (void)xrtFutureCancel(pAccept); (void)xrtFutureWaitFor(pAccept, INT64_C(5000)); } xrtFutureDestroy(pAccept); if ( !exampleTcpServerWaitStop(&State) && (iResult == 0) ) { iResult = 2; } return iResult; }
刚才发生了什么。 ① 动态回环端口的 Server 启动(端口 0 + Local 取回——第 67 章姿势)。② Future Accept:事件循环侧取第一条连接——编排友好(后续操作可组合)。③ 阻塞 Accept:管理线程侧取第二条——普通线程的汇合形态。④ 两种姿势同一 Server 同场——迁移期真实形态的实证(从阻塞模型渐进到事件模型不需要一步切换)。server_tour 范例补多端点/共享端口/Accept 统计——服务端全接口。
68.4契约
- 发送五档:Send 复制 / Vec 聚集 / Ref 引用+回调恰好一次 / Take 接管 / File 委托内核——按来源选(与第 65 章入链四类对应)。
- 写预算:WriteLimit 上限 + Pending 水位 + Writable 余量——慢消费者背压的发送侧执法。
- Worker 约束:Buffer/Read/Consume 仅 Worker 内;跨线程经 Post——零锁纪律。
- 流控:Pause/Resume 手动挡——暂停保留数据,上游决策丢弃或断开。
- 双面 Accept:拉取/Future/阻塞三形态并存——迁移渐进。
- 接入背压:Accept 队列深度限制+快速拒绝——风暴挡在门外。
常见错误
症状:接收端偶发收到脏数据——发送回调还没来,数据源已被释放;与第 65 章坑 1 同族。
原因:SendRef 是引用发送——释放回调前数据冻结期(引擎正在读它)。
char* Block = xrtMalloc(1024); Fill(Block); xrtNetStreamSendRef(Stream, Block, 1024, releaseCb, Ctx); xrtFree(Block); /* 回调没来就释放——引擎正在读 */
xrtNetStreamSendTake(Stream, TakeBlock, 1024); /* 接管——引擎发完释放 */ /* 或 SendRef+回调:回调恰好在数据不再被引用后触发——不动它直到回调 */
症状:服务内存随慢客户端增长——某客户端不读,引擎缓冲无限积压;OOM 崩溃在深夜流量高峰。
原因:没设 WriteLimit——发送无上限,TCP 窗口收缩的积压全部堆在服务内存。
xnetstreamconfig Config; xrtNetStreamConfigInit(&Config); /* 默认无预算限制 */ xrtNetStreamSend(Stream, HugeData, HugeSize); /* 无界积压 */
xnetstreamconfig Config; xrtNetStreamConfigInit(&Config); Config.WriteLimit = 1u << 20; /* 1MB 预算 */ /* 发送前查余量 */ if ( xrtNetStreamWritable(Stream) < Need ) { WaitOrPause(Stream); /* 背压生效——等或暂停,不上冲 */ }
编程练习
同一数据分别用五档发送(File 档用临时文件)——接收端全量验证;各档的释放时机(回调/接管)打印确认。
慢客户端(收 1 字节歇 100ms)+ 服务大量发送:有 WriteLimit vs 无限制对照——观察服务内存曲线(第 6 章统计)与 Pending 水位;超预算的处置路径验证。
静态文件服务:多客户端并发下载同一文件——SendFile 区间分块(块预算)+ 每客户端 WriteLimit + 全局接受上限。验收标准:1GB 文件服务内存峰值 <100MB(分块+预算的实证);慢客户端不影响其他客户端;停机全部收尾(三件套协议);下载校验逐客户端 Hash64 一致。
小结
| 知识点 | 速查 |
|---|---|
| 发送五档 | Send 复制/Vec 聚集/Ref 引用+回调/Take 接管/File 内核 |
| 写预算 | WriteLimit 上限 + Pending 水位 + Writable 余量——慢客户端背压 |
| Worker 约束 | Buffer/Read/Consume 仅 Worker 内——跨线程 Post 投递 |
| 流控 | Pause/Resume——保留数据的手动挡 |
| 半关闭 | ShutdownWrite——对端读 EOF(单向结束) |
| 双面 Accept | 拉取/Future/阻塞并存——迁移渐进 |
| 接入背压 | Accept 队列深限+快速拒绝——风暴挡门外 |
📖 本章涉及的 API 参考手册:tcp(81 个条目)tcp_server(22 个条目)net(247 个条目)
点击查看完整签名、参数约束、返回值与错误说明