ZeroMQ logo

Build a High-Performance Messaging Service with Qt and iOS

By Boris Ralchenko

Are you a Qt developer interested in creative ways to build a messaging service for your app? If so, this blog is for you. I’ll introduce you to ZeroMQ and show you what you could do in Qt to implement messaging in your application.

Here’s why you should you care about ZeroMQ:

  • It speaks your language in terms of both usage patterns and programming language. While you can do almost everything in terms of Server/Client, it’s more convenient to think in terms of Publisher/Subscriber if that’s what you are after.
  • It's distributed by design and does not require single message broker. That’s great news since a single message broker may mean a single point of failure. (It does have a message broker if you need one for your mail server, for example.)
  • It's open source and cross platform.
  • It works with a variety of transports, including in-process, inter-process, TCP, multicast.
  • It's fast and scales well.

A Closer Look

ZeroMQ (also spelled ØMQ, 0MQ or ZMQ) is a high-performance asynchronous messaging library for use in distributed or concurrent applications. While it provides a message queue it is unlike message-oriented middleware in that a ZeroMQ system can run without a dedicated message broker. The library's API is designed to resemble that of Berkeley sockets.

Why choose ZeroMQ for your iOS messaging library? It’s powerful. It’s cross-platform. It’s smart. And its license is not restrictive. This unique combination of factors makes ZeroMQ аn attractive option. (Yes, there are some drawbacks. The downside is that this type of “Swiss-knife” product may be a little more complicated than desired. Just a heads up.)

So what could you use ZeroMQ for? You could use it to run a server on your Apple Watch that provides your spouse — and only your spouse — with your location. Sure, that’s probably not the best use of the server, but I’ll leave it up to you to decide how to use it.

At ICS, we’re using it to develop a prototype app. We chose to use ZeroMQ because our application runs on iOS, and while Qt works quite well with iOS, we want to get to a fully independent process where everything can be built from source code. The first step is to make an application working with ZeroMQ. I’ll walk you through our progress. But first, a word about usage patterns.

The built-in core ZeroMQ patterns are:

  • Request-reply connects a set of clients to a set of services. This is a remote procedure call and task distribution pattern.

  • Pub-sub connects a set of publishers to a set of subscribers. This is a data distribution pattern.

  • Pipeline connects nodes in a fan-out/fan-in pattern that can have multiple steps and loops. This is a parallel task distribution and collection pattern.

  • Exclusive pair connects two sockets exclusively. This is a pattern for connecting two threads in a process and not to be confused with "normal" pairs of sockets.

Here's Our Plan 

In theory it’s easy to get an application working with ZeroMQ thanks to its cross-platform nature — and the fact that someone else has done it before. We’ve discovered the difficulties begin when applying theory to a practical solutionOne of the problems is proprietary nature of Apple ecosystem. When Apple decides to change something, like replacing default compiler or changing paths, the company isn’t concerned with how this change affects other parties that do not belong to the ecosystem. Now clang is the compiler of choice for iOS/MacOS, where ZeroMQ relies on gcc/g++. Deal with it, ZeroMQ! The instructions for how to build a library for iOS are obsolete, and the build script does not work. Deal with it, Qt!

Still, we’re not to be dismayed. There are dozens of language bindings. We’re using Swift binding (find it here: https://github.com/azawawi/SwiftyZeroMQ). We don’t need Swift binding per se, but we need a working example and a working library. Swift satisfies our two requirements. But, Swift is a young language, and while it has been proven to work with iOS, how well remains to be seen.

Here's our plan: get a working example for iOS, get the library, build a working example with Qt using a pre-built library, and finally build the library from the source code. (I'll tackle the working example in this post. Next week, I'll show you how to build from source code.)

This is our environment:

  • MBP running MacOS 10.13.4
  • XCode 9.3 (9E149)
  • Qt 5.9.4
  • iPad Air 2 running iOS 11.3

SwiftyZeroMQ app

Clone, build and run. Success!! A few warnings, nothing serious, just some suggestions to update to newer version of Swift or newer version of XCode project. (Keep in mind that this project — the example I’m outlining — only does one thing: asks zmq library version. No socket, no networking, nothing.)

Still it is good enough for our purposes. There’s a zmq.h header file and set of libraries — MacOS, iOS, tvOS and watchOS. Now, I know that we can hack an Apple Watch but obtaining an actual Apple Watch will be much easier.

SwiftyZeroMQ app

Building Qt Application

While it’s a nice feature to be able to ask a library’s version, it is nearly not enough. We need actual communication flowing back forth between our iOS device and another side with a similar (or even the same) application running on MacOS that is (hopefully) built from the same source code.

Zero MQ Dev

To make this happen, I implement the Request-Reply model: one request followed by one reply. (ZeroMQ implements a number of communication models like Request-Reply, Publish-Subscribe, Pull-Push, Stream.) I just need to supply an IP address and port, and decide whether our device should be a Server (Reply side) or a Client (Request side).

Connecting as a Server

void MainWindow::msgLoop()
{
char buffer[20];
int res = zmq_recv(socket, buffer, 19, ZMQ_DONTWAIT); // Don’t hang up here, don’t wait
if (res >  0 ) {
if ( isClient ) {
ui > sendEdit_2->;setText(QString(buffer));  // Show reply - only for Request side
} else {
ui-> ;sendEdit->;setText(QString(buffer));   // Show request
buffer[0] = 'A';
buffer[1] = 'B';
zmq_send(socket, buffer, 19, 0) ;  // fix up the message and send it back
}
}
QTimer::singleShot(5000, this, &MainWindow::msgLoop);  // Repeat
}

Connecting as a Client

// Create Context first
 context = zmq_ctx_new();
 assert(context);
 // expect to get IP address and port in one line, like 11.12.13.14:5555
 QString ip_address = QString("tcp://") + ui->addressEdit-& ;text();
 socket = zmq_socket(context, ZMQ_REQ); // Request socket
 int res = zmq_connect(socket, ip_address.toLatin1());
 assert(res == 0);
 connected = true;

Message loop

With our application now in the proof-of-concept stage, I implement simple polling. It goes on both Request and Reply sides.

void MainWindow::msgLoop()
{
char buffer[20];
int res = zmq_recv(socket, buffer, 19, ZMQ_DONTWAIT); // Don’t hang up here, don’t wait
if (res >  0 ) {
if ( isClient ) {
ui > sendEdit_2->;setText(QString(buffer));  // Show reply - only for Request side
} else {
ui-> ;sendEdit->;setText(QString(buffer));   // Show request
buffer[0] = 'A';
buffer[1] = 'B';
zmq_send(socket, buffer, 19, 0) ;  // fix up the message and send it back
}
}
QTimer::singleShot(5000, this, &MainWindow::msgLoop);  // Repeat
}

Now it's time to build and deploy

Zero MQ Dev

Zero MQ Port

Before running, though I must answer a few questions:

How do I know the IP address?
Reply (Server) IP address is the one to use. If your server runs on the desktop, then it is desktop IP address, if your server runs on iPad, then it is iPad.  

Should I start Reply side first?
Not necessarily. Just don’t expect a reply if Reply side does not run

Why should I run Reply server on an iPad anyway?
Well, because you can. It is much easier to debug software on the desktop than on the device, so it is real life case where it does make sense. 

Putting It All Together

In this example we answered a very important question: does ZeroMQ work with Qt on iOS? Yes, it runs as Request side or as Reply side. It is cross-platform so the same source code builds and runs on MacOS and iOS (and on Linux  with a Linux-specific library). 

That's great news, but this is only a half of the process. Now we have to actually build the ZeroMQ library for iOS. I’ll show you how we did it next week.