콘텐츠로 이동

한국어 | English

레퍼런스 목차

03. Sockets

이 category는 socket_t(모든 구체 socket type이 파생하는, public 생성이 불가능한 공유 기반), common_socket_options_t와 타입별 서브클래스, 8개 구체 socket type, send/publish/request/reply는 Messaging category에 문서화된 operation-builder family를 반환한다 — 이 category는 각 builder가 어디서 시작하고 각 구체 타입이 고유하게 무엇을 더하는지만 다룬다. dotnet의 ISocket/ IStreamSocket interface와 달리 C++는 기본적으로 role interface를 노출하지 않는다 — 각 socket type은 구체 RAII class이며, socket_t 자체는 public 생성이 불가능하다 (생성자가 protected). 정확한 signature는 Contracts/Sockets/가 소유한다.


socket_t 공유 기반

모든 구체 socket type이 파생하는, public 생성이 불가능한 기반 — lifetime, binding, TLS, monitoring. data-plane send/receive/publish는 여기선 protected다 — 아래 각 구체 socket type이 필요한 부분집합을 public method로 재노출한다.

socket.bind ("tcp://*:5555");
socket.set_tls_server (cert_path, key_path, /*require_client_cert=*/true);
zlink::socket_monitor_t monitor = socket.monitor_open (zlink::monitor_event::all);
socket.close ();

옵션.

Member 기본값 의미
valid() 이 socket이 아직 사용 가능한지
close() native socket을 즉시 해제
bind(const std::string&) / unbind(const std::string&) 주소에서 수신 시작/중지
connect(const std::string&) / disconnect(const std::string&) peer 주소로 connect/disconnect
disconnect_rid(const routing_id_t&) 그 routing id로 식별되는 peer의 connection을 끊음
monitor_open(monitor_event events_) const monitor_event::all socket_monitor_t 반환(Eventing category)
options() common_socket_options_t(아래) 반환
set_tls_server(cert, key, require_client_cert) require_client_cert = false bind 전에 적용
set_tls_client(ca_cert, hostname, trust_system) trust_system = false connect 전에 적용

완료 결과. valid()/monitor_open()/options()를 제외한 모든 member는 반환값 없이 동기다. socket_t는 move-only다(복사는 delete) — 소멸자는 암묵적으로 close하지 않는다.

선택 기준. bind/connect 전에 각각 set_tls_server/set_tls_client를 호출한다. socket_t 자체가 아니라 구체 socket type을 직접 생성한다 — 기반 class는 public 생성자가 없다.


common_socket_options_t와 타입별 option facade

socket.options()로 도달하는, 모든 socket type이 공유하는 typed option facade.

socket.options ().send_hwm (zlink::byte_count_t::bytes (100'000));
socket.options ().linger (std::chrono::seconds (1));
socket.options ().submit_retry_mode (zlink::submit_retry_mode_t::local_failure);

옵션. common_socket_options_t:

Member 타입 의미
linger() std::chrono::milliseconds close()가 대기 중인 send가 flush될 때까지 기다리는 상한
send_hwm() / recv_hwm() byte_count_t, accounted-byte 제한 send/receive queue 제한 — Core category의 byte-HWM 참고
send_timeout() / recv_timeout() / connect_timeout() std::chrono::milliseconds 대응하는 blocking operation이 기다리는 상한
immediate() bool send가 지금 살아있는 connection을 요구할지, 아니면 생길 때까지 대기열에 쌓을지
ipv6() bool socket이 IPv6 connection을 받을지
tcp_no_delay() bool true면 Nagle 알고리즘을 끔
tcp_keepalive() tcp_keepalive_mode_t OS TCP keepalive 모드
rid_duplicate_policy() rid_duplicate_policy_t peer가 기존 routing id를 재사용하면 어떻게 되는지
max_message_size() byte_size_t 단일 수신 메시지의 최대 바이트 크기
backlog() socket_backlog_t listening socket의 대기 connection queue 길이
reconnect_interval() / reconnect_interval_max() std::chrono::milliseconds 재연결 시도 사이 간격, 그리고 그 상한
submit_retry_mode() submit_retry_mode_t local back-pressure에서 실패한 submit을 자동 재시도할지
submit_retry_timeout() std::chrono::milliseconds submit_retry_mode()local_failure일 때의 재시도 timeout
submit_retry_attempts() int submit_retry_mode()local_failure일 때의 재시도 횟수 상한
last_endpoint() std::string, 읽기 전용 실제로 resolve된 bind 주소

타입별 서브클래스(각각 대응하는 socket type의 참조로 생성):

타입 더하는 것
router_socket_options_t mandatory(), handover(), probe(), connect_routing_id()(std::optional<routing_id_t>), request_timeout(), peer_weight()(peer_weight_t)
dealer_socket_options_t probe(), request_timeout(), peer_weight()
stream_socket_options_t notify()(bool)
pub_socket_options_t verbose()/verboser()/no_drop()/manual()/manual_last_value()(bool), welcome_message()(message_t), approve_subscribe(const routing_id_t&)/reject_subscribe(const routing_id_t&), topics_count()(int)
sub_socket_options_t topics_count()

완료 결과. 모든 getter/setter는 동기다.

선택 기준. 기본값이 배포 환경에 맞지 않을 때 socket이 메시지 교환을 시작하기 전에 send_hwm/recv_hwm, linger를 설정한다.


pair_socket_t

라우팅이 없는 배타적 1:1 peering socket.

zlink::pair_socket_t pair (ctx);
std::move (pair.send ()).message (part).submit ();
zlink::received_t received;
if (pair.recv (received) == 0) { /* ... */ }

옵션.

Member 기본값 의미
explicit pair_socket_t(context_t&) 그 context에 묶인 socket을 생성
send() 공유 send_operation_t builder 시작
recv(received_t&, recv_flags_t) / recv(message_t&, recv_flags_t) recv_flags_t::none 뒤는 single-part shortcut

완료 결과. recvint를 직접 반환한다 — 성공하면 0, receive 실패나 no data면 recv_result_t 값, binding-local 실패에서만 errno가 설정된 채 -1이다(dotnet의 bool이 아니라 이 int 반환 관례는 이 category의 모든 구체 socket type의 recv가 공유한다).

선택 기준. 배타적 point-to-point 링크엔 PAIR를 쓴다 — peer 라우팅이 없고 load-balance하지 않는다.


dealer_socket_t

연결된 peer 전체에 send를 load-balance하고 routed request를 낼 수 있다.

zlink::dealer_socket_t dealer (ctx);
dealer.set_routing_id (zlink::routing_id_t::from (std::string ("worker-3")));
auto reply = std::move (dealer.request ()).message (payload).async ().get ();

옵션.

Member 기본값 의미
explicit dealer_socket_t(context_t&) 그 context에 묶인 socket을 생성
send() / recv(received_t&, recv_flags_t) / recv(message_t&, recv_flags_t) recv_flags_t::none pair_socket_t와 같은 형태
request() 공유 request_operation_t 시작; target 인자 없음 — DEALER는 API 레벨 peer routing id가 없음
set_routing_id(const routing_id_t&) / get_routing_id(routing_id_t&) const 이 socket 자신의 routing id를 지정/읽음, peer가 connect 시 관찰
options() dealer_socket_options_t 반환

완료 결과. recvpair_socket_t와 같은 int 관례를 따른다.

선택 기준. peer가 첫 메시지부터 관찰하도록 connect 전에 set_routing_id를 설정한다. DEALER는 임의 token에 reply할 protocol envelope helper가 없다 — 수신된 request context(received_t::reply())나 명시적 ROUTER reply 표면에서 답한다.


router_socket_t

routing id로 지정된 peer에게 메시지를 보내고, 특정 peer의 request에 reply할 수 있다.

zlink::router_socket_t router (ctx);
std::move (router.send (peer_rid)).message (part).submit ();
std::move (router.reply (peer_rid, reply_token)).message (reply).submit ();

옵션.

Member 기본값 의미
explicit router_socket_t(context_t&) 그 context에 묶인 socket을 생성
send(const routing_id_t&) 그 peer로 향하는 공유 send_operation_t 시작
recv(received_t&, recv_flags_t) recv_flags_t::none 다음 메시지로 envelope을 채움
recv(routing_id_t& source_rid_out_, message_t& part_out_, recv_flags_t) recv_flags_t::none pull 기반 single-part receive; caller가 오래 사는 received_t를 유지해 storage를 재할당 없이 재사용 가능
request(const routing_id_t&) Messaging category의 request_operation_t, 특정 peer로 향함
reply(const routing_id_t&, reply_token_t) opaque token이 식별하는 수신 request에 답하는 reply_operation_t
recv(...) reply token received_t::reply_token()이 token을 제공하고 received_t::reply()가 source RID와 함께 포착
request terminal reply 또는 terminal failure는 application DATA가 아니라 request(...).submit()/.async() 결과
set_routing_id(const routing_id_t&) / get_routing_id(routing_id_t&) const 이 socket 자신의 routing id를 지정/읽음, peer가 connect 시 관찰
options() router_socket_options_t 반환

완료 결과. recv는 위 int 관례를 따른다. Request terminal은 socket completion queue에서 settle된다.

선택 기준. DEALER가 특정 peer를 지정할 수 없는 ROUTER 주도·ROUTER 응답 request/reply엔 request(peer_rid)/reply(rid, reply_token)을 쓴다. Token은 receive에서 얻는 opaque socket-bound metadata이며 합성하지 않는다.


pub_socket_t / xpub_socket_t

PUB는 매칭되는 구독자가 없으면 버리는 topic-filtered 메시지를 publish하고, XPUB는 추가로 구독자의 subscribe/unsubscribe event를 노출한다. 둘 다 내부 publisher_socket_t 기반(그 자체는 public 생성 불가)에서 파생한다.

zlink::pub_socket_t pub (ctx);
std::move (pub.publish ("prices")).message (tick).submit ();

zlink::xpub_socket_t xpub (ctx);
zlink::subscription_event_t evt;
if (xpub.receive_subscription_event (evt) == 0) { /* ... */ }

옵션.

Member 기본값 의미
explicit pub_socket_t(context_t&) 그 context에 묶인 socket을 생성
publish(const std::string& topic_id_) 공유 send_operation_t 시작
options() pub_socket_options_t 반환
receive_subscription_event(subscription_event_t&, recv_flags_t) recv_flags_t::none 다음 subscribe·unsubscribe로 event를 채움; xpub_socket_t

pub_socket_t는 이 투영에서 set_routing_id/get_routing_id가 없다(둘 다 있는 dotnet의 IPubSocket과 다르다) — xpub_socket_t도 마찬가지다.

완료 결과. receive_subscription_event는 위와 같은 관례로 int를 반환한다.

선택 기준. receive_subscription_event로 구독자 변동을 관찰하거나 pub_socket_options_t::manual()/approve_subscribe/reject_subscribe로 수동 admission을 하려면 특별히 xpub_socket_t를 쓴다. 그 외엔 publish 자체는 둘이 같게 동작한다.


sub_socket_t / xsub_socket_t

SUB는 구독을 socket option으로 설정하는 방식으로 topic을 구독하고, XSUB는 대신 구독을 메시지로 실어 나른다. 둘 다 내부 subscriber_socket_t 기반에서 파생한다 — 하지만 각 구체 타입이 다른 signature로 자신만의 public overload를 다시 선언하므로, 기반의 형태는 caller가 직접 쓰는 public contract가 아니라 내부 배관으로 취급한다.

zlink::sub_socket_t sub (ctx);
sub.set_subscription ("prices.");
zlink::topic_message_t msg;
if (sub.subscribe (msg) == 0) { /* ... */ }

옵션. sub_socket_t:

Member 기본값 의미
explicit sub_socket_t(context_t&) 그 context에 묶인 socket을 생성
set_subscription(const std::string&) / unset_subscription(const std::string&) topic filter를 추가/제거; 구독은 누적된다; void 반환, base의 [[nodiscard]] int와 다름
subscription_at(size_t, std::string&, bool* = nullptr) 해당 index의 filter를 출력 인자에 씀
subscription_at(size_t) 값 반환 overload, subscription_filter_t 반환
subscribe(topic_message_t&, recv_flags_t) recv_flags_t::none 다음 매칭 publish로 envelope을 채움; int 반환, base의 예외를 던지는 값 반환 형태가 아님
subscribe_part(std::optional<routing_id_t>& source_rid_out_, std::string& topic_out_, message_t& part_out_, bool& has_more_out_, recv_flags_t) recv_flags_t::none pull 기반 single-part subscribe receive
options() sub_socket_options_t 반환

xsub_socket_tsub_socket_t와 member 집합이 동일하다 — 모든 method가 변경 없이 상속되는 게 아니라 각각 따로 재선언돼 있지만, SUB/XSUB 자체가 뜻하는 것 이상으로 둘 사이에 동작이 다른 member는 없다.

완료 결과. subscribe/subscribe_part는 위와 같은 관례로 int를 반환한다.

선택 기준. 일반적인 경우엔 sub_socket_t를 쓴다. 구독을 일반 메시지로 실어 날라야 할 때만 xsub_socket_t를 쓴다.


stream_socket_t

다른 모든 socket type이 쓰는 zlink wire protocol 밖에서, raw TCP peer와 framed packet을 직접 주고받는다.

zlink::stream_socket_t stream (ctx);
stream.options ().recv_mode (zlink::stream_recv_mode_t::packet);
zlink::stream_packet_t packet;
bool ok = stream.recv_packet (packet, zlink::recv_flags_t::none);

옵션.

Member 기본값 의미
explicit stream_socket_t(context_t&) 그 context에 묶인 socket을 생성
send(const routing_id_t&) 그 peer로 향하는 공유 send_operation_t 시작
recv(received_t&, recv_flags_t) recv_flags_t::none stream_recv_mode_t::raw에서 다음 raw record pull
recv_packet(stream_packet_t&, recv_flags_t) recv_flags_t::none stream_recv_mode_t::packet에서 source RID와 header/body packet 하나 pull
set_routing_id(const routing_id_t&) / get_routing_id(routing_id_t&) const 이 socket 자신의 routing id를 지정/읽음, peer가 connect 시 관찰
options() stream_socket_options_t 반환

완료 결과. recv는 위 int 관례를 따르고 recv_packet은 DONTWAIT no-data에서만 false이며, 그 외 성공은 packet message를 stream_packet_t에 이전한다.

선택 기준. Bind/connect 전에 options().recv_mode(...)로 RAW 또는 PACKET을 정하고 일치하는 pull receive family만 호출한다.


두 socket 사이의 양방향 message-forwarding loop을 실행한다(선택적으로 control socket을 통해 조종 가능). socket method가 아니라 자유 함수다 — static facade가 아니라(dotnet의 Zlink.Proxy(...)와 다르게) 이 category에서 socket_t 옆에 선언된다.

zlink::proxy (frontend, backend);
zlink::proxy (frontend, backend, capture);

옵션.

Member 의미
proxy(socket_t& frontend_, socket_t& backend_) context가 종료될 때까지 두 socket 사이에 메시지를 전달
proxy(socket_t&, socket_t&, socket_t& capture_) 같지만, 전달되는 모든 메시지의 복사본을 capture_로도 보냄

control 명령이나 에러가 loop을 끝낼 때까지) 호출 스레드를 block한다 — 둘 중 하나를 전용 스레드에서 실행한다.

선택 기준. 단순한 fire-and-forget forwarding loop엔 proxy를, application이 다른 스레드에서 control socket을 통해 loop을 일시정지·재개·종료해야


Socket enum과 flag

위 모든 항목에서 참조하는 공유 타입.

타입 사용처
socket_type 내부 socket 종류 식별 any, pair, pub, sub, dealer, router, xpub, xsub, stream
rid_duplicate_policy_t common_socket_options_t::rid_duplicate_policy, router_socket_options_t::handover reject, handover
submit_retry_mode_t common_socket_options_t::submit_retry_mode off, local_failure
tcp_keepalive_mode_t common_socket_options_t::tcp_keepalive os_default, off, on
send_flags_t(static member를 가진 class, enum이 아님) synchronous publish flag none, dontwait
recv_flags_t(static member를 가진 class, enum이 아님) 모든 recv/subscribe/receive_subscription_event none, dontwait
send_result_t non-blocking send 시도의 결과 sent, backpressured, not_ready
submit_result_t submit_error_t로 던져짐(Errors category) zlink_submit_result_t를 반영(Errors category 참고)
recv_result_t recv-family 호출이 실패·no-data 시 반환하는 int ok, no_data(201), busy(202), terminated(203), invalid_handle(204), not_supported(205), internal_error(206)

선택 기준. send_flags_t/recv_flags_t는 scoped enum class 타입이 아니라 int를 감싸는 class다 — static const member(send_flags_t::dontwait)를 쓴다, dotnet의 [Flags] enum SendFlags와 다르다. 둘 중 어느 쪽이든 dontwait는 blocking 호출을 non-blocking으로 바꿔 block하는 대신 back-pressure/no-data를 보고한다.


Contracts/Sockets/C++ 바인딩 스펙에서 전체 근거를 확인한다.