콘텐츠로 이동

가이드 목록 | 이전: STREAM | 다음: Transport 가이드

프록시 패턴

이 장이 답하는 것 — ROUTER·DEALER·PAIR와 PUB 계열 소켓을 조합해 메시지를 중계하는 proxy를 구성하는 방법을 설명한다. 개별 socket의 계약은 각 socket 스펙이 소유한다.

1. 개요

프록시는 두 소켓 사이에서 메시지를 중계하는 패턴이다. zlink_proxy()는 어떤 소켓 조합이든 동작하는 범용 유틸리티 함수이고, 공개 per-socket API를 조합하면 사용자가 직접 커스텀 프록시를 구성할 수도 있다.

zlink_config_result_t zlink_proxy (void *frontend, void *backend, void *capture);
  • frontendbackend 방향으로 메시지를 전달하고 동시에 반대 방향도 처리한다
  • capture가 NULL이 아니면 통과하는 모든 메시지를 capture 소켓에 복사한다
  • 블로킹 함수 — 별도 스레드에서 실행한다
  • 소켓 타입 제한 없음 — 내부적으로 raw socket API와 같은 socket_base_t recv/send 경로를 그대로 쓰므로, 공개 send/recv 표면이 ZLINK_SUBMIT_NOT_SUPPORTED / ZLINK_RECV_NOT_SUPPORTED를 반환하는 소켓 (예: XSUB, XPUB)을 짝지어도 proxy 안에서는 정상 동작한다

소켓 조합 예시

frontend backend 용도
XSUB XPUB PUB/SUB 중계 (가장 일반적)
ROUTER DEALER 요청/응답 브로커
DEALER DEALER 로드밸런싱 중계
PAIR PAIR 스레드 간 브릿지

3. PUB/SUB 프록시 — XSUB/XPUB

가장 일반적인 프록시 패턴이다.

%%{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 내장 프록시 사용

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()는 다음 두 가지를 내부에서 자동 처리한다: - 데이터 전달: XSUB에서 메시지를 꺼내 XPUB으로 전달 - 구독 전파: XPUB에서 구독 이벤트를 꺼내 XSUB으로 전파

3.2 수동 프록시 구성

중간에 로깅, 필터링, 토픽 변환 같은 맞춤 로직이 필요하면 공개 per-socket API인 zlink_subscribe() / zlink_publish()(데이터)와 zlink_xpub_recv() / zlink_set_subscription()(구독)으로 수동 프록시를 구성할 수 있다.

데이터 흐름

단계 소켓 API 설명
1 XSUB zlink_subscribe(xsub, ...) payload 전체를 배열로 수신 (토픽은 별도 반환)
2 커스텀 로직 필터링, 변환, 로깅 등
3 XPUB zlink_publish(xpub, topic, ...) payload 배열 전체 발행

구독 전파 흐름

단계 소켓 API 설명
1 XPUB zlink_xpub_recv(xpub, ...) SUB의 구독/해제 이벤트 수신
2 커스텀 로직 구독 인가, 토픽 재매핑 등
3 XSUB zlink_set_subscription(xsub, topic) upstream PUB에 구독 전파

전체 코드

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, record 전체를 한 번에 처리 */
    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.) */
        /* 받은 배열 전체를 한 호출로 넘겨 record 경계를 유지한다. */
        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 왜 XSUB/XPUB인가?

질문 SUB/PUB 사용 시 XSUB/XPUB 사용 시
데이터 통과 SUB 로컬 필터 켜짐 — 구독해야 통과 XSUB 로컬 필터 꺼짐 — 무조건 통과
구독 이벤트 관찰 PUB이 노출 안 함 XPUB이 zlink_xpub_recv()로 노출
프록시 적합성 프록시가 토픽을 직접 관리해야 함 중계만 하면 되므로 적합

핵심: zlink_proxy()는 raw socket API와 동일한 내부 recv/send 경로를 쓰며, 공개 zlink_send()/zlink_recv() 표면을 쓰지 않는다. 그 공개 표면으로는 여전히 XSUB에서 zlink_send()ZLINK_SUBMIT_NOT_SUPPORTED를, XPUB에서 zlink_recv()ZLINK_RECV_NOT_SUPPORTED를 반환한다. 프록시 동작은 zlink_proxy() 함수나 위의 수동 구성(전용 zlink_subscribe(), zlink_publish() 등 API 조합)으로만 가능하다.

4. 요청/응답 프록시 — 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 프록시는 구독 전파가 없으므로 zlink_proxy()만으로 충분하다. ROUTER 쪽을 수동으로 구성하려면 zlink_router_recv()zlink_send_rid() 조합을 사용한다(전체 시그니처와 예제는 ROUTER 가이드 참고). 멀티파트 record를 relay하려면 whole-message zlink_router_recv()로 배열에 받아 그대로 되보낸다.

5. 프록시가 필요한 이유

직접 연결 (프록시 없음) -- N x M 연결:

%%{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가 서로의 주소를 알아야 한다. 연결 수 = N x M.

프록시 사용 -- N + M 연결:

%%{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]

프록시 주소만 알면 된다. 연결 수 = N + M.

용도 설명
연결 수 감소 N×M → N+M
주소 분리 PUB/SUB가 서로의 endpoint를 몰라도 됨
동적 확장 PUB/SUB 독립 추가·제거
구독 변환 XPUB MANUAL 모드로 토픽 재매핑/필터링
네트워크 브리징 inproc ↔ tcp 같은 세그먼트 연결
모니터링 capture 소켓으로 통과 메시지 기록

← STREAM | Transport →