Most of the applications we develop have to communicate each other by exchanging messages. There are a lot of frameworks that implement such behavior. They are mature and highly tested, we can mention ActiveMQ, RabittMQ, nanomsg, ZeroMQ, raw BSD sockets… the list goes on and on. At the same time we hate “reinventing the wheel”, so a good decision is to evaluate a set -or all of them-, choose one and use it.
I’m not going to write about framework comparisons. I’m going to write about the choice I made: ZeroMQ. In the first part of this blog post series, I’m going to write about the objects and operation the user can use. In the second part, I’m going to write about the usage of those objects and operations. Finally, in the third part, I’m going to write about Patterns and some issues I experienced with the framework.
The KISS principle is a good approach. KISS stands for “Keep It Simple, Stupid” (read more about this principle in the blog post: KISS Principle from Laura Alonso). Having this principle in mind, the winner was ZeroMQ (ZMQ from now on). It is a simple and mature framework in active and continuous development that allows a very flexible implementation with just a few objects, without the need of deploying extra software such as brokers, like ActiveMQ requires.
ZMQ has just a few set of objects that you can use in your application. The most important and the ones you are going to use are described here. Every description is followed by a code snippet as an example. The snippets are written in C++, but you know that there are a lot of language bindings that goes from C++, C# and Java to Python, Perl and bash (yes, bash).
This is the playground for all the ZMQ objects. All objects must be created from a Context. Although you can create several Contexts in your application, it is recommended that you create only one in order to avoid mistakes (again, the KISS principle). All the objects inside one application can only interact between them if they belong to the same Context.
// Create the context.zmq::context_t context;
Every messaging activity is done through a Socket object. The name is tricky, they have nothing in common with the well-known BSD sockets. The Sockets can be created from one of the following types:
- REQ: Request Socket. This socket allows a client to send requests and receive replies to those requests in a strict synchronous way (more about this on the Request/Reply pattern description).
- REP: Reply Socket. This socket is used by services to receive requests and it send replies to those requests in a strict synchronous way (more about this on the Request/Reply pattern description).
- DEALER: This is the fully asynchronous version of the REQ Socket.
- ROUTER: This is the fully asynchronous version of the REP Socket.
- PUB: Publisher Socket. This socket is used by services that publish messages to a set of clients in a fan out fashion (more about this on the Publish/Subscribe pattern description).
- SUB: Subscriber Socket. This socket is used by subscriber clients to receive data from the publisher services (more about this on the Request/Reply pattern description).
- PUSH: This is used to distribute work among a set of working servers (more about this on the Pipeline pattern description).
- PULL: This is used in a working server to receive work to do (more about this on the Pipeline pattern description).
- PAIR: This socket is used to establish a connection with a single peer (more about this on the Exclusive pair pattern).
- STREAM: This socket is used to receive TCP data from a non-ZMQ peer (more about this on the Native pattern).
// Create a REP socket.
zmq::socket_t socket_rep( context, ZMQ_REP );
// Create a REQ socket.
zmq::socket_t socket_req( context, ZMQ_REQ );
ZMQ is a transport framework, it does not care about what you are sending. You can send raw data and it is fine. However, ZMQ gives you the concept of message to ease the data transport. The Frame object addresses this concept by letting you send and receive frames that can hold a single message or different parts of a single message.
std::string message_data = "message data";
// Create and fill the frame.zmq::message_t frame;
frame.rebuild( message_data.size() );
memcpy( frame.data(), message_data.data(), message_data.size() );
All the sockets have the same set of operation you can perform on them. There are five basic operations:
- BIND: This operation is implemented by the method bind() and asks the socket to receive connections from other peers.
- CONNECT: This operation is implemented by the method connect() and asks the socket to initiate a connection to other peers.
- SEND: This operation is implemented by the method send() and sends frames to other peers.
- RECEIVE: This operation is implemented by the method recv() and receives frames from other peers.
- CLOSE: This operation is implemented by the method close() and asks the socket to terminate all the ongoing connections.
Wait for the second part, I hope you can read it too.