Can Your Network Library Do This?
Strange, Advanced And Possibly Useful
This document presents a short series of software demonstrations. Each one is about a networking behaviour that is either difficult to implement with popular networking libraries, or impossible.
It starts with the easiest — You Should Talk To This Person— which shows how a process can be a container of distinct services. There is also I Can Patch You In — the ability for messages to pass through an intermediary process on the way to somewhere else, without any additional coding. If you do these kind of things on a regular basis then read no further. It would also be a big favour if you could respond with references to what tools you used. For everyone else —prepare yourself for something outside the norm.
Background
The demonstration software is in Python and uses the Ansar library. Most platforms are supported including Linux, macOS and Windows.
A fundamental influence on the design of the Ansar library was the OSI model for networking. Without going into too much detail, a part of the related work was to create a custom addressing system and supporting send
method. This system allows any networking process to register multiple objects (i.e. in the OSI vocabulary — applications) and allow those objects to be individually available to remote processes, across a single network connection.
When a connection is established using Ansar, addresses are provided to the connecting client and the accepting server. These are addresses of the accepting and connecting objects respectively — they contain all the information that the send
method needs to route a message over a connection and deliver it to a specific object.
There is otherwise nothing special about these two addresses — the client can send
to any object in the server that it has the address for, and of course, vice versa. It is also a trivial operation to transfer addresses across connections and once that is underway the value of the OSI model begins to reveal itself.
A Quick Chat
To get things started a git
repo needs to be cloned and a Python virtual environment configured;
$ cd <folder of repos>
$ git clone https://github.com/mr-ansar/can-your-networking-library-do-this.git
$ cd can-your-networking-library-do-this
$ python3 -m venv .env
$ source .env/bin/activate
$ pip3 install ansar-connect
Included in the repo are a client and server that simulate a quick chat across a network transport. These are helpful as minimalist introductions to the toolset, but more importantly the messages used to chat are seen in action. To start the server;
$ python3 quick_chat_server.py -dl=DEBUG
[03612257] 2021-08-25T00:36:23.869 + <00000013>SocketSelect - Created by <00000001>
[03612257] 2021-08-25T00:36:23.869 < <00000013>SocketSelect - Received Start from <00000001>
[03612257] 2021-08-25T00:36:23.869 > <00000013>SocketSelect - Sent SocketChannel to <00000001>
[03612257] 2021-08-25T00:36:23.869 + <00000014>PubSub[INITIAL] - Created by <00000001>
[03612257] 2021-08-25T00:36:23.869 < <00000014>PubSub[INITIAL] - Received Start from <00000001>
[03612257] 2021-08-25T00:36:23.869 + <00000015>object_vector - Created by <00000001>
[03612257] 2021-08-25T00:36:23.869 ~ <00000015>object_vector - Executable "/home/nigel/gh/can-your-networking-library-do-this/quick_chat_server.py" as object process (3612257)
..
[03612257] 2021-08-25T01:48:43.815 + <00000022>SocketProxy[INITIAL] - Created by <00000013>
[03612257] 2021-08-25T01:48:43.815 ~ <00000013>SocketSelect - Accepted "127.0.0.1:49242", requested "127.0.0.1:5055"
[03612257] 2021-08-25T01:48:43.815 > <00000013>SocketSelect - Forward Accepted to <00000016> (from <00000022>)
[03612257] 2021-08-25T01:48:43.815 < <00000016>QuickServer - Dropped Accepted from <00000022>
[03612257] 2021-08-25T01:48:43.815 < <00000022>SocketProxy[INITIAL] - Received Start from <00000013>
[03612257] 2021-08-25T01:48:43.815 > <00000013>SocketSelect - Forward Hello to <00000016> (from <00000022>)
[03612257] 2021-08-25T01:48:43.815 < <00000016>QuickServer - Received Hello from <00000022>
[03612257] 2021-08-25T01:48:43.815 > <00000016>QuickServer - Sent Welcome to <00000022>
[03612257] 2021-08-25T01:48:43.815 < <00000022>SocketProxy[NORMAL] - Received Welcome from <00000016>
[03612257] 2021-08-25T01:48:43.916 > <00000013>SocketSelect - Sent Stop to <00000022>
[03612257] 2021-08-25T01:48:43.916 > <00000013>SocketSelect - Forward Abandoned to <00000016> (from <00000022>)
[03612257] 2021-08-25T01:48:43.916 < <00000022>SocketProxy[NORMAL] - Received Stop from <00000013>
[03612257] 2021-08-25T01:48:43.916 X <00000022>SocketProxy[NORMAL] - Destroyed
[03612257] 2021-08-25T01:48:43.916 < <00000016>QuickServer - Dropped Abandoned from <00000022>, abandoned by 127.0.0.1:49242
..
To run the client;
$ python3 quick_chat_client.py -dl=DEBUG
[03633812] 2021-08-25T01:30:24.078 + <00000013>SocketSelect - Created by <00000001>
[03633812] -25T01:30:24.078 < <00000013>SocketSelect - Received Start from <00000001>
[03633812] 2024-11-25T01:30:24.078 > <00000013>SocketSelect - Sent SocketChannel to <00000001>
[03633812] 2024-11-25T01:30:24.078 + <00000014>PubSub[INITIAL] - Created by <00000001>
[03633812] 2024-11-25T01:30:24.078 < <00000014>PubSub[INITIAL] - Received Start from <00000001>
[03633812] 2024-11-25T01:30:24.078 + <00000015>object_vector - Created by <00000001>
[03633812] 2024-11-25T01:30:24.079 ~ <00000015>object_vector - Executable "/home/nigel/gh/can-your-networking-library-do-this/quick_chat_client.py" as object process (3633812)
..
[03633812] 2024-11-25T01:30:24.079 ~ <00000013>SocketSelect - Connected to "127.0.0.1:5055", at local address "127.0.0.1:43138"
[03633812] 2024-11-25T01:30:24.079 > <00000013>SocketSelect - Forward Connected to <00000016> (from <00000017>)
[03633812] 2024-11-25T01:30:24.079 < <00000016>QuickClient - Received Connected from <00000017>
[03633812] 2024-11-25T01:30:24.079 > <00000016>QuickClient - Sent Hello to <00000017>
[03633812] 2024-11-25T01:30:24.079 < <00000017>SocketProxy[INITIAL] - Received Start from <00000013>
[03633812] 2024-11-25T01:30:24.079 < <00000017>SocketProxy[NORMAL] - Received Hello from <00000016>
[03633812] 2024-11-25T01:30:24.080 > <00000013>SocketSelect - Forward Welcome to <00000016> (from <00000017>)
[03633812] 2024-11-25T01:30:24.080 < <00000016>QuickClient - Received Welcome from <00000017>
[03633812] 2024-11-25T01:30:24.080 X <00000016>QuickClient - Destroyed
..
[03633812] 2024-11-25T01:30:24.181 > <00000013>SocketSelect - Forward Closed to <00000016> (from <00000017>)
[03633812] 2024-11-25T01:30:24.181 X <00000013>SocketSelect - Destroyed
Ack()
The ultimate output of Ack()
from the client verifies the virtual environment that you have created. To terminate the server use control-c.
This document is about a few specific behaviours associated with the movement of Hello
and Welcome
messages between different processes. If you can follow their journey through the different logs (see above), a detailed understanding of the library is not needed. The salient lines (abbreviated) are;
[03633812]…<00000016>QuickClient - Sent Hello to <00000017>
[03612257]…<00000016>QuickServer - Received Hello from <00000022>
[03612257]…<00000016>QuickServer - Sent Welcome to <00000022>
[03633812]…<00000016>QuickClient - Received Welcome from <00000017>
These lines reflect specific lines of source code in the two quick chat modules, e.g.
[03633812]…<00000016>QuickClient - Sent Hello to <00000017>
is a log entry generated by this line of code from quick_chat_client.py
;
self.send(Hello(i_am=I_AM), self.return_address)
The integer enclosed in brackets (e.g. [03633812]
) is the process id of the process that generated the log. With this information you can now see that process [03633812]
sent a Hello
message that was received in process [03612257]
, etc.
For an introduction into the general structuring of an Ansar process (such as quick_chat_client.py
and quick_chat_server.py)
, refer to A Quality Python Server In 10 Minutes.
You Should Talk To This Person
There is something you need help with and you make a call. You put the question to the person who answers and they immediately refer you to someone else. A quick subsequent conversation with the second person and everything is resolved.
To start the help desk;
$ cd can-your-networking-library-do-this
$ source .env/bin/activate
$ python3 help_desk.py --debug-level=DEBUG
..
To see the help desk in action (this should run in a separate shell);
$ cd can-your-networking-library-do-this
$ source .env/bin/activate
$ python3 customer.py --debug-level=DEBUG
..
The salient lines of logging are;
[04025362]…<00000016>Customer — Sent Enquiry to <00000017>
[04020557]…<00000016>HelpDesk — Received Enquiry from <00000019>
[04020557]…<00000016>HelpDesk — Sent UseAddress to <00000019>
[04025362]…<00000016>Customer — Received UseAddress … <00000017>
[04025362]…<00000016>Customer — Sent Hello to <00000017>
[04020557]…<00000017>quick_server — Received Hello from <00000019>
[04020557]…<00000017>quick_server — Sent Welcome to <00000019>
[04025362]…<00000016>Customer — Received Welcome from <00000017>
This sequence of messages between different objects in the two processes mimics the human interaction described above. The crucial detail to note is that the Customer
sends two messages — one to the address supplied on connection (self.return_address
) and the other to the address supplied in the UseAddress
message (message.address
) received from the HelpDesk
. The second message is received by the quick_server
object without any involvement from the HelpDesk
object. All of this interaction occurs across the single transport.
This is a concise demonstration of OSI networking in action. The basic capability can be expanded on. Consider the following replacement of UseAddress
;
response = ar.AddressBook(manager=self.manager, subscriber=self.subscriber, device=self.device)
self.send(response, self.return_address)
By switching to AddressBook
, the Customer
is now receiving a map of named addresses.
During startup the HelpDesk
creates an instance of quick_server
and saves the address in self.csr
; this is the address handed out to all connecting Customers
. The code for quick_server
looks like;
def quick_server(self):
while True:
m = self.select(Hello, ar.Stop)
if isinstance(m, ar.Stop):
return ar.Aborted()
name = m.i_am
self.send(Welcome(to_you=name), self.return_address)
ar.bind(quick_server)
Aside from the Ansar registration (ar.bind
) this is a plain Python function definition. Ansar supports both function and class based objects (quick_server
and HelpDesk
, respectively). To preserve asynchronous behaviour, every instance of a function object is allocated its own unique thread at creation time.
Use of the function-based object was deliberate and intended to reinforce the fact that Ansar objects (i.e. OSI applications) are independent threads of execution. Class based objects like HelpDesk
are referred to as machines and these execute only as messages are dispatched to them. Further detail is outside the scope of this document. Technical documentation can be found here.
I Can Patch You In
You are learning to fly a plane and the instructor suffers a medical event. You contact the control tower and the traffic controller makes a call to another instructor. It takes some time but eventually the controller says “patching you in now”. A short chat follows and together you bring the plane safely to the ground.
In this scenario there are three distinct entities; the trainee pilot, the traffic controller and the off-duty instructor. It’s fortunate that the control tower has the capability to patch the audio between two distinct connections, such that the pilot can talk directly to the instructor.
To start the instructor;
$ cd can-your-networking-library-do-this
$ source .env/bin/activate
$ python3 instructor.py --debug-level=DEBUG
..
To start the control tower;
$ cd can-your-networking-library-do-this
$ source .env/bin/activate
$ python3 control_tower.py --debug-level=DEBUG
..
To see the what’s involved in averting disaster;
$ cd can-your-networking-library-do-this
$ source .env/bin/activate
$ python3 trainee_pilot.py --debug-level=DEBUG
..
The salient lines of logging are;
[04159064]…<00000016>TraineePilot — Sent Enquiry to <00000017>
[04157622]…<00000016>ControlTower — Received Enquiry from <0000001b>
[04157622]…<00000016>ControlTower — Sent UseAddress to <0000001b>
[04159064]…<00000016>TraineePilot — Received UseAddress … <00000017>
[04159064]…<00000016>TraineePilot — Sent Hello to <00000017>
[04157622]…<00000013>SocketSelect — Forward Incognito to <0000001a> …
[04157496]…<00000016>Instructor — Received Hello from <00000017>
[04157496]…<00000016>Instructor — Sent Welcome to <00000017>
[04157622]…<00000013>SocketSelect — Forward Relay to <0000001b> …
[04159064]…<00000016>TraineePilot — Received Welcome from <00000017>
The procedure is similar to the customer/help-desk procedure, except that this time the address returned in the UseAddress
message is the address obtained by connecting to the off-duty instructor (self.table.instructor
) — the control tower is passing one remote address to another remote address.
When the trainee sends the Hello
to the supplied address it passes through the control tower without it really being aware. The only evidence is related activity in the Ansar sockets machinery (i.e. SocketSelect
).
This is a demonstration of the absolute portability of Ansar addresses. They can be passed around within a process and passed to connected processes. Receiving processes can then treat the addresses in exactly the same way, i.e. passing them on to other further processes.
Technically there is a graph of connected processes. Addresses for objects can travel anywhere within that graph and a send
operation will route the message back to the proper object. Production software based on the Ansar library routinely passes addresses around such that an associated send
passes through six intermediary processes.
Summary
This document involved some reasonably complicated concepts that necessarily involved multiple source modules, shell commands, message sequences and process logs. The evidence of operation is diffuse. Hopefully there was enough to plant the seed that OSI networking is good and Ansar addresses are very good.
OSI networking naturally elevates the source code to a better level, i.e. the application layer. Accepting this new level means leaving behind issues such as serialization, marshaling and even some of the protocol wars (e.g. HTTP, JSON, protobuf). For some, the presence of socket calls in their code is comforting. It’s particularly difficult to suggest to others that HTTP is not needed in many IPC scenarios.
There seems to be a good chance that the kind of networking presented here will remain unexplored and unexploited. Attempts at improving the model of networking offered by HTTP is ongoing, e.g. server push and multiplexing. Based on the rate of progress and its deep relationship with websites it seems reasonable to say it will never offer object addresses that are portable across an entire distributed system.