Of course! Using gevent with UDP is a common and powerful pattern for building high-performance, non-blocking network services in Python. It allows you to handle thousands of concurrent UDP connections with a very small number of OS threads.

Here's a complete guide, from the basics to a practical example.
Why Use gevent for UDP?
Standard Python's socket module for UDP is blocking. When you call socket.recvfrom(), your program will pause (block) until a datagram arrives. If you want to listen on multiple ports or handle many clients, you typically need one thread per socket, which is inefficient.
gevent solves this by using coroutines (greenlets). When a greenlet makes a blocking call like recvfrom(), gevent automatically switches to another greenlet that can do work. This gives you the simplicity of single-threaded code with the performance of a multi-threaded or multi-process application.
The Core Concept: gevent.socket
gevent provides its own version of the socket module, gevent.socket. This is a drop-in replacement for the standard socket module, but all its I/O operations are non-blocking and cooperate with the gevent event loop.

The Golden Rule: Always use gevent.socket instead of the standard socket module when working with gevent.
Simple UDP Echo Server
This is the "Hello, World!" of UDP servers. It listens on a port and sends back any data it receives.
The Code
import gevent
from gevent import socket
import time
def udp_echo_server(port):
"""
A simple UDP echo server that runs on a given port.
"""
# Use gevent.socket to get a non-blocking socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('0.0.0.0', port))
print(f"UDP Echo Server listening on port {port}...")
try:
while True:
# The recvfrom() call is non-blocking and will yield control
# to the gevent event loop if no data is available.
data, addr = sock.recvfrom(1024) # Buffer size is 1024 bytes
print(f"Received from {addr}: {data.decode('utf-8')}")
# Echo the data back to the sender
sock.sendto(data, addr)
print(f"Echoed back to {addr}")
except KeyboardInterrupt:
print("\nServer shutting down.")
finally:
sock.close()
# To run the server, you just call the function.
# Since it's an infinite loop, it will run forever.
# In a real application, you'd run this in a greenlet.
udp_echo_server(9999)
How to Run It
-
Save the code as
echo_server.py. -
Run it from your terminal:
python echo_server.py
(图片来源网络,侵删) -
To test it, open another terminal and use
netcat(ornc):# Send a message to the server echo "Hello, gevent!" | nc -u 127.0.0.1 9999 # You should see the echoed message Hello, gevent!
You'll see the server's output in the first terminal:
UDP Echo Server listening on port 9999...
Received from ('127.0.0.1', 54321): Hello, gevent!
Echoed back to ('127.0.0.1', 54321)
Handling Multiple Ports (The gevent Way)
Now let's see the real power of gevent. Imagine you want to run two different UDP services on two different ports. A naive approach would be to use threads. With gevent, it's much simpler.
We'll create two server "workers" and run them concurrently using gevent.spawn().
The Code
import gevent
from gevent import socket
import time
# Server 1: A simple echo server on port 9999
def echo_server(port):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('0.0.0.0', port))
print(f"[Echo Server] Listening on port {port}...")
while True:
data, addr = sock.recvfrom(1024)
print(f"[Echo Server] Received from {addr}: {data.decode('utf-8')}")
gevent.sleep(0.5) # Simulate some work
sock.sendto(data, addr)
print(f"[Echo Server] Echoed back to {addr}")
# Server 2: A simple time server on port 8888
def time_server(port):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('0.0.0.0', port))
print(f"[Time Server] Listening on port {port}...")
while True:
data, addr = sock.recvfrom(1024)
# The time server doesn't care about the incoming data, it just replies
current_time = time.strftime("%Y-%m-%d %H:%M:%S")
response = f"Server time is: {current_time}"
sock.sendto(response.encode('utf-8'), addr)
print(f"[Time Server] Sent time to {addr}")
if __name__ == '__main__':
# Create greenlets for each server
# gevent.spawn starts a new coroutine that runs the target function
echo_greenlet = gevent.spawn(echo_server, 9999)
time_greenlet = gevent.spawn(time_server, 8888)
# gevent.joinall() waits for all the provided greenlets to complete.
# Since our servers run in infinite loops, this will wait forever.
print("Starting servers... Press Ctrl+C to stop.")
try:
gevent.joinall([echo_greenlet, time_greenlet])
except KeyboardInterrupt:
print("\nShutting down servers.")
# Greenlets can be killed
echo_greenlet.kill()
time_greenlet.kill()
How to Run and Test It
- Save as
multi_server.py. - Run:
python multi_server.py - In two separate terminals, test each server:
- Echo Server (port 9999):
echo "test message" | nc -u 127.0.0.1 9999 # Output: test message
- Time Server (port 8888):
echo "what time is it?" | nc -u 127.0.0.1 8888 # Output: Server time is: 2025-10-27 15:30:00
- Echo Server (port 9999):
Notice how both servers are running perfectly in a single thread, handling requests concurrently without blocking each other.
Advanced: A UDP "Chat" Server
This example shows how a server can maintain state (a list of connected clients) and broadcast messages to all of them.
The Code
import gevent
from gevent import socket
# A list to keep track of all connected clients
# Each client is a tuple: (socket, address)
clients = []
def broadcast(message, sender_addr=None):
"""Sends a message to all connected clients."""
for client_sock, client_addr in clients:
if client_addr != sender_addr: # Don't send back to the sender
try:
client_sock.sendto(message, client_addr)
except Exception as e:
print(f"Error sending to {client_addr}: {e}")
# Potentially remove the client if the send fails
clients.remove((client_sock, client_addr))
def chat_server(port):
"""A simple UDP chat server."""
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('0.0.0.0', port))
print(f"Chat Server listening on port {port}...")
while True:
data, addr = sock.recvfrom(1024)
# Check if the client is already in our list
client_exists = any(c[1] == addr for c in clients)
if not client_exists:
print(f"New client joined: {addr}")
clients.append((sock, addr))
# Send a welcome message to the new client
welcome_msg = f"Welcome to the chat, {addr[0]}! Your messages will be broadcasted."
sock.sendto(welcome_msg.encode('utf-8'), addr)
# Decode and broadcast the message
message = data.decode('utf-8')
print(f"Received from {addr}: {message}")
broadcast(data, addr)
if __name__ == '__main__':
try:
chat_server(7777)
except KeyboardInterrupt:
print("\nChat server shutting down.")
How to Run and Test It
-
Save as
chat_server.py. -
Run the server:
python chat_server.py -
Open three or more terminal windows.
-
In each window, connect to the chat server using
nc -u:# In terminal 1 nc -u 127.0.0.1 7777 # In terminal 2 nc -u 127.0.0.1 7777 # In terminal 3 nc -u 127.0.0.1 7777
-
Now, type a message in any terminal and press Enter. You will see that message appear in all the other terminals.
Key Takeaways and Best Practices
- Use
gevent.socket: This is the most critical step. It makes your blocking network calls non-blocking. - Think in Greenlets: Each logical task (like handling a client or listening on a port) can be a greenlet.
gevent.spawn()is your friend for creating them. - State Management: Be careful with shared state (like the
clientslist in the chat example).gevent.greenletprovidesgevent.getcurrent()to identify the current greenlet, which can be useful for locking, but for simple lists/dicts, careful programming is often enough. For more complex scenarios, considergevent.lock.Semaphore. - Graceful Shutdown: Use
try...except KeyboardInterruptto catch signals and then callgevent.joinall()orgreenlet.kill()to clean up resources properly. - No
gevent.monkeyNeeded (for pure UDP): Unlike TCP, UDP withgevent.socketdoesn't requiregevent.monkey.patch_all()because it doesn't deal with the complexities of TCP's connection states. However, if your application also does file I/O, DNS lookups, or other blocking operations,patch_all()is a good idea.
