asyncio
AsyncTFTPClient and AsyncTFTPServer run the same engine on an event loop, and
AsyncTFTPRelay relays between them (see Relaying).
import asyncio
from tftp import AsyncTFTPClient, AsyncTFTPServer
async def main():
client = AsyncTFTPClient("192.0.2.1", windowsize=8)
data = await client.get("pxelinux.0")
await client.put("logs/boot.txt", b"ok\n")
async for chunk in client.stream("images/large.img"): # backpressure: a slow
await sink.write(chunk) # consumer slows the server
asyncio.run(main())
A destination is a path or anything with async write(data) to download into;
a source is a path, bytes or anything with async read(n) to upload from.
Async handlers
class Images:
async def open_read(self, context):
record = await db.fetch_image(context.peer[0], context.filename)
if record is None:
raise tftp.TFTPError(tftp.TFTPErrorCode.FILE_NOT_FOUND)
return await storage.open(record.path) # an AsyncTFTPReader
async def open_write(self, context, size):
return await storage.create(context.filename) # an AsyncTFTPWriter
async def serve():
async with AsyncTFTPServer(Images(), port=69) as server:
await server.serve_forever()
async with binds on entry and awaits aclose() on exit. await server.start()
serves in a background task and returns once listening; shutdown() (safe from any
thread) and await server.wait_closed() stop and wait; await server.aclose()
releases the socket and is final.
The hooks are coroutines and the streams are asynchronous: an
AsyncTFTPReader has async read(size) and async close(), an
AsyncTFTPWriter has async write(data) and async close(). An upload's final
ACK is sent only once the writer has taken every byte and close has returned.
A synchronous handler, such as the built-in backends, is given through the one
adapter, tftp.server.ThreadedHandler: the backends that open quickly
(opens_fast) open on the loop, the others (HTTP, upstream TFTP) in the loop's
executor. A synchronous handler given without it raises TypeError when the
server is built; a directory path needs no adapter.
from tftp.backends import MemoryBackend
from tftp.server import ThreadedHandler
server = AsyncTFTPServer(ThreadedHandler(MemoryBackend({"hello": b"hi"})), port=69)
The server keeps replying from the request's own address on every loop: where
the loop has no add_reader (Windows' default Proactor loop), the listening
socket is read by a small thread.