ROUTER Socket¶
This chapter's contract-owning document — the ROUTER socket spec owns the contract. This chapter shows that contract through language examples.
1. Overview¶
ROUTER is an asynchronous raw socket that manages connections (pipes) to multiple peers on one socket. Every inbound message carries the sender's routing id, and every outbound message must name a target routing id. Use it when one socket must address multiple DEALER or ROUTER peers individually, rather than round-robin like DEALER.
Key characteristics:
- Receive: every record carries the sender's routing id and an opaque reply token
- Send: directed only — the caller selects the peer by routing id
- Two traffic shapes on one socket: ordinary DATA (reply token 0) and REQUEST
records that expect a reply (nonzero token)
Valid socket combinations: ROUTER ↔ DEALER, ROUTER ↔ ROUTER
%%{init: {'flowchart': {'nodeSpacing': 32, 'rankSpacing': 40, 'padding': 8, 'wrappingWidth': 180}, 'themeVariables': {'fontSize': '18px'}}}%%
flowchart LR
R[ROUTER] -->|by routing id| D1[DEALER 1]
R -->|by routing id| D2[DEALER 2]
D1 -->|fair-queue| R
D2 -->|fair-queue| R
2. Basic Usage¶
Creation and Binding¶
Receiving a Message¶
zlink_router_recv() returns the complete payload record in a caller-provided array. The routing-id view remains valid
until the next data-receive entry on the same socket. Copy it when it must outlive that call.
const zlink_routing_id_t *source_rid = NULL;
zlink_reply_token_t reply_token = 0;
zlink_msg_t parts[8];
size_t part_count = 0;
zlink_recv_result_t rc = zlink_router_recv(
router, &source_rid, &reply_token, parts, 8, &part_count, ZLINK_RECV_FLAGS_NONE);
if (rc == ZLINK_RECV_OK) {
/* source_rid selects the peer. Process and close the complete array. */
zlink_multipart_close(parts, part_count);
}
/* other rc values: ZLINK_RECV_NO_DATA (EAGAIN), TERMINATED, INVALID_HANDLE */
For ordinary routed DATA, reply_token is zero. A nonzero token identifies a REQUEST that must
be answered with zlink_reply() (see §4) rather than
zlink_send_rid(); the application does not interpret the token.
Sending Routed Data¶
zlink_send_rid() sends the complete part array as one record to the peer
identified by target_rid_.
zlink_msg_t parts[2];
zlink_msg_init_size(&parts[0], 6);
memcpy(zlink_msg_data(&parts[0]), "header", 6);
zlink_msg_init_size(&parts[1], 4);
memcpy(zlink_msg_data(&parts[1]), "body", 4);
zlink_submit_result_t rc = zlink_send_rid(
router, source_rid, parts, 2, ZLINK_SEND_FLAGS_NONE, NULL, NULL);
3. Options¶
| Option | Type | Default | Description |
|---|---|---|---|
ZLINK_ROUTER_OPT_MANDATORY |
int | 1 |
0=off, positive=on. When on, a directed submit to an unconnected routing id fails with ZLINK_SUBMIT_NOT_CONNECTED instead of being silently dropped |
ZLINK_ROUTER_OPT_PROBE |
int | 0 |
0=off, positive=on. Sends an empty raw message on connect so the peer observes the connection and this ROUTER's routing id |
ZLINK_ROUTER_OPT_CONNECT_ROUTING_ID |
binary, set-only | — | Local alias for the pipe created by the next zlink_connect(). Set before each connect |
ZLINK_ROUTER_OPT_REQUEST_TIMEOUT_MS |
int (ms) | 5000 |
Default timeout used when a request's timeout_ms_ == 0 |
ZLINK_ROUTER_OPT_WEIGHT |
int | 100, range 0..10000 |
Weight this ROUTER advertises to connected peers |
ZLINK_OPT_SNDHWM |
uint64_t bytes |
automatic | Manual settings take precedence; 0 is unlimited |
ZLINK_OPT_RCVHWM |
uint64_t bytes |
automatic | Manual settings take precedence; 0 is unlimited |
ZLINK_OPT_LINGER |
int | -1 |
Wait time on close (ms) |
ZLINK_OPT_SNDTIMEO |
int | 1000 |
Send timeout (ms); set -1 explicitly for infinite wait |
ZLINK_OPT_RCVTIMEO |
int | 1000 |
Receive timeout (ms); set -1 explicitly for infinite wait |
Set and get ROUTER-specific options with the typed accessors:
ZLINK_EXPORT zlink_config_result_t zlink_set_router_option(
void *handle_, zlink_router_option_t option_, const void *optval_, size_t optvallen_);
ZLINK_EXPORT zlink_config_result_t zlink_get_router_option(
void *handle_, zlink_router_option_t option_, void *optval_, size_t *optvallen_);
zlink_get_router_option() treats *optvallen_ as the input capacity of optval_; on success it
is updated to the number of bytes actually written.
ZLINK_ROUTER_OPT_MANDATORY¶
void *router = zlink_socket(ctx, ZLINK_SOCKET_ROUTER);
int mandatory = 1;
zlink_set_router_option(router, ZLINK_ROUTER_OPT_MANDATORY, &mandatory, sizeof(mandatory));
/* target_rid names a routing id with no connected pipe */
zlink_msg_t part;
zlink_msg_init_size(&part, 4);
memcpy(zlink_msg_data(&part), "data", 4);
zlink_submit_result_t rc = zlink_send_rid(
router, target_rid, &part, 1, ZLINK_SEND_FLAGS_NONE, NULL, NULL);
/* rc == ZLINK_SUBMIT_NOT_CONNECTED because MANDATORY is on */
Reference:
core/tests/integration/test_router_mandatory.cpp
ZLINK_ROUTER_OPT_CONNECT_ROUTING_ID¶
Set this before each zlink_connect() call to choose the local alias for the pipe that call
creates — useful when a ROUTER connects out to peers instead of only accepting inbound
connections.
zlink_set_router_option(
router, ZLINK_ROUTER_OPT_CONNECT_ROUTING_ID, "peer-a", 6);
zlink_connect(router, "tcp://127.0.0.1:5559");
4. Request and Reply¶
zlink_request() submits a routed request and returns a nonzero completion ID. Its reply or
terminal result is pulled with zlink_completion_recv(), never ordinary DATA receive. A received
REQUEST (nonzero reply token) is answered with zlink_reply() using the source RID and token
returned by the receive call.
zlink_msg_t req;
zlink_msg_init_size(&req, 4);
memcpy(zlink_msg_data(&req), "ping", 4);
zlink_completion_id_t id = 0;
zlink_submit_result_t rc = zlink_request(
router, peer_rid, &req, 1, ZLINK_SEND_FLAGS_NONE,
0 /* uses ZLINK_ROUTER_OPT_REQUEST_TIMEOUT_MS */, NULL, &id);
if (rc == ZLINK_SUBMIT_OK) {
zlink_completion_t completion = {0};
completion.struct_size = sizeof(completion);
if (zlink_completion_recv(router, &completion, ZLINK_RECV_FLAGS_NONE)
== ZLINK_RECV_OK)
zlink_completion_close(&completion);
}
On the receiving side, answer with the routing id and opaque token the receive call returned:
const zlink_routing_id_t *source_rid = NULL;
zlink_reply_token_t reply_token = 0;
zlink_msg_t parts[8];
size_t part_count = 0;
zlink_router_recv(router, &source_rid, &reply_token,
parts, 8, &part_count, ZLINK_RECV_FLAGS_NONE);
if (reply_token != 0) {
/* This record expects a reply, not a directed send. */
zlink_msg_t reply;
zlink_msg_init_size(&reply, 5);
memcpy(zlink_msg_data(&reply), "World", 5);
zlink_reply(router, source_rid, reply_token, &reply, 1);
}
zlink_multipart_close(parts, part_count);
A reply to a DEALER peer shares that DEALER-ROUTER Application connection's FIFO, HWM, and PAUSED
state, so it can report ZLINK_SUBMIT_BACKPRESSURED. A reply to a ROUTER peer uses the
ROUTER-ROUTER Completion lane. Only a successful reply submission consumes the token; a failed
attempt can be retried while the request lifecycle remains valid.
Reference:
core/tests/integration/test_zmp_request_reply.cppandcore/tests/integration/test_zmp_request_reply_router_recv_surface.cpp
5. Usage Patterns¶
Pattern 1: ROUTER ← Multiple DEALERs¶
The most common shape. Each DEALER connects with a routing id; ROUTER distinguishes senders by
source_rid and replies to the same id.
void *router = zlink_socket(ctx, ZLINK_SOCKET_ROUTER);
zlink_bind(router, "tcp://127.0.0.1:*");
char endpoint[256];
size_t len = sizeof(endpoint);
zlink_get_option(router, ZLINK_OPT_LAST_ENDPOINT, endpoint, &len);
void *dealer1 = zlink_socket(ctx, ZLINK_SOCKET_DEALER);
zlink_set_routing_id(dealer1, "D1", 2);
zlink_connect(dealer1, endpoint);
void *dealer2 = zlink_socket(ctx, ZLINK_SOCKET_DEALER);
zlink_set_routing_id(dealer2, "D2", 2);
zlink_connect(dealer2, endpoint);
/* router.recv distinguishes "D1" and "D2" by source_rid, and
zlink_send_rid(router, source_rid, ...) replies to the right one. */
Reference:
core/tests/integration/test_router_multiple_dealers.cpp
Pattern 2: Request-Reply with Correlation¶
Use zlink_request() / zlink_reply() (see §4)
when the caller needs delivery confirmation and a correlated answer instead of free-form
send/recv. The completion ID correlates the origin result; the opaque nonzero reply token lets the
responder answer one REQUEST and is 0 for ordinary DATA.
Pattern 3: Enforcing Reachability with MANDATORY¶
By default, a directed send to a routing id with no connected pipe is dropped without error. Set
ZLINK_ROUTER_OPT_MANDATORY to surface that as ZLINK_SUBMIT_NOT_CONNECTED so the caller can
detect stale routing ids instead of silently losing messages.
int mandatory = 1;
zlink_set_router_option(router, ZLINK_ROUTER_OPT_MANDATORY, &mandatory, sizeof(mandatory));
Reference:
core/tests/integration/test_router_mandatory.cpp,core/tests/integration/test_router_mandatory_hwm.cpp
Pattern 4: Proxy (ROUTER-DEALER)¶
ROUTER as a frontend and DEALER as a backend build a multi-threaded server. See
DEALER §5 Pattern 3 for the full
proxy example; the ROUTER side there is a plain zlink_socket(ctx, ZLINK_SOCKET_ROUTER) bound
as the frontend.
6. Caveats¶
Routing ID Lifetime¶
source_rid returned by zlink_router_recv() is a socket-owned view. It stays valid only
until the next data-receive entry on that same socket, successful or not; copy the bytes if the id
must outlive that call. The complete record is returned with one routing id and reply token.
See Routing IDs for the full lifetime and copy contract.
No Peer Connected vs. HWM Backpressure¶
These are two distinct results, same as on DEALER. With ZLINK_ROUTER_OPT_MANDATORY on, a send to
an unconnected routing id returns ZLINK_SUBMIT_NOT_CONNECTED — nothing is queued. A send to a
connected peer whose queue is at HWM blocks (default) or returns ZLINK_SUBMIT_BACKPRESSURED with
ZLINK_SEND_FLAGS_DONTWAIT.
Logical-RID Targeting¶
zlink_send_rid() and zlink_request() accept only the logical routing id. Physical
pair IDs and generations are not public send selectors. If Core retains a DONTWAIT record before
admission, it keeps the same logical RID across transient reconnect and reports the terminal via
the completion ID. After local admission, Core does not replay the payload on a new connection.
Concurrency¶
ROUTER's public handle follows the tiered concurrency contract described in Thread Safety: send/publish paths allow same-handle concurrent use, while option changes and close serialize for correctness. Each send call atomically submits one complete record, so multiple threads may submit independent records on the same handle.
Full language examples¶
zlink::context_t ctx;
zlink::router_socket_t router (ctx);
zlink::dealer_socket_t dealer (ctx);
zlink::socket_monitor_t router_monitor = router.monitor_open ();
zlink::socket_monitor_t dealer_monitor = dealer.monitor_open ();
router.bind ("tcp://127.0.0.1:0");
const std::string endpoint = router.options ().last_endpoint ();
assert (!endpoint.empty ());
dealer.connect (endpoint);
assert (detail::wait_connected (router_monitor, dealer_monitor, 2000, &router));
const std::string sent = detail::k_dealer_router_request;
zlink::message_t outbound = detail::make_message (sent);
dealer.send ().message (std::move (outbound)).submit ();
zlink::received_t inbound;
assert (router.recv (inbound) == 0);
assert (inbound.routing_id ().has_value ());
assert (inbound.parts ().size () == 1);
assert (inbound.parts ()[0].to_string () == detail::k_dealer_router_request);
const std::string reply_payload = detail::k_dealer_router_reply;
zlink::message_t reply = detail::make_message (reply_payload);
// Reply on the ROUTER socket, addressed by the received envelope's routing id.
router.send (*inbound.routing_id ()).message (std::move (reply)).submit ();
inbound.close ();
zlink::received_t echoed;
assert (dealer.recv (echoed) == 0);
assert (echoed.parts ().size () == 1);
const std::string received = echoed.parts ()[0].to_string ();
assert (received == detail::k_dealer_router_reply);
echoed.close ();
std::printf ("[dealer-router/recv] send: \"%s\" → recv: \"%s\"\n", sent.c_str (),
received.c_str ());
return 0;
if (!SampleSupport.IsNativeAvailable())
return;
using var ctx = Zlink.CreateContext();
using var dealer = ctx.CreateDealerSocket();
using var router = ctx.CreateRouterSocket();
string endpoint = SampleSupport.NewEndpoint("tcp", "sample");
using var dealerMonitor = dealer.MonitorOpen(SocketEvent.ConnectionReady);
using var routerMonitor = router.MonitorOpen(SocketEvent.ConnectionReady);
router.Bind(endpoint);
dealer.Connect(endpoint);
SampleSupport.WaitConnected(routerMonitor, dealerMonitor);
using (Message request = Message.From("ping"))
await dealer.Send().Message(request).Async().Admitted;
using var received = Received.Create();
if (!router.Recv(received))
throw new InvalidOperationException("recv failed");
string requestPayload = received.Parts[0].GetString();
SampleSupport.EnsureEqual("ping", requestPayload, "request");
using var reply = Message.From("pong");
received.Send().Message(reply).Submit();
string payload = SampleSupport.ReceiveUtf8(dealer, 2000);
Console.WriteLine($"[dealer-router/recv] send: \"ping\" -> recv: \"{payload}\"");
SampleSupport.ensureNative();
String endpoint = SampleSupport.tcpEndpoint();
try (Context ctx = Zlink.createContext();
RouterSocket router = ctx.createRouterSocket();
DealerSocket dealer = ctx.createDealerSocket();
var routerMonitor = router.monitorOpen(
systems.zlink.contracts.eventing.MonitorEventType.CONNECTION_READY);
var dealerMonitor = dealer.monitorOpen(
systems.zlink.contracts.eventing.MonitorEventType.CONNECTION_READY)) {
router.bind(endpoint);
dealer.connect(endpoint);
SampleSupport.waitConnected(routerMonitor, dealerMonitor);
try (Message request = Message.from(SampleSupport.DEALER_REQUEST)) {
dealer.send().message(request).submit().admitted().toCompletableFuture().join();
}
try (systems.zlink.contracts.messaging.Received received = new systems.zlink.contracts.messaging.Received()) {
router.recv(received, systems.zlink.contracts.sockets.RecvFlags.NONE);
String value = SampleSupport.singleUtf8(received);
if (!SampleSupport.DEALER_REQUEST.equals(value)) {
throw new IllegalStateException("unexpected request: " + value);
}
try (Message reply = Message.from(SampleSupport.DEALER_REPLY)) {
received.send().message(reply).submit();
}
}
try (systems.zlink.contracts.messaging.Received received = new systems.zlink.contracts.messaging.Received()) {
dealer.recv(received, systems.zlink.contracts.sockets.RecvFlags.NONE);
String value = SampleSupport.singleUtf8(received);
if (!SampleSupport.DEALER_REPLY.equals(value)) {
throw new IllegalStateException("unexpected reply: " + value);
}
System.out.println("[dealer-router/recv] send: \""
+ SampleSupport.DEALER_REQUEST + "\" \u2192 recv: \"" + value + "\"");
}
}
SampleSupport.ensureNative()
val endpoint = SampleSupport.tcpEndpoint()
Zlink.createContext().use { ctx ->
ctx.createRouterSocket().use { router ->
ctx.createDealerSocket().use { dealer ->
router.monitorOpen(MonitorEventType.CONNECTION_READY).use { routerMonitor ->
dealer.monitorOpen(MonitorEventType.CONNECTION_READY).use { dealerMonitor ->
router.bind(endpoint)
dealer.connect(endpoint)
SampleSupport.waitConnected(routerMonitor, dealerMonitor)
Message.from(SampleSupport.DEALER_REQUEST).use { request ->
dealer.send().message(request).submit().await()
}
Received().use { received ->
router.recv(received, RecvFlags.NONE)
val value = SampleSupport.singleUtf8(received)
check(SampleSupport.DEALER_REQUEST == value) { "unexpected request: $value" }
Message.from(SampleSupport.DEALER_REPLY).use { reply ->
received.send().message(reply).submit()
}
}
Received().use { received ->
dealer.recv(received, RecvFlags.NONE)
val value = SampleSupport.singleUtf8(received)
check(SampleSupport.DEALER_REPLY == value) { "unexpected reply: $value" }
println(
"[dealer-router/recv] send: \"${SampleSupport.DEALER_REQUEST}\"" +
" → recv: \"$value\""
)
}
}
}
}
}
}
_, endpoint = tcp_endpoint()
with zlink.create_context() as ctx:
with zlink.create_router_socket(ctx) as router:
with zlink.create_dealer_socket(ctx) as dealer:
with router.monitor_open(zlink.MonitorEventMask.CONNECTION_READY) as router_monitor:
with dealer.monitor_open(zlink.MonitorEventMask.CONNECTION_READY) as dealer_monitor:
dealer.set_routing_id(b"CLIENT")
router.bind(endpoint)
dealer.connect(endpoint)
wait_connected(router_monitor, dealer_monitor)
send = dealer.send().message(b"ping").submit()
await send.admitted
request = zlink.create_received()
if not router.recv_into(request):
raise AssertionError("expected dealer-router request")
with request:
if request.routing_id != zlink.RoutingId(b"CLIENT"):
raise AssertionError(f"unexpected routing id: {request.routing_id!r}")
if request.to_bytes_list() != [b"ping"]:
raise AssertionError("unexpected dealer-router request payload")
send = request.send().message(b"pong").submit()
await send.admitted
reply = zlink.create_received()
if not dealer.recv_into(reply):
raise AssertionError("expected dealer-router reply")
with reply:
if reply.to_bytes_list() != [b"pong"]:
raise AssertionError("unexpected dealer-router reply payload")
print('[dealer-router/recv] send: "ping" → recv: "pong"')
const endpoint = await tcpEndpoint();
const ctx = zlink.createContext();
const router = zlink.createRouterSocket(ctx);
const dealer = zlink.createDealerSocket(ctx);
try {
const routerMonitor = router.monitorOpen([zlink.MonitorEventType.ConnectionReady]);
const dealerMonitor = dealer.monitorOpen([zlink.MonitorEventType.ConnectionReady]);
try {
router.bind(endpoint);
dealer.connect(endpoint);
await waitForConnectionReady(routerMonitor, zlink);
await waitForConnectionReady(dealerMonitor, zlink);
} finally {
routerMonitor.close();
dealerMonitor.close();
}
const sent = 'ping';
await dealer.send().message(Buffer.from(sent)).submit().admitted;
const reply = 'pong';
const request = new zlink.Received();
router.recv(request);
try {
const recvReq = request.parts[0].data().toString();
assert.equal(recvReq, sent);
assert.ok(request.routingId instanceof zlink.RoutingId);
request.send().message(Buffer.from(reply)).submit();
} finally {
request.close();
}
const response = new zlink.Received();
dealer.recv(response);
try {
const recv = response.parts[0].data().toString();
assert.equal(recv, reply);
console.log(`[dealer-router/recv] send: "${sent}" \u2192 recv: "${recv}"`);
} finally {
response.close();
}
} finally {
dealer.close();
router.close();
ctx.close();
}
const port = await reservePort();
const endpoint = `tcp://127.0.0.1:${port}`;
const ctx = zlink.createContext();
const router = zlink.createRouterSocket(ctx);
const dealer = zlink.createDealerSocket(ctx);
try {
const routerMonitor = router.monitorOpen([zlink.MonitorEventType.ConnectionReady]);
const dealerMonitor = dealer.monitorOpen([zlink.MonitorEventType.ConnectionReady]);
try {
router.bind(endpoint);
dealer.connect(endpoint);
await waitForConnectionReady(routerMonitor);
await waitForConnectionReady(dealerMonitor);
} finally {
routerMonitor.close();
dealerMonitor.close();
}
const sent = 'ping';
dealer.send().message(Buffer.from(sent)).submit();
const reply = 'pong';
const request = new zlink.Received();
router.recv(request);
try {
const recvReq = request.parts[0].data().toString();
assert.equal(recvReq, sent);
assert.ok(request.routingId instanceof zlink.RoutingId);
request.send().message(Buffer.from(reply)).submit();
} finally {
request.close();
}
const response = new zlink.Received();
dealer.recv(response);
try {
const recv = response.parts[0].data().toString();
assert.equal(recv, reply);
console.log(`[dealer-router/recv] send: "${sent}" → recv: "${recv}"`);
} finally {
response.close();
}
} finally {
dealer.close();
router.close();
ctx.close();
}
ctx, err := zlink.NewContext()
samplecommon.Must(err)
defer ctx.Close()
router, err := ctx.RouterSocket()
samplecommon.Must(err)
defer router.Close()
dealer, err := ctx.DealerSocket()
samplecommon.Must(err)
defer dealer.Close()
routerMon := samplecommon.OpenMonitor(router)
defer routerMon.Close()
dealerMon := samplecommon.OpenMonitor(dealer)
defer dealerMon.Close()
endpoint := samplecommon.UniqueTCP("dealer-router-recv")
rid := zlink.NewRoutingID([]byte("dealer-sample"))
samplecommon.Must(router.Bind(endpoint))
samplecommon.Must(dealer.SetRoutingID(rid))
samplecommon.Must(dealer.Connect(endpoint))
samplecommon.WaitConnected(routerMon, dealerMon)
submission, err := dealer.Send().Message(
samplecommon.Message("ping")).Submit(context.Background())
samplecommon.Must(err)
samplecommon.Must(submission.Admitted(context.Background()))
var request zlink.Received
_, err = router.Recv(&request, zlink.RecvFlagsNone)
samplecommon.Must(err)
defer request.Close()
submission, err = request.Send().Message(samplecommon.Message("pong")).Submit(context.Background())
if err == nil {
err = submission.Admitted(context.Background())
}
samplecommon.Must(err)
var reply zlink.Received
_, err = dealer.Recv(&reply, zlink.RecvFlagsNone)
samplecommon.Must(err)
defer reply.Close()
part, err := reply.SinglePartOrError()
samplecommon.Must(err)
if !bytes.Equal(part.Data(), []byte("pong")) {
samplecommon.Must(fmt.Errorf("unexpected reply %q", string(part.Data())))
}
fmt.Printf("[dealer-router/recv] send: %q -> recv: %q\n", "ping", string(part.Data()))
let ctx = Context::new().expect("context creation failed");
let endpoint = sample_support::tcp_endpoint();
let router = ctx.router_socket().expect("router socket failed");
let dealer = ctx.dealer_socket().expect("dealer socket failed");
let rid = RoutingId::from(b"dealer-node-7");
dealer.set_routing_id(&rid).expect("set routing id failed");
let router_mon = SocketMonitor::open(&router).expect("router monitor open failed");
let dealer_mon = SocketMonitor::open(&dealer).expect("dealer monitor open failed");
router.bind(&endpoint).expect("bind failed");
dealer.connect(&endpoint).expect("connect failed");
sample_support::wait_connected(&[&router_mon, &dealer_mon]);
drop(router_mon);
drop(dealer_mon);
let req = Message::try_from(b"ping").expect("message failed");
let submission = dealer.send().message(req).submit().expect("send failed");
sample_support::block_on(submission.admitted).expect("send admission failed");
let mut received = zlink::Received::empty();
router
.recv(&mut received, zlink::RecvFlags::NONE)
.expect("router recv failed");
assert!(received.routing_id().is_some());
assert_eq!(received.parts()[0].as_str().unwrap(), "ping");
let resp = Message::try_from(b"pong").expect("message failed");
let submission = received
.send()
.message(resp)
.submit()
.expect("received send failed");
sample_support::block_on(submission.admitted).expect("received send admission failed");
let mut response = zlink::Received::empty();
dealer
.recv(&mut response, zlink::RecvFlags::NONE)
.expect("dealer recv failed");
assert_eq!(response.parts()[0].as_str().unwrap(), "pong");
println!(
"[dealer-router/recv] send: \"ping\" → recv: \"{}\"",
response.parts()[0].as_str().unwrap()
);
See Routing IDs for lifetime and copy rules and Thread Safety for same-handle concurrency.