asyncio-dgram
asyncio-dgram provides a higher-level, more convenient API for working with UDP datagram sockets in Python's asyncio framework. It simplifies common UDP operations like binding, connecting, sending, and receiving data with an async/await interface. The current version is 3.0.0, and it generally follows a release cadence driven by feature additions, bug fixes, and API improvements.
Common errors
-
TypeError: 'coroutine' object is not an async context manager
cause Attempting to `await` the result of `bind()` or `connect()` directly instead of using `async with` (a breaking change in v3.0.0).fixUse `async with bind(...) as stream:` or `async with connect(...) as stream:` instead of `stream = await bind(...)`. -
OSError: [Errno 98] Address already in use
cause Another process is already using the specified UDP port, or the previous socket was not properly closed and the OS hasn't released it yet (common after a crash).fixEnsure no other application is listening on the port. If running immediately after stopping a previous instance, wait a moment or choose a different port. On Linux, `sudo netstat -apun | grep <port>` can help identify the rogue process. -
TimeoutError: [Errno 110] Connection timed out (when sending/receiving to a remote address)
cause When using `connect()` and sending, the remote server might not be running, or a firewall is blocking packets, preventing any response from being received within a reasonable timeframe (though `asyncio-dgram`'s `send` itself doesn't typically timeout for UDP, `recv` can).fixVerify the remote host and port are correct and reachable. Check firewall settings on both sender and receiver. Ensure the remote server is actively listening for UDP packets.
Warnings
- breaking As of version 3.0.0, the `bind()` and `connect()` functions no longer return the datagram stream directly. Instead, they return an asynchronous context manager that must be used with `async with`.
- gotcha UDP is a connectionless protocol. While `asyncio_dgram.connect()` 'connects' the local socket to a specific remote address, it primarily filters outgoing and incoming packets on the local socket. It does not establish a persistent connection in the TCP sense, and `recv()` can still theoretically receive from other addresses if not explicitly handled or if the 'connected' state is not enforced by the OS.
- gotcha UDP offers no reliability guarantees; packets may be lost, duplicated, or arrive out of order. `asyncio-dgram` does not implement any form of reliability on top of UDP. Applications must handle these possibilities or use a higher-level protocol.
Install
-
pip install asyncio-dgram
Imports
- bind
from asyncio_dgram import bind
- connect
from asyncio_dgram import connect
Quickstart
import asyncio
from asyncio_dgram import bind, connect
async def udp_server(host, port):
async with bind((host, port)) as stream:
print(f"Server: Listening on {host}:{port}")
data, remote_addr = await stream.recv()
message = data.decode()
print(f"Server: Received '{message}' from {remote_addr}")
response = f"Hello from server! You sent: '{message}'".encode()
await stream.send(response, remote_addr)
print(f"Server: Sent '{response.decode()}' back to {remote_addr}")
print("Server: Stream closed.")
async def udp_client(host, port, message):
async with connect((host, port)) as stream:
print(f"Client: Sending '{message}' to {host}:{port}")
await stream.send(message.encode())
data, remote_addr = await stream.recv()
response = data.decode()
print(f"Client: Received '{response}' from {remote_addr}")
print("Client: Stream closed.")
async def main():
host = '127.0.0.1'
port = 9999
server_task = asyncio.create_task(udp_server(host, port))
# Give the server a moment to start listening
await asyncio.sleep(0.1)
client_task = asyncio.create_task(udp_client(host, port, "Hello UDP World!"))
await asyncio.gather(server_task, client_task)
if __name__ == "__main__":
asyncio.run(main())