Networking (TCP)¶
The bundled TcpTransport runs real choreographies over TCP so the same
projection works in one process (simulator) or across processes/machines.
The transport protocol¶
Every Transport implements the same two async methods:
async def send(self, src, dst, value) # send `value` from role src to role dst
async def recv(self, src, dst) -> value # receive the next value from src at dst
TcpTransport gives each role a listening server (its inbox) and a pool of
outbound connections (its writers). Values are pickle-serialized and
framed with a 4-byte length prefix.
import asyncio
from klorpy.sockets import TcpTransport
from klorpy.runtime import run_role
async def main():
a = TcpTransport("A", ("127.0.0.1", 0)) # port 0 = pick an ephemeral port
b = TcpTransport("B", ("127.0.0.1", 0))
await a.start()
await b.start()
# wire peers up after both are bound
a.peers["B"] = ("127.0.0.1", b.port)
b.peers["A"] = ("127.0.0.1", a.port)
try:
ra, rb = await asyncio.gather(
run_role(ping_pong, "A", a, {"x": 42}),
run_role(ping_pong, "B", b, {"x": 42}),
)
assert rb == 42
finally:
await a.close()
await b.close()
asyncio.run(main())
Why run_role and not play_role here¶
play_role wraps a single role in its own asyncio.run loop, which is fine for
one node. A multi-role TCP run needs all servers in one event loop, so use
the async run_role and asyncio.gather.
Two OS processes¶
examples_network.py plays the same ping_pong with A and B in separate
operating-system processes, connected over loopback TCP:
$ python examples_network.py
A -> result: NOOP
B <- result: 42
demo OK: A sent 42 to B over TCP; B returned 42
Each process builds its own TcpTransport and plays one role.
TcpTransport._writer_to performs a bounded connect-retry (5 s), so a
node may start connecting before its peer's server has bound.
Caveats¶
Pickle is not safe against untrusted data
TcpTransport uses pickle for serialization. This transport is a
prototype for trusted, same-author endpoints — do not expose it as a
public-facing service.
- A node connects out to each peer lazily and pools the writer; each role must
run its own
TcpTransportand be reachable at its advertised address. - Roles on different machines: use real host addresses instead of
127.0.0.1, and make sure the listening port is reachable. play_role+ConfigTransportremain the generic way to plug in any transport (an in-process test transport, anasyncio.Queuebridge, …).TcpTransportimplementsTransportdirectly, sorun_roleaccepts it without a config wrapper.