Day 20 - zeromq
Relevancy: 1.9 stable
ZeroMQ is a language-independent messaging solution. It's not a full-fledged system such as for example RabbitMQ, basically it's just a transport layer. From the programmer's perspective working with it doesn't differ much from ordinary sockets, but there's a lot of power hidden underneath. The rust-zmq crate is a Rust binding to the C library. There used to be a working native binding (zeromq), but it's now undergoing a redesign and rewrite.
Operational patterns
The ZeroMQ guide lists several messaging patterns such as request-response, pub-sub, pipeline etc. Different patterns suit different needs for distributed systems; for example the request-response pattern is commonly used for remote procedure calls. This is the only mode of operation implemented in the zeromq
crate at the moment, but hopefully more will be added soon.
Before we start implementing the client and server, let's prepare some boilerplate code. We will decide whether to run our demo program as client or server based on the commandline argument.
extern crate zmq;
use zmq::{Context, Message, Error};
if args.len() < 2 {
println!("Usage: {} (client|server)", args[0]);
return;
}
let mut ctx = Context::new();
let addr = "tcp://127.0.0.1:25933";
if args[1] == "client" {
println!("ZeroMQ client connecting to {}", addr);
run_client(&mut ctx, addr).unwrap_or_else(|err| println!("{:?}", err));
} else {
println!("ZeroMQ server listening on {}", addr);
run_server(&mut ctx, addr).unwrap_or_else(|err| println!("{:?}", err));
}
}
Client
fn run_client(ctx: &mut Context, addr: &str) -> Result<(), Error> {
let sock = ctx.socket(zmq::REQ)?;
sock.connect(addr)?;
let payload = "Hello world!";
println!("-> {:?}", payload);
let mut msg = Message::new()?;
sock.send(payload.as_bytes(), 0)?;
sock.recv(&mut msg, 0)?;
let contents = msg.as_str().unwrap();
println!("<- {:?}", contents);
Ok(())
}
A ZeroMQ request starts with opening a REQ
socket. The sockets send and receive Message
objects. You can use any encoding you like, ZeroMQ doesn't enforce anything. It can be JSON, msgpack, protobuf, whatever - as long as you push some bytes over the wire, ZeroMQ is happy.
Note that we're using the try! macro for error handling.
Server
We're going to build a simple echo server that repeats the incoming message in the response.
fn run_server(ctx: &mut Context, addr: &str) -> Result<(), Error> {
let sock = ctx.socket(zmq::REP)?;
sock.bind(addr)?;
let mut msg = Message::new()?;
loop {
if sock.recv(&mut msg, 0).is_ok() {
sock.send(msg.as_str().unwrap().as_bytes(), 0)?;
}
}
}
The server opens a REP
socket and then loops infinitely and echoes back incoming message data. In my first implementation I forgot to send the response and got weird socket errors - turns out a response is necessary in a request/response mode, who would have thought...
Let's start the server now:
$ cargo run -- server
ZeroMQ server listening on tcp://127.0.0.1:25933
And if we fire up the client in a new tab we should see a roundtrip message:
$ cargo run -- client
ZeroMQ client connecting to tcp://127.0.0.1:25933
-> "Hello world!"
<- "Hello world!"
See also
- ZeroMQ an introduction
- rust-zmq - Rust bindings to the C ZeroMQ library
- ØMQ Messaging Patterns