This introduction uses the ubiquitous “Hello world” example to smoke test the python based installation of ZeroMQ. In this chapter we examine the code and illustrate some basic points useful for future chapters.
This is the interactions of hwserver and hwclient, each in a different terminal:
The message transfers start with the server listening. Nothing happens until a client says “Hello” by sending a message to the server. The server then replies by sending a message of “World”.
Please don’t be overly concerned if you find yourself somewhat confused or don’t quite understand everything. Just stick with this and the concepts will clarify.
NOTE: Please download from PyIoTMessaging. The code discussed in this blog is in the ch01 directory.
The Server Code
The hwserver.py code looks like:
import time brings in the time package. Only
sleep() gets used. Timing routines
will measure the overall responsiveness of this client/server
import zmq brings in the ZeroMQ package. Everything
we need from ZeroMQ becomes available.
ZeroMQ requires a general context to operate. The
context = xmq.Context() creates this context.
ZeroMQ uses context
to create sockets. A “context” creates a general ecological environment
to perform capabilities specific to a given goal, in this case ZeroMQ.
One and only one ZeroMQ context should be created for each process! A ZeroMQ context must be created before using any other ZeroMQ functions and this context must be destroyed at the end of all other ZeroMQ function calls. In the act of destroying the ZeroMQ context, any lingering messages get flushed.
Lines 4 and 5 create the sockets bound to the context. Specifically,
socket = context.socket(zmq.REP) uses the context created in
line 4 build a ZeroMQ socket. This socket gets further specialized into
a reply, “REP”, style socket. A reply socket in ZMQ can only for replying to
socket.bind("tcp://*:5555") creates a “home” for
a socket. This socket uses the “tcp” protocol, a standard method of transmitting
messages. Additionally, the port 5555 identifies that the socket uses
the tcp for the specific transport layer. The socket thus becomes “bound”
to port 5555. (If your eyes are rolling,
just relax and it should clear up shortly.)
Lines 8 to the end of the file contains the actual handling of
messages. The overall logic waits for a message, announces the
message, performs some “work”, i.e.,
and sends a reply of “world”.
The Client Code
The client’s logic matches the simplistic nature of the server.
The zmq package gets imported, a context created, and a socket constructed just as the server.
One important note: the socket constructor on line 13 uses
context.socket(zmq.REQ). The “REQ” constructs a socket
that send requests. ZeroMQ specializes sockets, a point we will return to.
- Get created by the client with the word “World”.
- Sent to the server.
- The server receives the message.
- The sever sends a response of “Hello”.
- The client receives the response.
Timing Hello World
Now that the basic code works, let’s do something that should convince you of the speed of ZeroMQ.
Many people get overly concerned by the speed of anything software. Performing speed test builds confidence in the system under construction. ZeroMQ has performance tests that prove speed claims. ZeroMQ should easily meet almost any necessary speed used in common micro-board computers.
Performance test should also take into consideration the robustness of software, the ease of development and ease of maintenance. A good software package is much more than just speed.
hwclient_timing.py has the comments omitted because outputting takes
a considerable amount of time. The number of messages has been soft-coded
total_messages = 100000. Depending upon the speed of your computer and
your operating system, they may be adjusted up or down.
The client logic for messaging gets wrapped in a function named
looper(). All the messages gets performed here. The
client sends “Hello” and receives a reply. That is wrapped in a
loop executed total_messages = 10000 times.
The timing client has a bit more logic added. This timing logic gets used over and over as a sanity check to ensure clean code and clean design. If we notice a significant deviation, we know something has run aground.
if __name__ == '__main__': import timeit my_setup = 'from __main__ import looper' the_timing = timeit.timeit(stmt='looper()', setup=my_setup, number=1) print('%d messages in %f secs' % \ (total_messages, the_timing)) print('%6.0f messages / sec' % (total_messages / the_timing))
When hwclient_timing.py gets run by itself, it drops into
this timing logic. A call to
a special setup with the indicated parameters. Additionally, the
strange lines at
my_setup =... pulls in the looper
function for timeit.timeit(). This becomes necessary because
timeit forks a process and that process gets times.
The two print statements at the end report on collected statistics.
hwserver_timing.py remains mostly the same but removes any output when messaging.
Remove the output echoing message traffic to create hwserver_timing.py . The server receives a message replies “World” for any message received.
Run the timing test the same as before in two windows: one
python hwserver_timing.py and the other for
For the 100,000 messages, this took 7.78 sec on my linux desktop for 13,568 msg/sec. Obviously you mileage may vary.
Since this blog also addresses Raspberry Pis, the same identical code yielded 31.64 sec for 3,161 msg/sec. This slower reading from my Rapsberry Pi 2 is expected. The inexpensive and older Raspberry Pi 2 does not have the speed of my desktop, and it obviously shows.
Let’s try something you might consider remarkable. On your desktop, start the server
python hwserver_timing.py and in another window start two timing
python hwclient_timing.py& python hwclient_timing.py &
The “&” and the end of each python command sends the process into the background. In the background they both run “simultaneously” sending and receiving messages with the server. The single server handles both client’s messages, simultaneously.
The result of this two clients banging on a server may surprise you:
The result of having two clients sending messages round trip to a single server is faster than a lone single client! Why?
The answer lies in the way our code has been written. The
recv() have been designed to be synchronous. Synchronous
means they perform their function and wait. The type of sockets, REQ and REP, always
send one request and then wait for a reply.
The client runs in lockstep and waits for a reply after sending a message.
Thus the overall flow is send a message, wait for a reply.
When multiple clients send messages to the server, the server generally is just waiting for something to do. The synchronous messaging in the server replies to each client. Due to the design of the superlative ZeroMQ sockets, each reply gets sent to the originator of the message.
Messaging Between Linux and Your Raspberry Pi
Let’s run a timing test between the desktop and a Raspberry Pi 2 system. The desktop will be the client and the Raspberry Pi the server.
This will require a bit of setup. We now tackle remote computers messaging each other.
We will connect a desktop system to a Raspberry Pi.
Because this operation becomes common as we continue to develop our messaging software, a detailed explanation will serve as a future reference.
For this test, the IP address of the Raspberry Pi is necessary to properly identify the Raspian system.
An “IP address” defines a unique string of numbers separated by periods that identifies each computer using a network. Each computer connected to the internet has an IP address.
On the Raspberry Pi system command line:
pi@raspberry1 2 pi$ ifconfig eth0 Link encap:Ethernet HWaddr b8:27:eb:28:3c:d2 inet addr:192.168.1.85 Bcast:192.168.1.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 ...
Your output may be different! What we
need is the line
inet addr:...“. This section may not be necessarily
labeled “eth0” and could easily be something different.
ifconfig reports and/or configures ip addresses
among other things. Our IP address is
Rather than a name, we need this IP address to tell our configuration
where to send/receive messages.
Test this IP address by using a command line “ping”:
ping 192.168.1.85 < -- Change to YOUR IP address!! PING 192.168.1.85 (192.168.1.85) 56(84) bytes of data. 64 bytes from 192.168.1.85: icmp_seq=1 ttl=64 time=0.343 ms 64 bytes from 192.168.1.85: icmp_seq=2 ttl=64 time=0.284 ms 64 bytes from 192.168.1.85: icmp_seq=3 ttl=64 time=0.260 ms ...
This output indicates a successful connection.
However, if your output resembles:
ping 192.168.1.85 PING 192.168.1.85 (192.168.1.85) 56(84) bytes of data. From 192.168.1.80 icmp_seq=1 Destination Host Unreachable From 192.168.1.80 icmp_seq=2 Destination Host Unreachable ...
This indicates that the IP address is not correct or that your desktop cannot somehow reach your Raspberry Pi. Check that IP again on the Raspberry Pi using:
pi@raspberry1 2 pi$ ip addr 1: lo:
mtu 65536 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 2: eth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether b8:27:eb:28:3c:d2 brd ff:ff:ff:ff:ff:ff inet 192.168.1.85/24 brd 192.168.1.255 scope global eth0 valid_lft forever preferred_lft forever
An alternate method of IP address determination uses the command
ip addr. Please verify that this IP address matches
the expected IP address. Try this IP address in the ping test.
Your IP address that runs from a home network most likely uses something called DHCP, Dynamic Host Configuration Protocol. The “dynamic” means the IP address may change. This happens when a system boots or otherwise disconnects from the local home network. A DHCP server enables computers to request IP addresses and networking parameters dynamically and reduces the net for a network manager to configure settings manually.
We will return to this important topic of manually setting an IP address to simplify our configurations.
For now, take that IP address use it in the client code. In the hwclient_timing.pw code, change the line that performs a socket connect (remember to use your IP address):
socket = context.socket(zmq.REQ) #socket.connect("tcp:///localhost:5555") # Commented out. For local talking. socket.connect("tcp://192.168.1.85:5555") # socket talks to this IP
This tells the socket to talk to the designated IP address, i.e., the Raspberry Pi server.
Summary to Find Your IP Address
On the computer with the unknown IP address:
- ifconfig # Utility for network configuration
- Find “inet …” # locate the line with the IP address
- On another system in the network: # Test that IP address
- ping IP-ADDRESS
- Should have sequence of times.
Timing Between Linux and Your Raspberry Pi
Now the software in hwclient_timing.py on the desktop has the proper IP address of the Raspberry Pi.
On the Raspberry Pi,
cd to the directory where hwserver_timing.py
lives, and start the server:
Now start the client on the desktop:
python hwclient_timing.py Connecting to hello world profiling server... 100000 messages in 49.254013 secs 2030 messages / sec
Notice the timing has slowed significantly. This expected slowness results from the less powerful computer system on the Raspberry Pi.
Time the impact of multiple clients sending messages round-trip to the server. On the desktop:
python hwclient_timing.py & python hwclient_timing.py &  2176  2177 Connecting to hello world profiling server... Connecting to hello world profiling server... 100000 messages in 49.600041 secs 2016 messages / sec 100000 messages in 49.607473 secs 2016 messages / sec
The “” and “” indicate the two python jobs will run in the background.
Run this test multiple times and notice the timings are close.
Try this test with three or more clients.
In the table below, notice the python code is only slightly slower than C code! The ZeroMQ core code uses C for optimal speed and portability.
|Server Sys||Client Sys||# Clients||Time sec||Messages/sec|
|Linux C server||Linux C||1||7.37||13,642|
|Raspberry Pi 2||Linux||1||31.64||3,151|
|Raspberry Pi 2||Raspberry Pi 2||1||49.20||2,032|
|Raspberry Pi 2||Linux||3||53.35||1,955|
All the code discussed: PyIoTMessaging in github in the PyIoTMessaging/ch01 directory.