1   Introduction

This post reports on my efforts to learn ways to send messages between hosts (nodes, devices) on a local network (LAN). It describes my attempts to learn technologies that might be useful in implementing capabilities that enable the IoT (Internet of things).

pyre is a Python implementation of zyre.

A brief description of pyre:

"Pyre - an open-source framework for proximity-based peer-to-peer applications -- Pyre does local area discovery and clustering. A Pyre node broadcasts UDP beacons, and connects to peers that it finds. This class wraps a Pyre node with a message-based API."

You can learn about pyre here: https://github.com/zeromq/pyre.

And, information about zyre is here: https://github.com/zeromq/zyre.

Both pyre and zyre are built on top of ZeroMQ. See: http://zeromq.org/ and https://github.com/zeromq.

1.1   Requirements and what it runs on

It requires Python and pyre. pyre requires pyzmq.

I've tested the attached scripts with both Python 2.7 and Python 3.x.

I've run the dealer and clients on a Raspberry Pi (running Raspbian GNU/Linux 8 (jessie)), a laptop (running Ubuntu 17.10 GNU/Linux), and a desktop machine (also running Ubuntu 17.10 GNU/Linux).

So, that gives us some breadth of platforms on which to run pyre. You will have to try it in order to learn whether it runs on your device.

2   Installing pyre

I use virtualenv and virtualenvwrapper. See: https://virtualenvwrapper.readthedocs.io/en/latest/

Then, install pyre with this:

$ pip install https://github.com/zeromq/pyre/archive/master.zip

I normally use pyre under Python 3, but it seems to work fine under Python 2.7, also.

3   Sample code

There is some sample code:

You should be able to start the client on several hosts in your local network, and then start the dealer/server to send messages to those clients. You will need to repeatedly press Enter on the clients and the dealer to send each individual message.

4   Notes about the sample code

4.1   The dealer

pyre_dealer01.py creates a pyre node and sets the header.

It then sends messages to members of the group. The message content and the number of messages sent depend on command line options.

The body of the message (the "payload") is a Python dictionary that was converted to JSON.

Because that payload is a Python string, we send it with node.shouts('group1', string_payload). If the payload were a byte array (Python type bytes), we would send it using node.shout('group1', bytes_payload).

4.2   The client

pyre_client01.py creates a pyre node, the joins the group "group1".

The client uses node.recv() to wait for and receive messages.

Each message is a list. The first item in the list is the message type.

The client skips messages until it receives a message whose type is b'SHOUT', which is a message to the group.

It prints a message and then waits for the user to press Enter or enter a "q" to quit.

5   Hints and suggestions

5.1   Sending messages

Summary:

  • To send messages to a group, use node.shout('group1', bytes) or node.shouts('group1', string). This assumes that each peer that is to receive the message has done node.join('group1').
  • To send messages to a specific node, use node.whisper(uuid, bytes) or node.whispers(uuid, string). You can use node.peers() to get a list of the UUIDs of active peers
  • To send messages that are byte arrays, use node.whisper(uuid, byte_array) or node.shout(group, byte_array).
  • To send messages that are strings, use node.whispers(uuid, message_string) or node.shouts(group, message_string).

5.2   Peers and UUIDs

Start a peer with the following:

node = pyre.Pyre()
node.set_header('description', 'peer on host apple')
node.set_header('hostname', 'apple')
node.start()

Once peers have been started, then on another host machine, you can get a list of the UUIDs of all the peers that have been started with the following:

peers = node.peers()

5.3   Headers

Each call node.set_header(key, value) adds an item to a dictionary. So, on one host, you could do:

node = pyre.Pyre()
node.set_header('description', 'peer on host apple')
node.set_header('hostname', 'apple')
node.start()

And, then on another host in the same LAN, you could do:

peers = node.peers()
headers = node.peer_headers(peers[0])
hostname = headers.get('hostname')
description = headers.get('description')

6   Another example: request and response

This example, a "dealer" distributes requests across the available pyre clients.

Here is the code:

Notes:

  • The dealer uses a simple round-robin strategy to distribute messages across available clients. In a real-world use case, it is likely that we'd need something more complex, in particular where some clients are over-loaded or some requests take longer to process.
  • In order to return a response, the client needs the UUID of the sender of the message/request. That information is automatically packed into the message by pyre. The client only needs to pick that information out of the message, then convert it from Python bytes (text) into a UUID.
  • The dealer uses the Python json module to encode the request. The client uses json to decode the request and then to encode its response. The request and response are Python dictionaries.
  • The dealer uses gevent to wait on the responses. I'm not sure that this is needed, because, after all, we are distributing load across clients. In a really application, you would have to work on this part to get the behavior you want.
  • The dealer decodes each response using the Python json module and prints out information from each response.

- Dave Kuhlman