ZeroMQ identities aren’t just labels; they’re the programmable, stateful connections that let you build dynamic, multi-peer networks.

Let’s see this in action. Imagine a central broker that needs to send messages to specific worker processes, each identified by a unique name. We’ll use ROUTER and DEALER sockets.

Broker side (Python):

import zmq

context = zmq.Context()
# ROUTER socket accepts connections and receives messages with sender identity
router = context.socket(zmq.ROUTER)
router.bind("tcp://*:5555")

# Sockets for connecting to workers (we'll add these dynamically)
workers = {} # {identity_bytes: DEALER_socket}

print("Broker started, listening on tcp://*:5555")

while True:
    try:
        # RECV with zmq.RCVMORE will get the identity first, then the message
        identity, message = router.recv_multipart()

        print(f"Received message from {identity.decode()}: {message.decode()}")

        # If this is a new worker, register its identity
        if identity not in workers:
            print(f"New worker connected with identity: {identity.decode()}")
            worker_socket = context.socket(zmq.DEALER)
            # Connect to the worker using its address (assuming it binds to a known port)
            # In a real scenario, you'd have a way to discover worker addresses
            # For this example, let's assume workers bind to 5556, 5557, etc.
            # This part is simplified for demonstration; robust discovery is complex.
            # We'll simulate adding a worker connection here.
            # In a real app, the worker would connect to the broker.
            # Here, we're simulating the broker *knowing* how to talk back.
            # A common pattern is: worker connects to broker's ROUTER,
            # broker sends a "register" message, worker replies with its identity.
            # For this example, let's assume we know the worker's identity and address.
            # The key is the broker *stores* the identity to send back.
            pass # We don't actually create a DEALER socket here in the broker,
                 # the ROUTER socket handles outgoing messages to specific identities.

        # Send a reply back to the specific worker using its identity
        reply_message = b"ACK: " + message
        router.send_multipart([identity, reply_message])
        print(f"Sent reply to {identity.decode()}")

    except Exception as e:
        print(f"Error: {e}")

Worker side (Python):

import zmq
import sys

identity = sys.argv[1].encode() # Worker identity passed as command-line argument
broker_address = "tcp://localhost:5555"

context = zmq.Context()
# DEALER socket connects to the broker and sends messages without its own identity prepended
# The broker's ROUTER socket will automatically prepend the identity of the sender.
worker = context.socket(zmq.DEALER)
worker.setsockopt_string(zmq.IDENTITY, identity.decode()) # Set our identity
worker.connect(broker_address)

print(f"Worker {identity.decode()} started, connecting to {broker_address}")

# Send an initial message to register or just to start
worker.send(b"Hello from " + identity)

while True:
    try:
        # Receive messages from the broker
        reply = worker.recv()
        print(f"Received reply: {reply.decode()}")

        # Simulate doing some work and sending another message
        import time
        time.sleep(2)
        worker.send(b"Working on task..." + identity)

    except Exception as e:
        print(f"Error: {e}")

To run this:

  1. Start the broker: python broker.py
  2. Start multiple workers in separate terminals: python worker.py WORKER_A python worker.py WORKER_B

You’ll see the broker receive messages prefixed with the worker’s identity and then send replies back to those specific identities.

The core problem ZeroMQ identities solve is enabling stateful routing and communication in a distributed system without a central, monolithic connection manager. Instead of a single server managing all connections and knowing every client’s IP and port, each peer in a ZeroMQ network can be given a persistent, programmable identity. When you send a message using a ROUTER socket, you explicitly tell it who to send it to by prepending that peer’s identity. The ROUTER socket then keeps track of which connected peer matches that identity and delivers the message. Conversely, when a DEALER socket sends a message, the ROUTER socket on the other end automatically receives the sender’s identity. This identity is crucial because it allows the ROUTER to send replies back to the correct sender, maintaining the conversation’s state.

This identity mechanism is what transforms ZeroMQ from a simple messaging library into a framework for building complex network topologies. You can have a single ROUTER (like a broker) that talks to hundreds of DEALERs (workers), and each DEALER can have a unique identity. The ROUTER doesn’t need to know the IP address or port of each worker; it only needs to know the worker’s identity and that it’s connected. This makes your network resilient to changes in worker addresses or even temporary disconnections. When a worker reconnects, it can re-establish its identity, and the broker can resume sending it messages as if no interruption occurred.

The zmq.IDENTITY socket option is key for DEALER and REQR sockets. When you set zmq.IDENTITY on a DEALER socket, that identity is automatically prepended to any message it sends if the peer it’s connected to is a ROUTER or ROVER socket. This is how the ROUTER socket knows who sent the message. For ROUTER sockets, the identity of the incoming sender is automatically received as the first part of a recv_multipart() call. The ROUTER socket itself doesn’t have a persistent identity in the same way; its identity is the peer it’s talking to.

A common misconception is that ROUTER sockets have an identity that you set. They don’t. The identity is associated with the sender when using DEALER or REQR. The ROUTER socket receives the sender’s identity. When you send a message from a ROUTER socket, you explicitly provide the recipient’s identity as the first element of the send_multipart() call. This allows a single ROUTER to act as a central dispatch point, routing messages to any of its connected peers based on their unique identities.

The most surprising thing about ZeroMQ identities is that they are byte strings and are not validated for uniqueness by ZeroMQ itself; the application layer is responsible for managing and ensuring that identities are unique if that’s a requirement for your distributed logic.

Want structured learning?

Take the full Zeromq course →