ZBEACON(3)

🌈 zbeacon(3) β€” CZMQ Manual 🌈

okay so hi bestie πŸ’– you just opened the zbeacon page out of the CZMQ Manual (section 3, the fun one) and honestly? you're about to have a whole good time. this is the LAN discovery + presence class and it's literally so useful. let's go πŸ¦‹


✨ NAME ✨

zbeacon β€” Class for LAN discovery and presence πŸŽ€


πŸ¦‹ SYNOPSIS πŸ¦‹

so it's C, so it's giving pictures and pointers, but stay with me here 🫢 every single move you can make, in order:

🌸 create a new zbeacon actor instance

zactor_t *beacon = zactor_new (zbeacon, NULL);

πŸ’€ destroy the zbeacon instance

zactor_destroy (&beacon);

πŸ—£οΈ enable verbose logging of commands and activity

zstr_send (beacon, "VERBOSE");

like, all the chatter. very main character of you.

βš™οΈ CONFIGURE

this configures the beacon to run on the UDP port you hand it, and it gives you back the name of the host β€” which you can use as the endpoint for incoming connections. that's the whole point, bestie πŸ’…

to force the beacon onto a specific interface, set the environment variable ZSYS_INTERFACE, or call zsys_set_interface() before you create the beacon. and if the system doesn't support UDP broadcasts at all (like, no workable interface), you get an empty hostname back. not a vibe but it is what it is 😭
//  picture time: 's' = C string, 'i' = int
zsock_send (beacon, "si", "CONFIGURE", port_number);
char *hostname = zstr_recv (beacon);

πŸ“‘ PUBLISH

starts broadcasting your beacon on an interval, measured in msec. the beacon data can be at most UDP_FRAME_MAX bytes β€” that constant is defined in zsys.h and it's 255.

//  picture: 'b' = byte * data + size_t size
zsock_send (beacon, "sbi", "PUBLISH", data, size, interval);

🀫 SILENCE

stop broadcasting the beacon. so peaceful.

zstr_sendx (beacon, "SILENCE", NULL);

πŸ‘‚ SUBSCRIBE

start listening to beacons from your peers 🫢 the filter does a prefix match on the beacons coming in, which is how you throw out the junk. and ngl, note this: any received data that's identical to our own broadcast beacon_data gets discarded in any case. also, if the filter size is zero, we get all the peer beacons.

zsock_send (beacon, "sb", "SUBSCRIBE", filter_data, filter_size);

πŸ”‡ UNSUBSCRIBE

stop listening to the other peers.

zstr_sendx (beacon, "UNSUBSCRIBE", NULL);

πŸ’Œ receive the next beacon from a peer

received beacons are always a 2-frame message: first the ipaddress of the sender, then the binary beacon data exactly as published by the sender. periodt.

zmsg_t *msg = zmsg_recv (beacon);

🧚 this is the zbeacon constructor, as a zactor_fn

CZMQ_EXPORT void
    zbeacon (zsock_t *pipe, void *unused);

πŸ”₯ and the self test of this class

CZMQ_EXPORT void
    zbeacon_test (bool verbose);
heads up bestie: Please add '@interface' section in './../src/zbeacon.c'.

πŸ’œ DESCRIPTION πŸ’œ

okay so the zbeacon class implements a peer-to-peer discovery service for local networks, and it's honestly so cute. a beacon can broadcast and/or capture service announcements using UDP messages on the local area network, and this implementation uses IPv4 UDP broadcasts. you get to define the format of your outgoing beacons, and set a filter that validates the incoming ones. and the best part? beacons are sent and received asynchronously in the background. slay. πŸ’…

this class replaces zbeacon_v2, and it's meant for applications that use the CZMQ v3 API β€” meaning, zsock.

πŸ’– EXAMPLE πŸ’–

okay bestie, this whole section is lifted straight out of the zbeacon_test method, and it is a lot of code. so grab a snack πŸ“, we're going in. i'm gonna walk you through it in chunks so it doesn't fry your brain, but every single line stays exactly as written, no cap.

First up β€” Test 1: two beacons, one speaking, one listening. Iconic. One beacon yaps, one beacon listens, that's the whole vibe.

//  Test 1 - two beacons, one speaking, one listening
//  Create speaker beacon to broadcast our service
zactor_t *speaker = zactor_new (zbeacon, NULL);
assert (speaker);
if (verbose)
    zstr_sendx (speaker, "VERBOSE", NULL);

zsock_send (speaker, "si", "CONFIGURE", 9999);
char *hostname = zstr_recv (speaker);
if (!*hostname) {
    printf ("OK (skipping test, no UDP broadcasting)\n");
    zactor_destroy (&speaker);
    freen (hostname);
    return;
}
freen (hostname);

lowkey important: if hostname comes back empty, the test just bails with "OK (skipping test, no UDP broadcasting)" β€” that's the no-UDP-broadcasting escape hatch, not a bug, bestie.

Now the listener side πŸ’…

//  Create listener beacon on port 9999 to lookup service
zactor_t *listener = zactor_new (zbeacon, NULL);
assert (listener);
if (verbose)
    zstr_sendx (listener, "VERBOSE", NULL);
zsock_send (listener, "si", "CONFIGURE", 9999);
hostname = zstr_recv (listener);
assert (*hostname);
freen (hostname);

and then we broadcast the magic value 0xCAFE because why not, it's cute:

//  We will broadcast the magic value 0xCAFE
byte announcement [2] = { 0xCA, 0xFE };
zsock_send (speaker, "sbi", "PUBLISH", announcement, 2, 100);
//  We will listen to anything (empty subscription)
zsock_send (listener, "sb", "SUBSCRIBE", "", 0);

empty subscription = listen to everything. main character energy, honestly. πŸ‘‘

//  Wait for at most 1/2 second if there's no broadcasting
zsock_set_rcvtimeo (listener, 500);
char *ipaddress = zstr_recv (listener);
if (ipaddress) {
    zframe_t *content = zframe_recv (listener);
    assert (zframe_size (content) == 2);
    assert (zframe_data (content) [0] == 0xCA);
    assert (zframe_data (content) [1] == 0xFE);
    zframe_destroy (&content);
    zstr_free (&ipaddress);
    zstr_sendx (speaker, "SILENCE", NULL);
}
zactor_destroy (&listener);
zactor_destroy (&speaker);

see that 500? that's the 1/2 second max wait. and if nothing shows up, ipaddress is just falsy and we move on. no drama. ✨


πŸ¦‹ the 3-node subscription filter πŸ¦‹

now it gets spicy β€” Test subscription filter using a 3-node setup. three beacons, all on port 5670:

//  Test subscription filter using a 3-node setup
zactor_t *node1 = zactor_new (zbeacon, NULL);
assert (node1);
zsock_send (node1, "si", "CONFIGURE", 5670);
hostname = zstr_recv (node1);
assert (*hostname);
freen (hostname);

zactor_t *node2 = zactor_new (zbeacon, NULL);
assert (node2);
zsock_send (node2, "si", "CONFIGURE", 5670);
hostname = zstr_recv (node2);
assert (*hostname);
freen (hostname);

zactor_t *node3 = zactor_new (zbeacon, NULL);
assert (node3);
zsock_send (node3, "si", "CONFIGURE", 5670);
hostname = zstr_recv (node3);
assert (*hostname);
freen (hostname);

then everybody publishes their little signature, and node1 only subscribes to "NODE":

zsock_send (node1, "sbi", "PUBLISH", "NODE/1", 6, 250);
zsock_send (node2, "sbi", "PUBLISH", "NODE/2", 6, 250);
zsock_send (node3, "sbi", "PUBLISH", "RANDOM", 6, 250);
zsock_send (node1, "sb", "SUBSCRIBE", "NODE", 4);

so node3's "RANDOM" is getting absolutely ignored by node1 and honestly? deserved. πŸ™ˆ

//  Poll on three API sockets at once
zpoller_t *poller = zpoller_new (node1, node2, node3, NULL);
assert (poller);
int64_t stop_at = zclock_mono () + 1000;
while (zclock_mono () < stop_at) {
    long timeout = (long) (stop_at - zclock_mono ());
    if (timeout < 0)
        timeout = 0;
    void *which = zpoller_wait (poller, timeout);
    if (which) {
        assert (which == node1);
        char *ipaddress, *received;
        zstr_recvx (node1, &ipaddress, &received, NULL);
        assert (streq (received, "NODE/2"));
        zstr_free (&ipaddress);
        zstr_free (&received);
    }
}
zpoller_destroy (&poller);

the assert (which == node1) is doing so much work here β€” only node1 should ever wake up, because only node1 subscribed. and the payload it gets? "NODE/2". fr fr. πŸ’…

//  Stop listening
zstr_sendx (node1, "UNSUBSCRIBE", NULL);

//  Stop all node broadcasts
zstr_sendx (node1, "SILENCE", NULL);
zstr_sendx (node2, "SILENCE", NULL);
zstr_sendx (node3, "SILENCE", NULL);

//  Destroy the test nodes
zactor_destroy (&node1);
zactor_destroy (&node2);
zactor_destroy (&node3);

//  Unset multicast
zsys_set_ipv4_mcast_address (NULL);

clean exit, we love to see it. πŸŽ€


😭 and now… the second half 😭

bestie. i need you to know. the test literally does the entire Test 1 block again, word for word, same comments and everything. so here it is, all of it, in its full duplicated glory:

//  Test 1 - two beacons, one speaking, one listening
//  Create speaker beacon to broadcast our service
zactor_t *speaker = zactor_new (zbeacon, NULL);
assert (speaker);
if (verbose)
    zstr_sendx (speaker, "VERBOSE", NULL);

zsock_send (speaker, "si", "CONFIGURE", 9999);
char *hostname = zstr_recv (speaker);
if (!*hostname) {
    printf ("OK (skipping test, no UDP broadcasting)\n");
    zactor_destroy (&speaker);
    freen (hostname);
    return;
}
freen (hostname);

//  Create listener beacon on port 9999 to lookup service
zactor_t *listener = zactor_new (zbeacon, NULL);
assert (listener);
if (verbose)
    zstr_sendx (listener, "VERBOSE", NULL);
zsock_send (listener, "si", "CONFIGURE", 9999);
hostname = zstr_recv (listener);
assert (*hostname);
freen (hostname);

//  We will broadcast the magic value 0xCAFE
byte announcement [2] = { 0xCA, 0xFE };
zsock_send (speaker, "sbi", "PUBLISH", announcement, 2, 100);
//  We will listen to anything (empty subscription)
zsock_send (listener, "sb", "SUBSCRIBE", "", 0);

//  Wait for at most 1/2 second if there's no broadcasting
zsock_set_rcvtimeo (listener, 500);
char *ipaddress = zstr_recv (listener);
if (ipaddress) {
    zframe_t *content = zframe_recv (listener);
    assert (zframe_size (content) == 2);
    assert (zframe_data (content) [0] == 0xCA);
    assert (zframe_data (content) [1] == 0xFE);
    zframe_destroy (&content);
    zstr_free (&ipaddress);
    zstr_sendx (speaker, "SILENCE", NULL);
}
zactor_destroy (&listener);
zactor_destroy (&speaker);

//  Test subscription filter using a 3-node setup
zactor_t *node1 = zactor_new (zbeacon, NULL);
assert (node1);
zsock_send (node1, "si", "CONFIGURE", 5670);
hostname = zstr_recv (node1);
assert (*hostname);
freen (hostname);

zactor_t *node2 = zactor_new (zbeacon, NULL);
assert (node2);
zsock_send (node2, "si", "CONFIGURE", 5670);
hostname = zstr_recv (node2);
assert (*hostname);
freen (hostname);

zactor_t *node3 = zactor_new (zbeacon, NULL);
assert (node3);
zsock_send (node3, "si", "CONFIGURE", 5670);
hostname = zstr_recv (node3);
assert (*hostname);
freen (hostname);

zsock_send (node1, "sbi", "PUBLISH", "NODE/1", 6, 250);
zsock_send (node2, "sbi", "PUBLISH", "NODE/2", 6, 250);
zsock_send (node3, "sbi", "PUBLISH", "RANDOM", 6, 250);
zsock_send (node1, "sb", "SUBSCRIBE", "NODE", 4);

//  Poll on three API sockets at once
zpoller_t *poller = zpoller_new (node1, node2, node3, NULL);
assert (poller);
int64_t stop_at = zclock_mono () + 1000;
while (zclock_mono () < stop_at) {
    long timeout = (long) (stop_at - zclock_mono ());
    if (timeout < 0)
        timeout = 0;
    void *which = zpoller_wait (poller, timeout);
    if (which) {
        assert (which == node1);
        char *ipaddress, *received;
        zstr_recvx (node1, &ipaddress, &received, NULL);
        assert (streq (received, "NODE/2"));
        zstr_free (&ipaddress);
        zstr_free (&received);
    }
}
zpoller_destroy (&poller);

//  Stop listening
zstr_sendx (node1, "UNSUBSCRIBE", NULL);

//  Stop all node broadcasts
zstr_sendx (node1, "SILENCE", NULL);
zstr_sendx (node2, "SILENCE", NULL);
zstr_sendx (node3, "SILENCE", NULL);

//  Destroy the test nodes
zactor_destroy (&node1);
zactor_destroy (&node2);
zactor_destroy (&node3);

#if defined (__WINDOWS__)
zsys_shutdown();
#endif

and that last bit β€” the #if defined (__WINDOWS__) / zsys_shutdown(); / #endif β€” only shows up at the very end of the second copy. Windows girlies get a lil extra cleanup, as they should. 🫢


🫢 AUTHORS 🫢

the czmq manual was written by the authors in the AUTHORS file. stan. πŸ’œ


🌈 RESOURCES 🌈

Main web site:

Report bugs to the email <zeromq-dev@lists.zeromq.org[1]>

like, take your bugs and your praise to the mailing list, bestie, that's the whole resource situation. πŸ¦‹


Copyright (c) the Contributors as noted in the AUTHORS file. This file is part of CZMQ, the high-level C binding for 0MQ: http://czmq.zeromq.org. This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. LICENSE included with the czmq distribution.

Mozilla Public License v2.0, for the record. read it, it's giving responsible open source citizen. πŸ“œβœ¨


πŸ“ NOTES πŸ“

  1. zeromq-dev@lists.zeromq.org mailto:zeromq-dev@lists.zeromq.org

and that's a wrap on ZBEACON(3) πŸ’… this page is from CZMQ 4.2.1, dated 01/16/2026 β€” yes bestie, that's the source and the date, and you're all caught up. go beacon something. πŸŽ€πŸ’–

πŸ“¦ where this page comes from