26 Sep 2015, 16:57

Problem: I want to use ZeroMQ with Go

Share

Solution: GoCZMQ

Background

ZeroMQ is a distributed messaging library focused on efficiency and simplicity. CZMQ is a high level C binding for ZeroMQ. It provides a clean API across multiple versions of ZeroMQ that additionally provides a suite of useful services. I have been using ZeroMQ and CZMQ in projects for several years and occassionally contributing to CZMQ and other ZeroMQ related projects. When I first started experimenting with Go, I felt its focus on simple concurrency was a good match with the guiding philosophy behind ZeroMQ.

While Pebbe’s Go bindings for ZeroMQ were available at the time and looked to be high quality, I decided writing a Go language binding for CZMQ would be a fun way to learn about Go’s cgo interface to C.

I decided to develop the bindings as a ZeroMQ organization project using the Collective Code Construction Contract process. While I had contributed to ZeroMQ projects using this process, I had never started a project from scratch using it. I started the project with the pull request “Problem: there is no documentation” on September 6th, 2014.

Since then, I picked up a regular collaborator (hi Luna!) and five other other contributors. The API is stable, and myself and others are building projects on top of it. The experience has been great, and there are many things I could discuss about what I’ve learned on the way - but for now, let’s just answer the question “how do I use it?”.

Installation

These instructions are for building GoCZMQ from git master. We’ll build it against CZMQ from git master, which is in turn built against ZeroMQ from git master. If you’re used to stable releases, this may seem odd. In ZeroMQ organization projects, we do not use development branches. We also don’t care much about traditional “stable” releases. Version tags are mostly to make packaging easier for OS maintainers who care about such things. This practice is codified in the C4.1 process documentation:

“The project SHALL have one branch (“master”) that always holds the latest in-progress version and SHOULD always build.”

“The project SHALL NOT use topic branches for any reason. Personal forks MAY use topic branches.”

libsodium

First, we need libsodium. Sodium is a “modern, easy-to-use software library for encryption, decryption, signatures, password hashing and more”. ZeroMQ relies on it for encryption. It is likely there are dev packages for it for your OS, but here are the instructions for building it just in case:

wget https://download.libsodium.org/libsodium/releases/libsodium-1.0.3.tar.gz
wget https://download.libsodium.org/libsodium/releases/libsodium-1.0.3.tar.gz.sig
wget https://download.libsodium.org/jedi.gpg.asc
gpg --import jedi.gpg.asc
gpg --verify libsodium-1.0.3.tar.gz.sig libsodium-1.0.3.tar.gz
tar zxvf libsodium-1.0.3.tar.gz
cd libsodium-1.0.3
./configure; make check
sudo make install
sudo ldconfig
zeromq

Next up, we build ZeroMQ with libsodium support:

git clone git@github.com:zeromq/libzmq.git
cd libzmq
./autogen.sh
./configure --with-libsodium
make check
sudo make install
sudo ldconfig
czmq

Now, we’ll build CZMQ against ZeroMQ. For an overview of what the CZMQ API provides, see the reference manual.

git clone git@github.com/zeromq/czmq.git
cd czmq
./autogen.sh
./configure
make check
sudo make install
sudo ldconfig
goczmq

Now, finally we can build GoCZMQ itself:

mkdir -p $GOPATH/src/github.com/zeromq
cd $GOPATH/src/github.com/zeromq
git clone git@github.com/zeromq/goczmq.git
cd goczmq
go test -v
go install

For an overview of the GoCZMQ API, see the godoc.org

Hello World

No “getting started” post is complete without the requisite “hello world” example. In this example, we’ll connect a “push” socket to a “pull” socket in the same process, and send a message. This is probably not something we’d do in a real use case, but it’s a nice demonstration of how ZeroMQ is asyncronous.

helloworld.go
package main

import (
	"fmt"

	"github.com/zeromq/goczmq"
)

func main() {
	push, err := goczmq.NewPush("tcp://127.0.0.1:31337")
	if err != nil {
		panic(err)
	}

	pull, err := goczmq.NewPull("tcp://127.0.0.1:31337")
	if err != nil {
		panic(err)
	}

	err = push.SendFrame([]byte("Hello World"), goczmq.FlagNone)
	if err != nil {
		panic(err)
	}

	frame, sz, err := pull.RecvFrame()
	if err != nil {
		panic(err)
	}

	fmt.Printf("We received a message of size %d\n", sz)
	fmt.Printf("The message was: '%s'\n", frame)

	pull.Destroy()
	push.Destroy()
}

Let’s go over what is happening in this short example in detail. First, we create a ZMQ_PUSH socket:

 	push, err := goczmq.NewPush("tcp://127.0.0.1:31337")
	if err != nil {
		panic(err)
	}

The NewPush function does quite a bit for us under the hood. Since it’s the first socket we’ve created in this program, it creates a ZeroMQ context for us. Then, it creates the socket and starts trying to connect the socket to the endpoint.

Note that we’ve constructed a TCP socket that is connecting before we’ve created the bound socket! With ZeroMQ, the order of bind and connect calls does not matter. The socket will retry connecting in the background until it is successful.

Next, we’ll construct a ZMQ_PULL socket which will bind to the endpoint. After it is bound, the ZMQ_PUSH socket will successfully connect to it.

	pull, err := goczmq.NewPull("tcp://*:31337")
	if err != nil {
		panic(err)
	}

Now comes the exciting part - sending a message! In ZMTP (the protocol implemented by ZeroMQ), a “message” consists of one or more byte agnostic frames. We will send a message by using the SendFrame API call. SendFrame accepts a []byte followed by flags. In this case, FlagNone indicates there are no frames following this frame, so the single frame should be treated as a full message. If this frame were the first frame in multi-part message, we would use goczmq.FlagMore.

	err = push.SendFrame([]byte("Hello World"), goczmq.FlagNone)
	if err != nil {
		panic(err)
	}

An important detail to note here is that ZeroMQ is asyncronous and provides a buffer under the hood - so our program execution continues after the call to SendFrame even though the message has not yet been received. This is what allows this example to send and receive in the same thread.

Now it’s time to receive the message:

	frame, sz, err := pull.RecvFrame()
	if err != nil {
		panic(err)
	}

	fmt.Printf("We received a message of size %d\n", sz)
	fmt.Printf("The message was: '%s'\n", frame)

After we’re done with the sockets, we call sock.Destroy to clean up memory. While Go is a garbage collected language, we’re wrapping a C API, so we need to clean up after ourselves:

	pull.Destroy()
	push.Destroy()

Next Steps

ZeroMQ is a large topic, and ZeroMQ combined with Go doubly so. I’ll be working on a series of articles covering the usage of interesting parts of the GoCZMQ API as well as lessons learned from working on the project.

Resources