콘텐츠로 이동

가이드 목록 | 이전: Java | 다음: Python

Node.js 바인딩 가이드 (@zlink-systems/zlink)

이 장의 계약 소유 문서Node.js bindings 스펙이 다룬다. 이 장은 그 계약을 실제 샘플 코드로 보여준다.

Node.js에서 zlink를 쓰는 방법을 실제 샘플 코드 중심으로 설명합니다. 메시징 개념은 코어 가이드를 참고하세요.


설치

npm install @zlink-systems/zlink
  • Node.js 22 이상.
  • 네이티브 코어가 플랫폼별 prebuild로 번들됩니다.
const zlink = require('@zlink-systems/zlink');
// 또는 ESM / TypeScript
import * as zlink from '@zlink-systems/zlink';

5분 예제

const zlink = require('@zlink-systems/zlink');

// 서버
const ctx = zlink.createContext();
const server = zlink.createPairSocket(ctx);
server.bind('tcp://127.0.0.1:5555');

const received = new zlink.Received();
server.recv(received);
console.log(received.parts[0].data().toString()); // PING
received.close();

await server.send().message(Buffer.from('ACK')).submit().admitted;

server.close();
ctx.close();
// 클라이언트
const ctx = zlink.createContext();
const client = zlink.createPairSocket(ctx);
client.connect('tcp://127.0.0.1:5555');

await client.send().message(Buffer.from('PING')).submit().admitted;

const received = new zlink.Received();
client.recv(received);
console.log(received.parts[0].data().toString()); // ACK
received.close();

client.close();
ctx.close();

핵심 타입

컨텍스트

const ctx = zlink.createContext();
// 사용 후 반드시 close — 하위 소켓의 블로킹 작업이 중단됩니다
ctx.close();

메시지

Node 바인딩은 Buffer를 메시지로 직접 씁니다. message()를 호출하면 복사본을 만들기 때문에 원본 Buffer를 마음껏 재사용할 수 있습니다.

await socket.send().message(Buffer.from('hello')).submit().admitted;
await socket.send().message(Buffer.from([0x01, 0x02])).submit().admitted;

// 수신 후 페이로드 접근
const received = new zlink.Received();
socket.recv(received);
const data = received.parts[0].data();   // Buffer
const text = data.toString('utf8');
received.close();

HWM 대기 가능 send는 비동기 submit()과 동기 submit_sync() terminal을 제공합니다. Node 이벤트 루프에서는 결과 객체를 돌려주는 submit()을 기본으로 사용합니다. submit()SendSubmission(result: OK|BACKPRESSURED 동기 필드, admitted: Promise<void>)을 돌려주며 DONTWAIT을 사용하고 admission은 socket completion queue에서 settle됩니다. submit_sync()은 local admission까지 Core 안에서 blocking합니다.

const send = socket.send().message(Buffer.from('data')).submit(); // 결과 객체
if (send.result === SubmitResult.BACKPRESSURED) await send.admitted; // HWM일 때만 대기
socket.send().message(Buffer.from('data')).submit_sync();  // 동기 Core admission

Request는 reply까지 blocking하는 submit_sync()과, RequestSubmission(result·admittedreply: Promise<Message[]> 추가)을 돌려주는 submit()을 제공합니다. resultOK면 바로 reply를 기다리면 되고, reply는 terminal 결과이며 별도 DATA receive가 아닙니다.

Core가 pre-admission operation을 접수한 뒤 retry를 소유하므로 caller retry queue를 만들거나 payload를 재전송하지 않습니다. 공용 native ZLINK_OPT_PENDING_MAX_MSGS/BYTES 제한은 pending SEND와 REQUEST에 함께 적용되고 send 전용 pending 이름은 없습니다. Completion은 local admission일 뿐 peer delivery나 application acknowledgement가 아닙니다.

Submit 전 cancellation은 호출하지 않는 것으로 처리합니다. Successful submit 뒤에는 public Core cancel이 없으며 Promise 관찰을 버려도 socket owner가 늦은 completion을 drain합니다. Bind/connect 전에 stream.options.recvModezlink.StreamRecvMode.Raw 또는 .Packet으로 정한 뒤 각각 recv 또는 recvPacket을 사용합니다.

public poller가 socket의 zlink.PollEventFlag.PollCompletion owner이면 blocking request나 Promise가 남아 있는 동안 다른 thread가 wait() loop를 계속 실행해야 합니다. wait()가 native completion을 drain해 Node state를 settle/cleanup하므로 같은 thread에서 wait 사이에 blocking terminal을 호출하면 진행이 멈출 수 있습니다.

Received — 수신 봉투

const received = new zlink.Received();
socket.recv(received);                  // 동기 블로킹
try {
  const parts = received.parts;         // Message[]
  const rid = received.routingId;       // RoutingId 또는 null
  const token = received.replyToken;    // ROUTER request의 ReplyToken 또는 null
} finally {
  received.close();
}

라우팅 ID

const rid = zlink.RoutingId.from(Buffer.from('server-01'));
socket.setRoutingId(rid);

소유권과 수명

상황 규칙
submit() 성공 전달된 Buffer는 내부 복사되므로 원본 재사용 가능
recv() 성공 received.close() 필수 (finally 블록 권장)
submit()(Promise) 완료 회신 파트 배열을 각각 part.close()
ctx.close() 하위 소켓의 블로킹 작업 중단
const received = new zlink.Received();
socket.recv(received);
try {
  // 파트 처리
} finally {
  received.close();
}

공유·이전·복제 (copy / move / clone)

Message payload를 다루는 세 가지 명시적 동작입니다. 이름과 의미는 모든 바인딩에서 동일하며 Core C API(zlink_msg_copy/zlink_msg_move)와 1:1로 대응합니다.

동작 시그니처 의미 언제
copy() copy(): Message ref-count 공유 — 같은 버퍼를 가리키는 새 Message, 원본 유효 유지 같은 payload를 보관하며 원본도 계속 써야 할 때
move(dest) move(dest: Message): void 소유권 이전dest로 넘기고 호출자는 empty 받은 메시지를 사본 없이 그대로 다시 보낼 때(relay/echo)
clone() clone(): Message 깊은 복사 — 독립 버퍼 복제 후 payload를 독립적으로 수정할 때
// Copy: 같은 버퍼를 공유하는 새 핸들. 둘 다 각자 close.
const shared = msg.copy();
await socket.send().message(shared).submit().admitted;  // shared는 소비됨
// msg는 여전히 유효

// Move: 받은 메시지를 사본 없이 그대로 echo (가장 효율적)
const out = new zlink.Message();
receivedPart.move(out);                         // receivedPart는 empty가 됨
await socket.send(routingId).message(out).submit().admitted;

// Clone: 독립 복제 후 수정
const dup = msg.clone();

⚠ Breaking change: 기존 copy()는 깊은 복사였으나 이제 copy()ref-count 공유이고, 깊은 복사는 clone()으로 이동했습니다. JS는 동일 시그니처를 반환 의미만 달리해 공존시킬 수 없어 alias가 불가능하므로 major 버전 breaking으로 처리합니다. 기존 코드의 copy()(깊은 복사 의도)는 반드시 clone()으로 바꾸세요.

참고(refcount 타이밍): Node는 payload를 Buffer로 노출합니다. 노출된 Buffer가 살아 있는 동안 native 저장소는 그 Buffer가 GC될 때 정리됩니다. 그래서 copy()로 공유한 뒤 한쪽을 close()해도 refCount() 값은 즉시 1로 떨어지지 않고 버퍼 GC 후 반영됩니다(진단용 표시일 뿐, 동작·안전·소유권 독립엔 영향 없음).


에러 처리

Node 바인딩은 작업별 에러 클래스를 던집니다.

try {
  await socket.send().message(Buffer.from('data')).submit().admitted;
} catch (error) {
  if (error instanceof zlink.SubmitError) {
    if (error.result === zlink.SubmitResult.Backpressured) {
      // 재시도
    } else {
      throw error;
    }
  }
}

에러 클래스는 SubmitError, RequestError, RecvError, BindError, ConnectError, ConfigError, CloseError, HandlerError입니다. 각각 .result 속성으로 결과 코드를 노출합니다.


C API 대응표

C API Node API
zlink_ctx_new() zlink.createContext()
zlink_ctx_term() ctx.close()
zlink_socket(ctx, type) zlink.createPairSocket(ctx)
zlink_bind(s, ep) socket.bind(ep)
zlink_connect(s, ep) socket.connect(ep)
zlink_send(..., parts, count, ...) / zlink_send_rid(..., parts, count, ...) + NONE socket.send().message(buf).submit_sync()
DONTWAIT send + completion pull await socket.send().message(buf).submit().admitted
zlink_recv(..., parts_out, capacity, count_out, ...) socket.recv(received)
zlink_msg_data(msg) part.data() (Buffer)
zlink_routing_id_t zlink.RoutingId
zlink_socket_monitor_open(...) socket.monitorOpen([...])
zlink_poller_new() zlink.createPoller()
zlink_timer_new() zlink.createTimer()

네이티브 라이브러리 / 배포

네이티브 코어는 플랫폼별 prebuild로 패키지에 들어갑니다. 별도 빌드 없이 npm install만으로 동작합니다.

const [major, minor, patch] = zlink.version(); // [number, number, number]
console.log(`zlink ${major}.${minor}.${patch}`);

스레딩 유의사항. Node는 단일 스레드 이벤트 루프 모델입니다.

항목 규칙
Context·소켓 메인 이벤트 루프에서 사용
블로킹 recv() 이벤트 루프를 막으므로 짧게 사용하거나 논블로킹 + 폴러 권장
동기 submit_sync() HWM 대기 시 이벤트 루프 전체를 멈춤 — 이벤트 루프에서 사용하지 않음
비동기 submit() Promise 기반 — 이벤트 루프를 막지 않음

Node에서는 blocking send를 이벤트 루프에서 실행하지 않습니다. 비동기 submit()await하고 submit_sync()은 적절한 worker thread에서만 사용합니다.


샘플

bindings/node/samples/ 디렉터리의 검증된 샘플입니다.

파일 설명
pair_recv_sample.ts PAIR 송수신
dealer_router_recv_sample.ts DEALER/ROUTER 송수신
request_reply_sample.ts 요청/응답
pubsub_recv_sample.ts PUB/SUB 발행·구독
stream_recv_sample.ts STREAM 원시 TCP
stream_packet_sample.ts STREAM PACKET pull
monitor_recv_sample.ts 모니터 이벤트 수신

SPOT·Actor 예제는 core 바인딩이 아니라 framework 샘플이 다룬다 — 아래 더 보기의 서비스 링크를 본다.

cd bindings/node
npm run build
node dist-tools/samples/pair_recv_sample.js

JavaScript

JavaScript는 별도 네이티브 바인딩 없이 Node 바인딩(@zlink-systems/zlink)을 그대로 씁니다. 위의 설치·핵심 타입·소유권·에러·대응표가 똑같이 적용되고 TypeScript 타입 표기만 빠집니다.

  • 의존성: @zlink-systems/zlink(위와 동일). TypeScript 빌드 단계가 필요 없고 순수 .js로 바로 require한다.
const zlink = require('@zlink-systems/zlink');

const ctx = zlink.createContext();
const socket = zlink.createPairSocket(ctx);
// ... 사용 후 socket.close(); ctx.close();
  • 소유권: 명시적 close()로 정리한다(Node와 동일, GC에 의존하지 않는다).
  • 샘플: bindings/javascript/samples/(.js)에 Node 샘플과 같은 canonical 세트가 있다. Node 바인딩을 빌드한 뒤 node로 바로 실행한다.
cd bindings/node && npm run build      # 공유 런타임 빌드
cd ../javascript/samples
node pair_recv_sample.js                # 또는 ./run_samples.sh

코어 가이드의 언어 탭에는 JavaScript 칸이 따로 있어 메시징·서비스 사용법을 JavaScript 코드로 바로 볼 수 있다.


더 보기

소켓 패턴 - 소켓 패턴 개요PAIR · PUB/SUB · DEALER · ROUTER · STREAM · 프록시

서비스 - Framework 서비스 개요

운영 - 소켓 옵션 - TLS 보안 - 모니터링 - 스레드 안전성 - 메시지 API - 라우팅 ID