8.4.4 다양한 타입의 데이터 페이로드(Payload) 전송 기법
TS 바인딩에서 에러가 제일 많이 나는 곳이 바로 payload 파라미터다!
C/Rust와 달리 JS 세계에서는 바이너리(ArrayBuffer)와 텍스트(String)를 엄격히 구별한다. 하지만 Zenoh에게 당신의 데이터는 무조건 순백의 바이트 덩어리(Uint8Array/Buffer) 로 보여야 한다.
이 폴더에서는 프론트/백엔드 개발자가 가장 자주 마주하는 세 가지 데이터 형태(원시 String, 거대한 JSON 구조체, 무손실 바이너리)를 완벽하게 직렬화/역직렬화하여 라우터 그물에 던지는 테크닉과 런북을 개별 MD 파일로 쪼개어 서술한다.
1. 원시 문자열(String) 및 숫자 처리
간단한 제어 명령(“STOP”, “START”)이나 숫자 하나 정밀하게 보낼 때, 이걸 JSON으로 말고 거대한 오브젝트로 만들어 쏘는 건 대역폭 낭비다.
1.0.1 [Runbook] 심플 텍스트 인코딩 전술
브라우저 내장(혹은 Node 내장) 모듈인 TextEncoder 와 TextDecoder 만 쓰면 완벽히 호환된다.
// --- 송신부 (퍼블리셔 혹은 쿼리 리플라이) ---
const simpleCommand = "EMERGENCY_HALT";
// 1. 단순 문자를 UTF-8 바이너리 배열로 압축 해체
const encoder = new TextEncoder();
const payloadBytes = encoder.encode(simpleCommand);
// 2. 발사
pub.put(payloadBytes);
// --- 수신부 (서브스크라이버 콜백) ---
const decoder = new TextDecoder("utf-8");
const callback = (sample: zenoh.Sample) => {
// 1. 수신된 바이너리 덩어리(sample.payload)를 다시 V8 문자열 체계로 환원
const rawString = decoder.decode(sample.payload);
if (rawString === "EMERGENCY_HALT") {
console.error("긴급 제어 명령 수신!");
} else {
// 만약 숫자(ex: "98.5")가 날아왔다면 문자열로 받은 뒤 Float로 캐스팅한다.
const batteryLvl = parseFloat(rawString);
console.log(`Battery: ${batteryLvl}%`);
}
};
가장 빠르고 가볍다. 다만 C++ 노드와 통신할 경우, C++ 쪽에서 이 문자열을 받을 때 끝에 \0 (널 종료 문자)가 포함되어 있는지 여부를 조심해야 한다. TS TextEncoder는 순수 문자열만 바이트로 만들며 널 문자를 자동 삽입하지 않는다.
2. 복잡한 JSON 객체의 직렬화 및 역직렬화
사실 TypeScript 개발자가 가장 많이 쓸 수법은 이것이다. 웹 서버 백엔드, Redux 스토어, MongoDB 도큐먼트 등등 모든 것이 딕셔너리({}) 모델로 짜여 있으니까.
여기에 TypeScript 고유의 제네릭(Generic) 인터페이스를 더해, 패킷에 완벽한 “마법의 제약“을 건다.
2.0.1 [Runbook] TS 타입 세이프(Type-Safe) JSON 패킹 전술
import * as zenoh from "@eclipse-zenoh/zenoh";
// 1. 완벽한 명세서(Interface) 작성 (에러의 근원 차단)
interface DroneTelemetry {
droneId: string;
lat: number;
lng: number;
armed: boolean;
metadata: Record<string, any>;
}
// 송수신용 공통 인코더/디코더 장착
const encoder = new TextEncoder();
const decoder = new TextDecoder();
// --- 쏘는 놈 (Publisher) ---
async function broadcastTelemetry(pub: zenoh.Publisher, data: DroneTelemetry) {
// TS 가 data 객체의 구조를 완벽히 검증하고, JSON 바이트로 구워버린다.
const jsonStr = JSON.stringify(data);
await pub.put(encoder.encode(jsonStr));
console.log("JSON 객체 발사");
}
// --- 받는 놈 (Subscriber) ---
async function listenTelemetry(session: zenoh.Session) {
await session.declareSubscriber("drone/*/telemetry", (sample) => {
try {
// 1. 바이트를 문자로 풀고,
const rawJson = decoder.decode(sample.payload);
// 2. JSON.parse 객체를 DroneTelemetry 타입이라고 "단언(Cast)"한다!
const telemetryData = JSON.parse(rawJson) as DroneTelemetry;
// 이 순간부터 에디터(VSCode)는 telemetryData.lat 에 자동 완성을 지원해준다.
if (telemetryData.armed) {
console.log(`무장 상태 드론: ${telemetryData.lat}, ${telemetryData.lng}`);
}
} catch (e) {
// 만약 C++ 드론이 쓰레기값을 보내서 JSON 파싱이 터져도
// TS 서버 전체가 죽지 않도록 방어막 전개
console.error("파싱 불가능한 더미 JSON 패킷 수신됨", e);
}
});
}
이 방법은 웹 개발자들에게 최고의 환희(생산성)를 안겨주지만, 데이터 한 건당 중괄호 배열과 딕셔너리 키 문자열들이 통신 용량(Overhead)으로 추가된다는 점을 잊지 말라. (1초에 1천 건 이상 쏠 때는 8.4.4.3 장으로 넘어가야 한다.)
3. Uint8Array를 활용한 고속 바이너리 데이터 스트리밍
카메라 센서의 이미지 프레임 스트림, 압축된 .tar.gz 펌웨어 바이너리 조각, 혹은 C++ 들이 던진 팩 구조체(Packed Struct)를 주고받을 때 텍스트나 JSON은 절대 금물이다. 메모리 복사와 인코딩 지연이 발생한다.
TypeScript(JS)의 뷰어인 DataView와 Uint8Array를 활용해 하부 메모리를 직접 칼질하는 기법이다.
3.0.1 [Runbook] 영거리 V8 메모리 투척 전술
// --- C++ 측이 보낸 바이너리 예시 ---
// float x (4 byte), float y (4 byte) = 총 8 byte 패킷을 생으로 쐈다고 치자.
const callback = (sample: zenoh.Sample) => {
// sample.payload 는 이미 TS 환경에서 Uint8Array 계열의 객체다.
const buffer = sample.payload.buffer; // 밑바닥 ArrayBuffer 덩어리를 꺼냄!
const byteOffset = sample.payload.byteOffset;
const byteLength = sample.payload.byteLength;
// DataView 를 씌워서, 이 바이트 덩어리를 "C언어 포인터"처럼 쪼갠다!
// JSON 파싱이고 나발이고 없이, 메모리 읽기 한 줄로 끝나는 극강의 퍼포먼스.
const dv = new DataView(buffer, byteOffset, byteLength);
// 첫 0번지부터 4바이트 긁어서 실수(Float)로 만듦. (C++ 메모리가 리틀 엔디안이라 가정)
const x = dv.getFloat32(0, true);
// 그 다음 4번지부터 4바이트 긁음.
const y = dv.getFloat32(4, true);
console.log(`[초고속 파싱 완료] X: ${x.toFixed(2)}, Y: ${y.toFixed(2)}`);
};
// --- 이번엔 반대로 프론트엔드가 생으로 쏠 때 ---
function sendFastBinary(pub: zenoh.Publisher) {
// 정확히 8바이트 메모리 공간을 V8 힙에 파냄
const buffer = new ArrayBuffer(8);
const dv = new DataView(buffer);
dv.setFloat32(0, 100.5, true); // x
dv.setFloat32(4, -80.2, true); // y
// 이 8바이트 껍데기만 포장해서 발사! (JSON 변환 오버헤드 0%)
pub.put(new Uint8Array(buffer));
}
이 기술을 응용하면 브라우저의 HTML <canvas> 에서 그리는 화면 이미지용 Uint8ClippedArray(픽셀 데이터)를 Zenoh망 너머의 클라우드로 프레임 지연 없이 통째로 쏴버리는 “엣지 - 클라우드 브라우저 화면 동기화 파이프라인” 도 쉽게 그려낼 수 있다.