콘텐츠로 이동

한국어 | English

레퍼런스 목차

02. Messaging

이 category는 message 소유권, receive envelope 타입(Received, TopicMessage, SubscriptionEvent), 그리고 모든 socket type 진입점이 반환하는 공유 send/request/reply operation-builder family를 다룬다. 정확한 signature는 contracts/messaging/가 소유한다.


Message

native zlink frame 하나를 소유한다 — 모든 send·request·reply·receive API가 옮기는 단위다. Java는 borrowed-payload wrapper를 노출하지 않는다 — native queue lifetime이 Java object reachability로 안전하게 bound되지 않기 때문이다 — 모든 from(...) factory는 message 소유 storage로 복사한다.

Message empty = new Message();
Message sized = new Message(4096);
Message copy = Message.from("payload".getBytes(StandardCharsets.UTF_8));
Message fromString = Message.from("hello");

옵션.

Member 의미
Message() 빈 메시지
Message(int size) 쓰기 가능 storage
allocate(int) static factory, Message(int)와 동일
from(byte[]) / from(byte[] data, int offset, int length) 전체 배열 복사, 또는 선택한 범위 복사
from(Message) 다른 메시지의 payload 복사
from(String) UTF-8 인코딩
from(ByteBuffer) source cursor를 바꾸지 않고 남은 byte 복사
from(io.netty.buffer.ByteBuf) Netty interop, source cursor를 바꾸지 않고 readable byte 복사
size() payload의 바이트 길이
more() multipart-continuation flag
refCount() native reference count, 진단 전용
empty()/isEmpty() size()가 0인지
data()/toByteArray() payload를 byte[]로 복사
toUtf8String() payload를 UTF-8로 디코딩
dataBuffer() 이 인스턴스 storage에 backing된 읽기 전용 ByteBuffer view
mutableDataBuffer() 이 인스턴스 storage에 backing된 쓰기 가능 ByteBuffer view
copyTo(byte[]) / copyTo(byte[], int offset) / copyTo(byte[] dst, int srcOffset, int dstOffset, int length) payload(또는 범위)를 caller가 제공한 배열로 복사
copyTo(ByteBuffer) / copyTo(ByteBuf) payload를 caller가 제공한 buffer로 복사
tryCopyTo(ByteBuffer) / tryCopyTo(ByteBuf) bounds-check 버전, 예외 대신 boolean 반환
copyFrom(byte[]|Message, int srcOffset, int dstOffset, int length) source에서 이 메시지 storage로 byte 복사
readByte/readIntLe/readIntBe/readLongLe/writeByte/writeIntLe/writeIntBe/writeLongLe wire format을 message storage에 직접 파싱·쓰기하는 in-place binary accessor
fill(byte) / fill(byte, offset, length) payload(또는 범위)를 반복되는 byte로 덮어씀
contentEquals(byte[]) payload 동등성 확인
closeAll(Message[]) / closeAll(Iterable<? extends Message>) static; 한 호출로 모든 part를 닫으며 개별 close 실패는 조용히 무시

완료 결과. 모든 member는 동기다. Message implements AutoCloseable이다. 메시지를 보내면 native frame이 socket으로 이전돼 이후 읽기에 대해 instance가 무효화된다 — 보내지 않을 메시지를 해제하려면 close()를 쓴다. 범위를 벗어난 offset/length는 IndexOutOfBoundsException을 던진다.

선택 기준. outbound payload를 만들 땐 크기 지정 생성자나 복사하는 from(...) factory를 쓴다. wire format을 중간 byte[] 없이 message storage에 직접 파싱·쓰기하려면 in-place binary accessor(readIntLe 등)를 쓴다. 수신되거나 구성된 multipart 배열의 모든 part를 손으로 짠 loop 대신 한 호출로 해제하려면 closeAll(...)을 쓴다.


Received

recv 결과 하나를 집계한다: 선택적 routing id, opaque reply token, 소유한 message part. 반환된 parts() view는 불변이며 밑에 깔린 배열을 복사하지 않는다.

Received received = new Received();
if (router.recv(received)) {
    received.replyToken().ifPresent(token ->
        received.reply().message(Message.from("ok")).submit());
}

옵션. caller-provided storage용 public 인자 없는 생성자 Received() — binding이 매 성공적인 receive마다 내부 상태를 그 자리에서 덮어쓴다(receive마다 할당을 피함).

Member 반환 의미
getRoutingId() Optional<RoutingId> receive 경로가 제공할 때만 존재
replyToken() Optional<ReplyToken> ROUTER request에 있는 opaque reply capability
parts() List<Message>, 불변 view 이 envelope이 담은 모든 message part
isSinglePart() boolean parts()가 정확히 하나인지
firstPart() Message 첫 part, 소유권 이전 없음
singlePartOrThrow() Message 단일 part, parts()가 정확히 하나가 아니면 예외
reply() builder 공유 ReplyOperation 시작; 유효한 reply context가 없으면 submit()에서 ZlinkSubmitException
send() builder 공유 SendOperation 시작, 이 envelope이 포착한 source route로 향함

Received implements AutoCloseable이며, close()는 소유한 모든 part를 닫는다.

완료 결과. 모두 동기다. firstPart()/singlePartOrThrow()는 각각 데이터가 없거나 part 개수가 맞지 않을 때 ZlinkRecvException을 던진다 — Errors category에 문서화된 receive측 result code를 그대로 반영한다.

선택 기준. message마다 새로 생성하는 대신 receive loop 전체에서 Received 하나를 재사용한다. reply()를 호출하기 전에 replyToken()으로 envelope이 실제로 reply 가능한지 확인한다.


TopicMessage

raw subscription 경로가 쓰는 topic-aware recv 결과 — 수신된 publish의 topic, source routing id, message part.

TopicMessage published = new TopicMessage();
if (sub.subscribe(published)) {
    String topic = published.topic();
}

옵션. public 인자 없는 생성자 TopicMessage().

Member 반환 의미
getRoutingId() Optional<RoutingId> 발행자의 routing id, receive 경로가 제공할 때만 존재
topic() String 이 publish가 전송된 topic
parts() List<Message> 이 publish가 담은 모든 message part
isSinglePart() / firstPart() / singlePartOrThrow() Received와 같은 형태

TopicMessage implements AutoCloseable.

완료 결과. 동기다. firstPart()/singlePartOrThrow()Received의 대응 메서드와 같은 방식으로 ZlinkRecvException을 던진다.

선택 기준. Received와 같은 방식으로 subscribe-receive loop 전체에서 instance 하나를 재사용한다.


SubscriptionEvent / SubscriptionEntry

XPUB socket이 관찰한 구독자 한 명의 subscribe·unsubscribe를 보고하고, 활성 구독 항목 하나를 기술한다.

SubscriptionEvent evt = new SubscriptionEvent();
if (xpub.receiveSubscriptionEvent(evt)) { /* ... */ }

옵션. SubscriptionEvent() public 인자 없는 생성자.

타입 Member 의미
SubscriptionEvent getRoutingId()(Optional<RoutingId>) 구독자의 routing id, receive 경로가 제공할 때만 존재
topic()(String) 구독·구독 취소된 topic
subscribed()(boolean) 구독이면 true, 구독 취소면 false
SubscriptionEntry(String filter, boolean pattern) record 활성 구독 하나
filterBytes() filter를 UTF-8로 인코딩
fromBytes(byte[], boolean) filter를 UTF-8 byte에서 복원하는 static factory

완료 결과. 둘 다 async 동작이 없는 순수 데이터 홀더다. SubscriptionEventclose()가 없다 — native resource를 소유하지 않는다.

선택 기준. XPUB socket의 subscription-event receive 경로(Sockets category)에서 구독자 변동을 관찰할 때 쓴다. SubscriptionEntry는 socket의 subscription-snapshot 조회(Sockets category)의 반환 타입이다.


Send / request / reply operation-builder 형태

모든 socket type의 send/publish/request/reply 진입점(Sockets category)이 part·flag·terminal submit을 누적하기 위해 반환하는 fluent builder. 모든 builder interface는 공유 MessageBuilderStage<TSubmit>(TSubmit message(Message part))를 확장하며, request family는 추가로 TimeoutSubmitOperation<List<Message>>을 확장한다.

dealer.send().message(part1).message(part2).submit();

CompletionStage<List<Message>> future = dealer.request()
    .message(Message.from("payload"))
    .timeout(Duration.ofSeconds(5))
    .submit();
List<Message> reply = future.toCompletableFuture().join();

// 또는 blocking 가능한 thread에서:
List<Message> reply2 = dealer.request().message(Message.from("payload")).submit_sync();

received.reply().message(Message.from("ok")).submit();

옵션.

단계 Member 의미
SendOperation .message(Message) chain 시작
SendSubmitOperation .message(...) / .submit_sync() / .submit() part 추가 뒤 blocking 또는 CompletionStage terminal 선택
RequestOperation/RequestSubmitOperation Send와 동일 + .timeout(Duration) send chain을 그대로 반영하며 reply 대기 timeout을 더함
RequestSubmitOperation terminal .submit_sync() / .submit() blocking reply 또는 completion-backed CompletionStage reply 결과
ReplyOperation/ReplySubmitOperation Send와 같은 형태 flags 단계가 없음 — 밑바탕 reply 함수가 send-flag 인자를 받지 않음
TimeoutSubmitOperation .timeout(Duration) request reply timeout만 설정하며 send deadline을 추가하지 않음

완료 결과.

Terminal 반환 의미
SendSubmitOperation.submit_sync() void Core local admission까지 blocking, 실패하면 ZlinkException
SendSubmitOperation.submit() CompletionStage<Void> DONTWAIT submit을 socket completion queue에서 settle
ReplySubmitOperation.submit() void 실패하면 ZlinkException을 던짐
RequestSubmitOperation.submit() CompletionStage<List<Message>> caller가 reply message를 소유하며 반드시 close해야 함
RequestSubmitOperation.submit_sync() List<Message> completion queue가 reply를 낼 때까지 blocking, caller가 part close

모든 builder는 성공적인 submit에서만 누적된 Message part를 소비한다 — 실패 시 소유권은 caller에게 복원된다.

선택 기준. 일반 async 코드에선 submit()CompletionStage를 쓰고 blocking 가능한 thread에서는 submit_sync()를 쓴다. 목적지 route를 손으로 재구성하는 대신 Received.reply()/send()를 쓴다.


contracts/messaging/Java 바인딩 스펙에서 전체 근거를 확인한다.