가이드 목록 | 이전: STREAM | 다음: Transport 가이드
프록시 패턴¶
이 장이 답하는 것 — ROUTER·DEALER·PAIR와 PUB 계열 소켓을 조합해 메시지를 중계하는 proxy를 구성하는 방법을 설명한다. 개별 socket의 계약은 각 socket 스펙이 소유한다.
1. 개요¶
프록시는 두 소켓 사이에서 메시지를 중계하는 패턴이다.
zlink_proxy()는 어떤 소켓 조합이든 동작하는 범용 유틸리티 함수이고,
공개 per-socket API를 조합하면 사용자가 직접 커스텀 프록시를 구성할 수도 있다.
2. zlink_proxy() — 내장 프록시¶
frontend→backend방향으로 메시지를 전달하고 동시에 반대 방향도 처리한다capture가 NULL이 아니면 통과하는 모든 메시지를 capture 소켓에 복사한다- 블로킹 함수 — 별도 스레드에서 실행한다
- 소켓 타입 제한 없음 — 내부적으로 raw socket API와 같은
socket_base_trecv/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¶
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 소켓으로 통과 메시지 기록 |