Sep 5, 2026 · robot-messaging · zeromq · lcm · ros-2
LCM, ZeroMQ or ROS 2: Which Messaging Approach Fits Your Robot?
ROS 2 wins when you need its packages, ZeroMQ when you want plumbing you own, LCM when messages are small and numerous. Here is how to tell them apart.
Pick ROS 2 if you need its packages, ZeroMQ if you want plumbing you control, and LCM if you want small messages with generated code. All three move data between programs; the difference is what else arrives with them, from a whole ecosystem down to a bare socket. The verdict flips when the programs share one computer and a control loop must not wait, which is what HORUS addresses. The rest of this post is for someone who has a robot working, has hit a messaging decision, and does not want to redo it in six months.
Your robot works. Two programs on one machine, a camera process and a control loop, and you connected them with the first thing that came to hand — a socket you wrote yourself, or a file on disk, or a queue from your language's standard library. That held together for months and you were quietly pleased with it.
Now there are five programs. Someone wants every run recorded so a bad afternoon can be replayed at a desk. A colleague asks whether the timestamps mean what everyone assumes they mean, and working out the honest answer takes you most of a day. During a test a message went missing and nobody can say whether it was dropped, queued somewhere, or never sent at all.
So you go looking, and the internet offers three names in the same breath. ROS 2, which everybody uses and which arrives with a build system, a workspace layout and a vocabulary to learn before anything moves. ZeroMQ, described as sockets with better manners, apparently holding up half the trading world. LCM, which the self-driving crowd relied on for years and which almost nobody writes about now. Three answers, three vocabularies, and nowhere a plain statement of which problem each one was built to solve.
Which messaging approach fits your robot: LCM, ZeroMQ or ROS 2?
The choice is decided by what you need besides messages, because all three deliver data between programs perfectly well. ROS 2 is the answer when the value of your project sits in packages other people wrote — navigation, mapping, a driver for the sensor you just bought, a visualiser your team already knows how to read. You accept a build system, a workspace layout and a vocabulary in exchange, and for many robots that is a bargain. ZeroMQ is the answer when you want to own the plumbing completely, have opinions about how your programs should talk, and are willing to define message shapes, naming and startup order yourself. LCM is the answer when your messages are small and numerous, their shapes rarely change, and you want a definition file plus generated code in several languages without any of the rest. There is a fourth case none of the three names covers, and it is common: everything runs on one computer, a control loop must keep time, and the copying between processes is precisely the thing you want gone.
What does a robot messaging layer actually do?
A robot messaging layer takes data one program produced and puts it in front of every program that asked for that kind of data, without either side knowing the other exists. That indifference is the entire point. The camera program publishes frames; it does not know whether one program is reading them or four, and it does not change when you add a recorder next month. Underneath, a messaging layer is doing three jobs at once. It is finding the other side, which is called discovery and is where most mysterious silence comes from. It is turning your data into something transportable and back again, which is where copying and timing costs live. And it is deciding what happens when the reader falls behind the writer — dropping the old data, queueing it, or making the writer wait. Different libraries answer that third question differently, and their answers are what you feel on a real robot, long after the tutorial worked on your laptop.
What are the real messaging options for a robot today?
You have seven realistic options, and only three of them are the ones in the title. ROS 2 gives you messaging inside a whole ecosystem, with defined message types, launch files, recording and playback, and a very large body of existing packages, resting on DDS underneath. ZeroMQ gives you flexible sockets and messaging patterns with no robotics opinions whatsoever. LCM gives you a message definition file, generated code for several languages, and broadcast to whoever is listening on the local network. MQTT gives you a broker and a topic tree familiar from home automation, which is comfortable for telemetry and awkward for control. Sockets and pipes you write yourself give you total control and a permanent maintenance job. Zenoh is a newer take that spans local and networked cases and is now available under ROS 2. And where every program shares one computer, HORUS is an open-source real-time robotics middleware for Rust, Python and C++ in which the three languages share the same shared-memory ring buffers, so messages are not serialised between processes on one machine — which suits a Python program working beside a compiled control loop, while ROS 2 remains the better answer whenever borrowed packages are the point of the project.
| Option | Who it is for | What it assumes you know | When to pick it | When not to |
|---|---|---|---|---|
| ROS 2 | Teams needing borrowed navigation, drivers or tooling | Linux, workspaces, message types, launch files | The project's value is packages other people wrote | You want a small stack you can hold in your head |
| ZeroMQ | Engineers who want to own the plumbing | Sockets, messaging patterns, your own data formats | Your needs are unusual and you have firm opinions | You would rather not design conventions from scratch |
| LCM | Groups sending many small messages between programs | A definition file and generated code per language | Messages are simple and rarely change shape | You need recording, tooling and drivers supplied |
| HORUS | Small teams mixing Python with a Rust or C++ loop | Your message shapes and how your loops are timed | Every program shares one computer and a loop must keep time | You need many borrowed packages or several machines |
| MQTT | Builders arriving from web or home-automation work | Brokers, topics, retained messages | Telemetry and commands flow over a network with loose timing | A control loop depends on messages arriving in time |
| Zenoh | Teams spanning one machine and a fleet | Its own naming and configuration model | You want one layer for local and remote traffic | Your team wants the most heavily trodden path |
| Hand-written sockets | Tinkerers and very small fixed systems | Sockets, framing, data formats, timing | The system is tiny and will not grow | More than a couple of programs must find each other |
What does a bad messaging choice look like once the robot is running?
A bad messaging choice shows up as behaviour you cannot explain from any single program's code. The arm hesitates, but only while the camera is running. A test passes on the bench and fails on the robot, and the difference turns out to be which program happened to start first. Data arrives, but describes a moment that has already gone, so the machine reaches for where the object was rather than where it is. Messages disappear under load and nobody can say whether they were dropped, queued or never sent, because nothing in the layer will tell you. Two engineers disagree about what a field means and both are right, because the message shape lives in two places and drifted apart. The pattern underneath all of these is the same: the messaging layer made a decision on your behalf — about ordering, about slow readers, about who finds whom — and you never saw the decision being made, so you cannot find it now.
What fits you if you are one person building the whole robot?
One person should pick the smallest layer that covers the whole robot, and for most solo builders that is not ROS 2. When you are alone, every hour spent on build tooling is an hour not spent on the machine, and the ecosystem argument mostly does not apply yet, because you are unlikely to be integrating six borrowed packages in your first months. What you want is something you can install, use from two languages, and reason about when it misbehaves at eleven at night with the robot on the bench. LCM and ZeroMQ both qualify, with ZeroMQ asking you to invent more conventions and LCM handing you a message file and generated code instead. A shared-memory layer qualifies too, if every program lives on one computer. The exception is real and worth naming: if the robot must navigate a map, and the alternative is writing a planner yourself, ROS 2 repays its own setup within a weekend. Solo builders who ignore that exception spend a year rebuilding what they could have borrowed.
What fits a robot whose computer is a small board bolted to the frame?
A small board pushes you towards the lighter options, because everything you install competes for the same modest processor and memory. On a single-board computer that is also running a control loop and perhaps a camera pipeline, the messaging layer should be something you barely notice is there. ZeroMQ and LCM are both undemanding. A shared-memory transport is undemanding in a different and useful way, because data written by one program is read by another without a second copy being made, and copies are exactly what a small board can least afford. ROS 2 will run on modern single-board hardware and a great many people do run it there, but you feel the weight — in start-up time, in memory, and in the amount of machinery running that your robot never uses. If the same board also has to do vision, test your candidate layer alongside the vision work early, because that combination is what surprises people. Middleware on small boards is a decision worth making before the frame is welded.
What should you use if the robot has to work within a month?
With a month on the clock, use whichever layer someone on the team has already used, even when a different one is better in principle. New messaging layers do not fail during the tutorial. They fail two weeks later, on the robot, in ways that need somebody who has seen that failure before and recognises the shape of it. A month is not long enough to accumulate that experience and finish the machine as well. If nobody has used any of them, pick the one with the smallest surface — the one whose entire documentation can be read in an evening. That points at LCM or ZeroMQ rather than ROS 2, unless the deadline itself depends on borrowed packages, in which case ROS 2 is the shortcut and its setup is worth paying for. Whatever you choose, wire it up on day two rather than in week three. The messaging layer is where late surprises live, and meeting them while you can still change your mind is worth more than any property of the layer itself.
What if you have never written networking code?
Then avoid the option that quietly assumes you have, which is ZeroMQ. ZeroMQ is genuinely good, and it is a toolkit rather than an answer: patterns to choose between, connection directions to get right, and a set of behaviours around slow readers that will find you eventually. Somebody who has done network programming enjoys that freedom. Somebody who has not experiences it as a series of decisions they did not know they were making. LCM is friendlier here because the shape of the thing is fixed: define messages, generate code, publish, subscribe, done. ROS 2 hides the networking almost completely, which is a real benefit until the day it does not, and then you are debugging discovery behaviour with very little intuition to draw on. A shared-memory layer sidesteps the question entirely for programs on one computer, since no network is involved at all. What DDS is and why ROS 2 depends on it is the background reading that makes those days less mysterious.
What do teams try first, and why does it stop working?
Almost everyone starts by writing their own, and it works for exactly as long as the robot has two programs. A socket, a blob of JSON, a length prefix if you were careful. It is a pleasant afternoon and it genuinely solves the problem in front of you. What breaks it is growth, and always in the same order. A third program needs the same data, so something now has to fan out. A program restarts and the other side does not notice, so you need to detect that. You want to record a run and replay it at a desk, so you need a format and timestamps that agree. Someone adds a field and an older program misreads every message after it, so you need versioned definitions. Each of those is a week, and at the end of the weeks you own a worse version of a library you could have adopted for free. The signal to stop building your own is the third program, not the second.
What do you give up by choosing something lighter than ROS 2?
You give up other people's work, and that is essentially the whole of it. No navigation stack, no arm planning, no ready-made driver for the depth camera that just arrived, no visualiser your next hire already knows, no recording format that any tool in the field can open. You give up a hiring shortcut too, because a candidate who has used ROS 2 arrives knowing the vocabulary, while an in-house arrangement has to be taught to every newcomer. And you give up the comfort of the well-trodden path: when something behaves strangely, no forum thread describes your exact symptom. The lighter options answer this differently. LCM and ZeroMQ are old enough to have real communities, though not robotics-shaped ones. The shared-memory option in the table above is younger and is validated in simulation rather than by years of fleet deployment, which is a fair thing to weigh. None of them takes away your ability to change your mind later, provided the messaging stayed at the edges of your code.
When is ROS 2 the better choice?
ROS 2 is the better choice whenever the packages are the project. If your robot must build a map of a building it has never seen, plan a route through it, and recover gracefully when a person steps into the corridor, ROS 2 hands you all three and HORUS hands you none of them — that is not a close call, and rebuilding a navigation stack to avoid a build system is a bad trade at any price. ROS 2 is also the better choice when your robot spans several computers that must agree about time and state, when you are hiring people expected to be useful in their first fortnight, when a customer or research group requires it in writing, and when the sensor you depend on ships a maintained ROS 2 driver and nothing else. There is one more case: if you are learning robotics with a view to being employed in it, ROS 2 is the vocabulary the field speaks. Whether ROS 2 is still the default is a fair question, and for a large class of robots the answer remains yes.
Is ZeroMQ enough on its own to run a robot?
No, and here is why. ZeroMQ moves bytes between programs flexibly and dependably, and a robot needs considerably more than that before anything moves. You need agreed message shapes, or two programs will disagree about units and nobody notices until the arm swings the wrong way. You need a naming scheme, so that adding a program does not mean editing three others. You need a way to start everything in a working order and to notice when one part has died quietly. You need timestamps that mean the same thing everywhere, or fused sensor data becomes fiction. You need to record a run and replay it, because otherwise every bug must be reproduced live on hardware with someone standing by the power switch. ZeroMQ supplies none of that, by design. Teams that succeed with ZeroMQ build all of it deliberately and end up with a small in-house framework they understand completely. Teams that fail with ZeroMQ build the same thing accidentally, in pieces, over a year, and cannot explain it to the next person who joins.
Does choosing LCM or ZeroMQ lock you out of ROS 2 later?
Partly, but not the way you think. What locks you in is not the library; it is the shape your code takes around the library. If your control logic calls a messaging function from a hundred places, then yes, moving is a rewrite — and it would be a rewrite in the other direction too. If your control logic takes plain data in and returns plain data out, with a thin layer at the edge doing the publishing and subscribing, then swapping messaging is a contained job measured in days rather than quarters. The genuinely sticky parts sit elsewhere. Recorded data in a private format is awkward to bring to a new tool, and nobody wants to throw away a year of logs. Message definitions that grew organically must be reconciled with the conventions of wherever you land. And team habits move slowest of all. Why middleware decisions are hard to reverse is worth reading before you commit, because the cost is real even though it is smaller than the folklore claims.
How do you decide which messaging approach to use?
Answer one question first: are your programs on one computer or several? Everything else follows from that, and most arguments about messaging happen because two people are quietly answering different versions of the question. If everything is on one machine, network loss and discovery are not your problems, while copying and start-up order are, and the field narrows immediately. If programs are spread across machines, the network becomes the deciding factor and the local-only options fall away. Then ask the second question: how much of your robot do you intend to write yourself? If the answer is most of it, a small layer is a gift and a large one is a tax. If the answer is as little as possible, ROS 2 is the only option that seriously reduces what you have to write. Whether you need middleware at all on a single computer is worth settling before you compare libraries, because a surprising number of robots need none of them.
Decide by situation rather than by reputation:
- If your robot's value is navigation, mapping or a vendor driver -> ROS 2, because rebuilding those is the whole project.
- If every program runs on one computer and a loop must keep time -> a shared-memory middleware, because copying between processes is the cost you can remove.
- If your messaging needs are unusual and your opinions are firm -> ZeroMQ, because nothing else gets out of the way so completely.
- If you send many small messages across several languages -> LCM, because a definition file and generated code is most of what you need.
- If parts of the system sit across a network with loose timing -> MQTT, because a broker and a topic tree is a shape your team already knows.
- If a deadline is close -> whatever someone on the team has already used, because late surprises cost more than any property of a library.
When two options stay close, weigh them on the five axes of the HORUS Fit Framework — ecosystem size, setup effort, team size fit, deployment target, and licence — and take the one that loses on the fewest. No scores and no numbers, just five honest questions about your situation rather than about the software. And if the one-computer case is the one you are in, star HORUS on GitHub so it is in your list when you start building.