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

Вторая половина TCP охватывает три продвинутых блока: четыре семейства отправки (Vec сбор / Ref ноль копий + колбэк освобождения / Take захват / File посылка файла ядром) с бюджетом записи (потолок WriteLimit / запас Writable / в полёте Pending — тройка противодавления отправки); чтение и ограничения Worker (Buffer/Read/Consume только внутри Worker — грань потребления буферов прямой передачи; межпоточно через Post); и серверная форма (xnetserver — вытягивающий Accept (соединения из очереди), Future Accept и блокирующий Accept сосуществуют, многослушание и общие порты). Четыре состояния и эхо золотого стандарта (глава 67) — базовая форма; эта глава — её полное цветение.

68.1引入

Три продвинутых сценария. Сценарий один: файловый сервер должен отправить файл 1 ГБ — прочитать весь файл в память и Send — катастрофа (взрыв памяти); xrtNetStreamSendFile (посылка файла ядром + ограниченные диапазоны — место приземления sendfile, анонсированное главой 45) не проходит через пользовательские буферы. Сценарий два: прокси пересылает вниз цепочку сегментов, принятую сверху — копировать перед отправкой расточительно; 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: срабатывает ровно один раз, когда данные отправлены (или поток разрушен) — та же дисциплина, что AppendRef главы 65; SendRefs — многосегментный вариант (целая цепочка сегментов одной отправкой). Диапазон SendFile: смещение + длина ограничены — поблочная посылка файла управляет памятью (буфер ядра на блок, а не на весь файл).

Бюджет записи: тройка противодавления отправки

1WriteLimit(N)потолок бюджета отправки на соединение (в полёте + в очереди <= N)
2Pending()текущие байты в полёте/очереди — датчик уровня
3Writable()запас = Limit - Pending — безопасный объём подачи
4Send сверх бюджетанеудача или блокировка (по форме вызова) — точка укуса противодавления
5В пару управление потокомPause/Resume — после Pause движок держит данные (страховка памяти)

Полная цепь противодавления: пир перестаёт читать → окно TCP сжимается → буферы движка копятся → Pending приближается к Limit → новые Send отвергаются → верхний слой замечает (обработчик видит неудачу отправки / необходимость ждать) — медленный потребитель не может утащить память сервиса вниз. Pause/Resume — ручная передача (семантика Pausable: после Pause движок удерживает данные без продвижения — верхний слой решает выбросить или разорвать). Очередное противодавление главы 21 в версии соединения.

Чтение и ограничения Worker

API внутри Worker (Buffer/Read/Consume и др. — обязаны вызываться в потоке Worker, владеющего Stream): грань потребления буферов прямой передачи (Buffer берёт цепочку, Consume продвигает — операции главы 65 напрямую на данных движка); межпоточный вход (xrtNetPost доставляет задачу в Worker Stream — engine-версия Post планировщика главы 56); семейство ожидания (Wait/WaitAvailable блокирующие — рандеву из не-Worker потоков; WaitAvailableAsync в форме Future — для оркестрации). Зачем ограничение: предпосылка безблокировочных операций прямой передачи — «один поток»; ограничение Worker — engine-версия дисциплины привязки планировщика главы 56; межпоточно исполнение передаётся через Post, а не блокировкой — дисциплина нулевых замков повсюду.

Сервер: двуликий Accept и общие порты

xnetserver (TCP Server): вытягивающий Accept (соединения из очереди — пришло одно, взял одно; верхний слой напрямую управляет троттлингом/диспетчеризацией — форма примера tcp_server); Future Accept (на стороне событийного цикла — удобно для оркестрации); блокирующий Accept (из не-Worker потока — управляющий поток ждёт соединений).Сосуществование двух лиц — реальная форма эпохи миграции (пример tcp_server_sync: Future и блокирующее извлечение на одном Server в одной сцене). Многослушание и общие порты: один Server — несколько конечных точек (двухстековый v4+v6); общие порты в стиле SO_REUSEPORT (утверждение shared-port=1 в server_tour — платформенная поддержка совместного прослушивания многих процессов). Статистика Accept (accepted/queued — поля наблюдения сервиса).

Остановка и шторм: серверная уборка

Close слушателя (прекратить приём — вытянуть accept в полёте), потолок соединений (глубина очереди ServerConfig — противодавление приёма: при заполнении отказывать новым, а не стоять бесконечно), порядок остановки (Listener → Stream → Engine — порядок главы 67 в серверной версии). Точка обработки шторма соединений: ограничение глубины очереди Accept + быстрый отказ (низкая цена ошибки) — а не принять и разорвать (ресурсы уже выделены).

68.3示例

Полная программа: тур по всему семейству Stream

Из примера репозитория examples/network/tcp_stream_tour/main.c — полная приёмка семейств отправки + бюджет + управление потоком + полузакрытие:

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;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/network/tcp_stream_tour/main.c -lws2_32 -liphlpapistream-tour: config write-limit>=64k writable>=0sends: vec+ref+refs+take+file = 53 bytes verifiedworker-read: buffer=8 read="buf-" consume=4 socket+setevents+setdata=okwaits: read=1 available=1 available-async=1 write=1flow: pause=held resume=deliveredshutdown: peer-eof=1 stats(sent=65) error=(none)

Что только что произошло. (1) Конфигурационный уровень: WriteLimit не меньше 64k, Writable стартует с 0 — отправная точка тройки бюджета записи. (2) Совместное выступление четырёх семейств отправки: Vec+Ref+Refs+Take+File отправляют по отрезку, 53 байта сверены целиком — все пять ступеней владения пройдены на одном потоке. (3) Строка worker-read: тройка чтения внутри Worker (Buffer = 8 байт доступно, Read берёт "buf-", Consume продвигает на 4) + Socket/SetEvents/SetData — практика API ограничения Worker. (4) Строка waits: четыре формы ожидания (блокирующее чтение / снимок Available / Async Future / ожидание записи), по одной на каждую. (5) Строка flow: после Pause данные held (движок удерживает без продвижения), после Resume delivered — два такта управления потоком. (6) Строка shutdown: пир читает EOF, счётчик sent статистики (65 = 53 + 12 дополнительных байт), error=(none) — чистое конечное состояние. Этот тур — приёмка всех операций плоскости данных Stream — чек-лист для продакшн-кода.

Полная программа: двуликий Accept в одной сцене

Из examples/network/tcp_server_sync/main.c — Future и блокирующее извлечение соединений сосуществуют:

examples/network/tcp_server_sync/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;
}



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;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/network/tcp_server_sync/main.c -lws2_32 -liphlpapiaccepted Future and synchronous connections on port NNNNN

Что только что произошло. (1) Server стартует на динамическом петлевом порте (порт 0 + извлечение Local — поза главы 67). (2) Future Accept: событийный цикл берёт первое соединение — удобно для оркестрации (последующие операции компонуются). (3) Блокирующий Accept: сторона управляющего потока берёт второе — форма рандеву обычных потоков. (4) Обе позы на одном 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 + быстрый отказ — шторм останавливается за дверью.

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

Ловушка 1: досрочное освобождение данных после SendRef:Ловушка 1: досрочное освобождение данных после SendRef

Симптомы: приёмник изредка получает грязные данные — колбэк отправки ещё не пришёл, а источник уже освобождён; одно семейство с ловушкой 1 главы 65.

Причина: SendRef — отправка ссылкой; окно заморозки данных до колбэка освобождения (движок его читает).

错误写法
char* Block = xrtMalloc(1024);
Fill(Block);
xrtNetStreamSendRef(Stream, Block, 1024, releaseCb, Ctx);
xrtFree(Block);   /* освободили до колбэка — движок ещё читает */
正确写法
xrtNetStreamSendTake(Stream, TakeBlock, 1024);   /* захват — движок освобождает после отправки */
/* или SendRef + колбэк: колбэк срабатывает ровно когда данные больше не ссылаются — не трогайте до него */
Ловушка 2: отправка без бюджета взрывает память:Ловушка 2: отправка без бюджета взрывает память

Симптомы: память сервиса растёт вместе с медленными клиентами — один клиент не читает, буферы движка копятся без границ; OOM-падение в пике ночного трафика.

Причина: WriteLimit не установлен — отправка без потолка, и откат сжимающегося окна TCP весь сваливается в память сервиса.

错误写法
xnetstreamconfig Config;
xrtNetStreamConfigInit(&Config);   /* по умолчанию без ограничения бюджета */
xrtNetStreamSend(Stream, HugeData, HugeSize);   /* безграничный откат */
正确写法
xnetstreamconfig Config;
xrtNetStreamConfigInit(&Config);
Config.WriteLimit = 1u << 20;   /* бюджет 1 МБ */
/* перед отправкой проверяем запас */
if ( xrtNetStreamWritable(Stream) < Need ) {
	WaitOrPause(Stream);   /* противодавление укусило — ждать или ставить на паузу, не наваливаться */
}

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

Базовое: сравнение пяти ступеней

Одни и те же данные отправьте каждой из пяти ступеней (ступень File — через временный файл) — полная сверка на приёмнике; моменты освобождения каждой ступени (колбэк/захват) печатаются и подтверждаются.

Продвинутое: эксперимент противодавления

Медленный клиент (читает 1 байт, пауза 100 мс) + сервер шлёт интенсивно: с WriteLimit против без ограничений — наблюдайте кривую памяти сервиса (статистика главы 6) и уровень Pending; проверьте путь обработки сверхбюджета.

Вызов: сервис раздачи файлов

Статический файловый сервис: много клиентов конкурентно скачивают один файл — SendFile поблочно по диапазонам (блочный бюджет) + WriteLimit на клиента + глобальный потолок приёма. Критерий приёмки: раздача файла 1 ГБ держит пиковую память сервиса <100 МБ (доказательство блоков + бюджета); медленные клиенты не мешают остальным; остановка полностью убирается (протокол тройки); скачивания сверяются поп клиентски Hash64.

Итоги

ТемаКраткая справка
Пять ступеней отправкиSend копия / Vec сбор / Ref ссылка + колбэк / Take захват / File ядро
Бюджет записипотолок WriteLimit + уровень Pending + запас Writable — противодавление медленным клиентам
Ограничение WorkerBuffer/Read/Consume только внутри Worker — межпоточно доставка Post
Управление потокомPause/Resume — ручная передача, удерживающая данные
ПолузакрытиеShutdownWrite — пир читает EOF (одностороннее завершение)
Двуликий Acceptвытягивающий/Future/блокирующий сосуществуют — постепенная миграция
Противодавление приёмаограничение глубины очереди Accept + быстрый отказ — шторм за дверью

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

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