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
46.1 Project goal
| Capability | Design |
|---|---|
| Command set | GET/SET/DEL/PING (REQUEST → RESPONSE, id correlation) |
| Push | on key change, send EVENT to subscribed connections (the PUSH channel) |
| Sessions | a connection registry: processor + carry buffer + subscription set |
| Persistence | in-memory KV + optional disk persistence (reusing ch. 32's pattern) |
| Defenses | xtp2 limits + idle_timeout + command rate limiting |
| Deployment | dual endpoints: plaintext 9000 + TLS 9443 |
46.2 Sessions & command dispatch
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
| Dimension | Standard |
|---|---|
| Function | four commands + subscription push all pass; id correlation correct |
| Protocol resilience | fragmentation/sticky/malicious-frame tests pass (ch. 33 exercises) |
| Concurrency | 100 in-flight requests on one connection under load; multi-connection broadcasts never cross |
| Lifecycle | subscriptions auto-restore after swaps (the set enters the Swap snapshot — another "values can hand over" sample) |
| Deployment | dual endpoints + certificate hot-swap drill |
Common mistakes
- 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.
- 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
Build the KV gateway per 46.2–46.5 (the client can be written with nested TCC as a test end).
Write a minimal client module (connect → encode requests → feed responses → match ids) reusable by other scripts.
Add an HTTP service translating REST requests into XTP2 calls (a reverse gateway) — two servers cooperating (ch. 11).
Summary
| Point | Conclusion |
|---|---|
| Structure | session registry + command dispatch table + id correlation |
| Push | the EVENT channel reuses WS broadcast discipline (limit/merge/demote) |
| Defenses | limits tightened per business + bad frame = disconnect + idle reclaim |
| Generations | subscription sets hand over (values); the session table doesn't (connections belong to their generation) |
- xrt · tcp — client-connection material (advanced exercise)