ZIO Tutorial: Ports
Table of Contents
ZIO provides zio.Port
which is a high level wrapper around a PyZMQ
zmq.socket
. A port can work with a zio.Peer
(peer writup and peer
tutoral) usually via a zio.Node
(node writeup and node tutorial)
provide dynamic socket address discovery. This frees the user from
having to specify precise TCP/IP addresses and instead use abstracted
names for connections. No DNS or central naming service required! A
zio.Port
also can help send and receive zio.Message
(messages and
messages tutorial) so that their "partness" is correctly handled based
on the port's underlying socket type.
1 Create a port
Again, ports are usually created and used via zio.Node
but with a bit
of extra low-level attention, they can also be created directly.
import zmq port = zio.Port("client", zmq.CLIENT, "127.0.0.1")
A portname, the socket type and an optional hostname are given. The
last is a detail to set which network interface to use if the port
will later bind()
.
2 Connecting, binding
Before a port can be used it needs a way to talk to other ports. It may either connect or bind to ZeroMQ addresses and it may connect to abstract pairs of node/port names. And, a port may have any number and a mix of connects and binds!
port.connect("ipc://myfifo.ipc") # 1 port.connect("tcp://someserver.example.com:12345") # 2 port.connect("myfriend","spigot") # 3
- Directly connect to a Unix domain socket
myfifo.ipc
- Directly connect to a TCP/IP address
- Indirectly connect to a node/port name pair to be resolved
port.bind() # 1 port.bind("ipc://myfifo.ipc") # 2 port.bind("someserver.example.com", 12345) # 3
- Bind to first available port on the hostname given during construction
- Bind to a specific ZeroMQ address (Unix domain socket in this case)
- Bind to TCP/IP on given host and port
3 Peer headers
A zio.Port
can hold headers that may be used with a zio.Peer
. Some
headers are created based on any bind()
calls but the user may load up
arbitrary headers.
port.add_headers(greeting = 'Hello World') print (port.headers) # {'zio.port.client.greeting': 'Hello World'}
As can be seen, zio.Port
header keys are forced to be presented in a
convention adds zio.port.<portname>.
as a prefix.
4 Going online
Taking a port online requires certain stages. When ports are used via
a zio.Node
this is handled automatically. An application that
constructs a port outside a node and use peering must take care as
described in this tutorial.
port = zio.Port("client", zmq.CLIENT) port.add_headers(greeting = 'Hello World') port.bind() headers = port.do_binds() print (headers) # {'zio.port.client.greeting': 'Hello World', # 'zio.port.client.address': 'tcp://127.0.0.1:56572', # 'zio.port.client.socket': 'CLIENT'} peer = zio.Peer("mynode", **headers) port.online(peer)
This verbose sequence is needed to first allow for all ports to really
perform their binding (do_binds()
) and generate the ZIO peer headers
related to that. Then, these headers can be collected from all ports
and given to a zio.Peer
. Finally, a port makes actual connections
(online()
) and is given the peer object in order to resolve any
connections registered with node/port name pairs. If an application
only registers direct connections then a peer is not required for
online()
and if no binding is requested then the do_binds()
may be
omitted.
Again, this is much simpler when the application uses ports through a
zio.Node
.
5 Going offline
The binds and connects established by a port may be undone very simply:
port.offline()
This also "forgets" all registered binds, taking the port state essentially back to just after construction.
6 Ports and messages
A zio.Port
can send and receive ("recv") a zio.Message
. See message
tutorial for ways to construct and work with messages.
# An echo service timeout = 1000 # units of ms m = port.recv(timeout) if not m: print("timeout") else: print (m) port.send(m)
Regardless of the type of the underlying socket, the message will be properly serialized.
A port will also assure a sent message has its origin
set if the port
itself was given one.
port.origin = 42 port.send(m) # other end point: m = otherport.recv() print (m.origin) # 42