Skip to content

← STREAM | Transport →

Proxy Pattern

What this chapter answers — how to compose ROUTER, DEALER, PAIR, and the PUB-family sockets into a proxy that relays messages. Each individual socket's contract is owned by that socket's own spec.

1. Overview

A proxy relays messages between two sockets. zlink_proxy() is a general-purpose utility that works with any socket combination. Users can also build custom proxies by composing the public per-socket APIs.

zlink_config_result_t zlink_proxy (void *frontend, void *backend, void *capture);
  • Forwards messages from frontend to backend and vice versa
  • If capture is non-NULL, copies all passing messages to the capture socket
  • Blocking function — run in a dedicated thread
  • No socket type restriction — internally uses the same socket_base_t recv/send paths as the raw socket APIs, so pairing sockets whose public send or receive surface returns ZLINK_SUBMIT_NOT_SUPPORTED / ZLINK_RECV_NOT_SUPPORTED (for example XSUB or XPUB) still works inside a proxy

Example Socket Combinations

frontend backend Use case
XSUB XPUB PUB/SUB relay (most common)
ROUTER DEALER Request/reply broker
DEALER DEALER Load balancing relay
PAIR PAIR Inter-thread bridge

3. PUB/SUB Proxy — XSUB/XPUB

The most common proxy pattern.

%%{init: {'flowchart': {'nodeSpacing': 32, 'rankSpacing': 40, 'padding': 8, 'wrappingWidth': 180}, 'themeVariables': {'fontSize': '18px'}}}%%
flowchart LR
    PUB -->|data| XSUB
    XSUB ==>|proxy| XPUB
    XPUB -->|data| SUB
    SUB -.->|subscribe| XPUB
    XPUB -.->|proxy| XSUB
    XSUB -.->|subscribe| PUB

3.1 Built-in Proxy

void *xsub = zlink_socket(ctx, ZLINK_SOCKET_XSUB);
zlink_bind(xsub, "tcp://*:5556");      /* PUBs connect here */

void *xpub = zlink_socket(ctx, ZLINK_SOCKET_XPUB);
zlink_bind(xpub, "tcp://*:5557");      /* SUBs connect here */

void *capture = zlink_socket(ctx, ZLINK_SOCKET_PUB);
zlink_bind(capture, "tcp://*:5558");   /* optional: message recording */

zlink_proxy(xsub, xpub, capture);      /* blocking */

zlink_proxy() handles two things internally: - Data relay: Pulls messages from XSUB and pushes to XPUB - Subscription propagation: Pulls subscription events from XPUB and pushes to XSUB

3.2 Manual Proxy

When custom logic (logging, filtering, topic transformation) is needed, build a manual proxy using the public per-socket APIs: zlink_subscribe() / zlink_publish() for data, zlink_xpub_recv() / zlink_set_subscription() for subscriptions.

Data Flow

Step Socket API Description
1 XSUB zlink_subscribe(xsub, ...) Receive the complete payload into an array (topic returned separately)
2 App Custom logic Filtering, transformation, logging
3 XPUB zlink_publish(xpub, topic, ...) Publish the complete payload array

Subscription Propagation

Step Socket API Description
1 XPUB zlink_xpub_recv(xpub, ...) Receive SUB subscribe/unsubscribe events
2 App Custom logic Authorization, topic remapping
3 XSUB zlink_set_subscription(xsub, topic) Propagate to upstream PUB

Full Code

void *xsub = zlink_socket(ctx, ZLINK_SOCKET_XSUB);
void *xpub = zlink_socket(ctx, ZLINK_SOCKET_XPUB);
zlink_bind(xsub, "tcp://*:5556");
zlink_bind(xpub, "tcp://*:5557");

while (running) {
    /* Data relay: XSUB -> app -> XPUB, one complete record at a time */
    char topic[256];
    size_t topic_len = 0;
    zlink_msg_t parts[16];
    size_t part_count = 0;

    zlink_recv_result_t rc = zlink_subscribe(
        xsub, NULL, topic, sizeof(topic), &topic_len,
        parts, 16, &part_count,
        ZLINK_RECV_FLAGS_DONTWAIT);
    if (rc == ZLINK_RECV_OK) {
        /* Insert custom logic here (filtering, logging, etc.) */
        /* Forward the complete array in one call to preserve the record boundary. */
        zlink_publish(xpub, topic, parts, part_count, ZLINK_SEND_FLAGS_NONE);
    }

    /* Subscription propagation: XPUB -> app -> XSUB */
    const zlink_routing_id_t *sub_rid = NULL;
    int subscribed = 0;
    char sub_topic[256];
    size_t sub_len = 0;
    zlink_recv_result_t sub_rc = zlink_xpub_recv(
        xpub, &sub_rid, &subscribed, sub_topic, sizeof(sub_topic), &sub_len,
        ZLINK_RECV_FLAGS_DONTWAIT);
    if (sub_rc == ZLINK_RECV_OK) {
        /* Insert custom logic here (authorization, remapping, etc.) */
        if (subscribed)
            zlink_set_subscription(xsub, sub_topic);
        else
            zlink_unset_subscription(xsub, sub_topic);
    }
}

3.3 Why XSUB/XPUB?

Question With SUB/PUB With XSUB/XPUB
Data pass-through SUB local filter on — must subscribe XSUB local filter off — passes all
Subscription events PUB doesn't expose XPUB exposes them via zlink_xpub_recv()
Proxy suitability Proxy must manage topics itself Relay only — ideal for proxy

Key point: zlink_proxy() uses the same internal recv/send paths as the raw socket APIs, not the public zlink_send()/zlink_recv() surface. Through that public surface, zlink_send() on XSUB still returns ZLINK_SUBMIT_NOT_SUPPORTED and zlink_recv() on XPUB still returns ZLINK_RECV_NOT_SUPPORTED. Proxy operation is only possible via zlink_proxy() or the manual approach above (using the dedicated zlink_subscribe(), zlink_publish(), etc. APIs).

4. Request/Reply Proxy — ROUTER/DEALER

Client (DEALER) --> ROUTER == proxy ==> DEALER --> Server (ROUTER)
void *frontend = zlink_socket(ctx, ZLINK_SOCKET_ROUTER);
zlink_bind(frontend, "tcp://*:5559");

void *backend = zlink_socket(ctx, ZLINK_SOCKET_DEALER);
zlink_bind(backend, "tcp://*:5560");

zlink_proxy(frontend, backend, NULL);  /* blocking */

ROUTER/DEALER proxy has no subscription propagation, so zlink_proxy() alone is sufficient. For manual construction of the ROUTER-facing side, use zlink_router_recv()zlink_send_rid() (see the ROUTER guide for the full signature and a worked example). To relay a multipart record, use the whole-message zlink_router_recv() to receive it into an array and send that array unchanged.

5. Why Use a Proxy?

Direct (no proxy) -- N x M connections:

%%{init: {'flowchart': {'nodeSpacing': 32, 'rankSpacing': 40, 'padding': 8, 'wrappingWidth': 180}, 'themeVariables': {'fontSize': '18px'}}}%%
flowchart LR
    P1[PUB 1] --> S1[SUB 1]
    P1 --> S2[SUB 2]
    P2[PUB 2] --> S1
    P2 --> S2

PUB/SUB must know each other's addresses. Connection count = N x M.

With proxy -- N + M connections:

%%{init: {'flowchart': {'nodeSpacing': 32, 'rankSpacing': 40, 'padding': 8, 'wrappingWidth': 180}, 'themeVariables': {'fontSize': '18px'}}}%%
flowchart LR
    P1[PUB 1] --> XSUB
    P2[PUB 2] --> XSUB
    subgraph Proxy
        XSUB --> XPUB
    end
    XPUB --> S1[SUB 1]
    XPUB --> S2[SUB 2]

Only the proxy address is needed. Connection count = N + M.

Use Case Description
Reduce connections N×M → N+M
Address decoupling PUB/SUB don't need each other's endpoints
Dynamic scaling PUB/SUB add/remove independently
Subscription transformation XPUB MANUAL mode for topic remapping/filtering
Network bridging Connect different network segments (e.g., inproc ↔ tcp)
Monitoring Capture socket records all passing messages

← STREAM | Transport →