So, what does it mean to put together ZeroMQ and Protocol Buffers inside of Clojure? What exists below is really just a quick thrown-together combination of a few sample bits of code from projects. This isn’t intended as a lesson in ZeroMQ, Protocol Buffers, or Clojure, but if you’ve got even a little bit of experience with a Lisp-ish language, you should be fine.
First, you’re going to need to have ZeroMQ installed, and Antonio Garrote’s post is a great guide. On Mac OS X, at least, you can’t use the Mac Ports version of ZeroMQ, as it’s built 32-bit. Maybe, if you ran the Java VM as 32-bit, but I just built it myself, following the instructions in the guide. The only other thing I did was install the jar using the following Maven incantation:
$ mvn install:install-file -DgroupId=org.zmq -DartifactId=jzmq \ -Dversion=2.0.7-SNAPSHOT -Dpackaging=jar -Dfile=src/zmq.jar
Once you’ve got that, you’ll need to install Protocol Buffers, and again, I build it from scratch as I couldn’t find a port in the Mac Ports collection. You’ll also need to do a mvn install in the java subdirectory to get it installed in your local Maven repository ($HOME/.m2/).
Here’s my hacked-together Leiningen project file:
(defproject cljpbzmq "0.0.1-SNAPSHOT" :description "FIXME: I'm a lazy SOB" :dependencies [[org.clojure/clojure "1.2.0"] [org.clojure/clojure-contrib "1.2.0"] [org.clojars.mikejs/clojure-zmq "2.0.7-SNAPSHOT"] [org.zmq/jzmq "2.0.7-SNAPSHOT"] [clojure-protobuf "0.2.11"] [com.google.protobuf/protobuf-java "2.3.0"]] :dev-dependencies [[swank-clojure "1.3.0-SNAPSHOT"]] :native-path "/usr/local/lib")
Once you run lein deps you should have all the pieces. If not, please post a comment so I can update things since I did this over 2 days and didn’t pay complete attention the whole time.
So the first thing we need to do is create the Protocol Buffer description. I’ve stolen this wholesale from the Java tutorial from Google, and then simplified the code even more.
package tutorial;
option java_package = "org.amber.tutorial";
option java_outer_classname = "AddressBookProtos";
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
}
I put this into $PROJECT_HOME/proto/addressbook.proto, which is the recommended place from the clojure-protobuf project. To compile the code into the Java code, and then into a compiled Java class, use the following bits:
$ protoc --java_out=src proto/addressbook.proto $ javac -cp lib/protobuf-java-2.3.0.jar src/org/amber/tutorial/AddressBookProtos.java
Unfortunately, I don’t use cake and wasn’t able to get the tasks to work correctly from clojure-protobuf so I did it by hand. Need to write some Leiningen extensions soon.
Now, we need to write some code to actually receive all those fascinating messages we’re going to send. Rather than keep it all inside the single process, I wanted to just throw it together as a multi-process application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | (use 'protobuf) (use 'org.zeromq.clojure) (import org.amber.tutorial.AddressBookProtos) (def *ctx* (make-context 1)) (defprotobuf Person org.amber.tutorial.AddressBookProtos$Person) (future (let [sock (make-socket *ctx* +upstream+)] (bind sock "tcp://127.0.0.1:5555") (loop [msg (recv sock)] (println (str "Received message: " (protobuf-load Person msg))) (recur (recv sock))))) |
So let’s walk through the code. First, lines 1 – 3 bring in the packages that we’re going to need. On line 5, we create a “global” context for ZeroMQ. Note that since we’re not using the inproc: communication method, we pass an argument of 1 to make-context, which tells it to create a single thread in the thread pool for communication. If we were using inproc:, we’d have to pass a 0 in instead. Next, we need to build the Clojure version of the Java Protocol Buffers skeleton code. This eliminates all the tedious getters and setters that Java developers so love, and makes it behave much more like a proper Clojure data structure.
Finally, we get into the actual code, where we create a socket, bind it, and sit and wait for messages to come in. Those messages then get passed through protobuf-load to de-serialize them, and poof, they are printed. This will run forever waiting.
Now, we have the sender code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | (use 'protobuf) (use 'org.zeromq.clojure) (import org.amber.tutorial.AddressBookProtos) (def *ctx* (make-context 1)) (defprotobuf Person org.amber.tutorial.AddressBookProtos$Person) (doseq [i (range 0 5)] (future (let [s (make-socket *ctx* +downstream+)] (connect s "tcp://127.0.0.1:5555") (loop [c 0] (send- s (protobuf-dump (protobuf Person :id (* i c) :name "Bob" :email "bob@foobar.com"))) (Thread/sleep (rand 5000)) (recur (inc c)))))) |
This creates 5 separate threads — that’s what future does underneath — and sends messages. Not a lot different here, except instead of protobuf-load we use protobuf with the Person “object” to build a new data structure, and then protobuf-dump to serialize it into its native binary format. And as “they say”, Bob’s your uncle.
One final bit is that the whole (protobuf Person ...) nonsense seems a bit annoying to me. Until I figure out a better way to do it, I’ve taken to using partial applications to hide it all:
(def pb-person (partial protobuf Person))
Now, you can simply do:
(pb-person :id 42 :name "Bob" :email "foo@bar.com")
It’s not perfect, but it’s a start.



So, when I wanted to listen to things, I returned to my ever trustworthy