Chapter 46 / 51
Volume 5 · Extensions, Ops & Projects

Project 2: an XTP2 TCP gateway (a key-value service)

Speaking XTP2 over TCP: a KV gateway with sessions, command dispatch and server push — every concept of ch. 23 and ch. 33 combined.

Overview

Positioning: the graduation project for the binary-protocol track. Choosing XTP2 over a homemade frame format keeps your attention on the service structure — exactly the division of labor the framework era should have.

46.1 Project goal

CapabilityDesign
Command setGET/SET/DEL/PING (REQUEST → RESPONSE, id correlation)
Pushon key change, send EVENT to subscribed connections (the PUSH channel)
Sessionsa connection registry: processor + carry buffer + subscription set
Persistencein-memory KV + optional disk persistence (reusing ch. 32's pattern)
Defensesxtp2 limits + idle_timeout + command rate limiting
Deploymentdual endpoints: plaintext 9000 + TLS 9443

46.2 Sessions & command dispatch

The dispatch core (extending ch. 33's assembly)
static void DispatchMessage(XS_StreamConn* pConn, const xtp2_message* pMsg)
{
    switch ( pMsg->command ) {
    case CMD_GET: {
        xtp2_span tKey = { pMsg->body, pMsg->body_size };
        xbuffer* pVal = KV_Get(tKey);
        ReplyResponse(pConn, pMsg->id, pVal ? 0 : 1, pVal);  /* id echoed */
        break; }
    case CMD_SET:
        KV_Set(SplitKeyVal(pMsg));               /* body = key\0val convention */
        NotifySubscribers(SplitKey(pMsg), pMsg); /* EVENT push */
        ReplyResponse(pConn, pMsg->id, 0, NULL);
        break;
    default:
        ReplyResponse(pConn, pMsg->id, 4096 + pMsg->command, NULL); /* unknown command */
    }
}
/* ReplyResponse: measure→encode→Send (reusing ch. 33's packing pattern) */

What just happened: the command field (u16) becomes a stable dispatch axis — a new command = one case + one doc line, while the protocol surface (the frame format) never moves. The error-code space is partitioned too (0 ok / 1 not-found / 4096+ protocol-layer) — project 1's "error contract first" holds equally in the binary world.

46.3 Responses & push

  • id correlation: RESPONSE echoes the REQUEST's id verbatim — clients match asynchronous requests on it (one connection can hold several in flight)
  • EVENT push: subscription sets (one key-prefix set per session) are walked after a SET commits — the broadcast discipline copies ch. 22 (walk under lock, copy-send, failure-tolerant)
  • Backpressure: the defense against subscription storms is application-level rate limiting (max EVENTs per connection per second) + slow-consumer demotion — ch. 22's policy-layer backpressure transplanted to TCP

46.4 Defenses & dual stack

  • Tighten limits: the KV value ceiling is max_body — set it per business (e.g. 256KB), don't keep the 1MiB default
  • Bad frame = disconnect + count: increment a malicious-frame counter on SessionDrop into /stats (ch. 43's observability habit)
  • Idle reclamation: idle_timeout sweeps zombie connections; subscription sets clean up in the Close terminal
  • Dual stack: plaintext (intranet debugging) + TLS (external) share one callback set (ch. 23 exercise 3's output) — SNI certificates configured per ch. 12

46.5 The acceptance checklist

DimensionStandard
Functionfour commands + subscription push all pass; id correlation correct
Protocol resiliencefragmentation/sticky/malicious-frame tests pass (ch. 33 exercises)
Concurrency100 in-flight requests on one connection under load; multi-connection broadcasts never cross
Lifecyclesubscriptions auto-restore after swaps (the set enters the Swap snapshot — another "values can hand over" sample)
Deploymentdual endpoints + certificate hot-swap drill

Common mistakes

Push without rate limiting
Symptom
With a hot key SET at high frequency, subscribed connections' send buffers bloat.
Cause
TCP sends are asynchronously buffered — without application-level EVENT limits and merging (e.g. a 50ms window merging like kinds), slow consumers drag memory down. Copy ch. 22's policy-layer backpressure.
Subscription sets not handed over
Symptom
Push stops working after a hot reload.
Cause
Subscription sets are "values" (key-prefix strings) — per ch. 28's criterion they should hand over (snapshot into Swap); the session registry itself doesn't (connections belong to the old generation); new connections re-subscribe.

Exercises

BasicComplete the gateway

Build the KV gateway per 46.2–46.5 (the client can be written with nested TCC as a test end).

Acceptance: all five checklist dimensions green.
AdvancedA protocol client

Write a minimal client module (connect → encode requests → feed responses → match ids) reusable by other scripts.

Acceptance: 100 concurrent in-flight requests with zero cross-talk.
ChallengeAn HTTP bridge

Add an HTTP service translating REST requests into XTP2 calls (a reverse gateway) — two servers cooperating (ch. 11).

Acceptance: REST semantics map completely onto the command protocol; the bridge layer never blocks.

Summary

PointConclusion
Structuresession registry + command dispatch table + id correlation
Pushthe EVENT channel reuses WS broadcast discipline (limit/merge/demote)
Defenseslimits tightened per business + bad frame = disconnect + idle reclaim
Generationssubscription sets hand over (values); the session table doesn't (connections belong to their generation)
API references in this chapter
xrt further reading
  • xrt · tcp — client-connection material (advanced exercise)