3. UDP消息发送与接收
任务目标
实现一个最小可运行的UDP通信程序:服务器端绑定固定端口并循环接收客户端发来的数据报,客户端向该端口发送一条文本消息并接收服务器回显的应答。通过该闭环理解UDP的无连接特性——通信前不需要建立连接,数据报直接发往目标地址端口。
环境与依赖
仅使用Python 3标准库socket,无需安装第三方包。代码在Windows/Linux/macOS均可运行。
完整实现
# udp_demo.py
import socket
SERVER_HOST = "127.0.0.1"
SERVER_PORT = 8888
BUFFER_SIZE = 1024
def run_server():
"""UDP服务器:绑定端口,循环接收消息并回显"""
server_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_sock.bind((SERVER_HOST, SERVER_PORT))
print(f"[服务器] 已启动,监听 {SERVER_HOST}:{SERVER_PORT}")
while True:
data, client_addr = server_sock.recvfrom(BUFFER_SIZE)
message = data.decode("utf-8")
print(f"[服务器] 收到来自 {client_addr} 的消息: {message}")
# 回显应答
reply = f"服务器已收到: {message}"
server_sock.sendto(reply.encode("utf-8"), client_addr)
def run_client():
"""UDP客户端:发送一条消息,等待服务器应答后退出"""
client_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
message = "Hello UDP Server"
client_sock.sendto(message.encode("utf-8"), (SERVER_HOST, SERVER_PORT))
print(f"[客户端] 已发送: {message}")
# 等待应答(设置超时避免无限阻塞)
client_sock.settimeout(3)
try:
data, _ = client_sock.recvfrom(BUFFER_SIZE)
print(f"[客户端] 收到应答: {data.decode('utf-8')}")
except socket.timeout:
print("[客户端] 等待应答超时(服务器可能未启动)")
finally:
client_sock.close()
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("用法: python udp_demo.py [server|client]")
sys.exit(1)
if sys.argv[1] == "server":
run_server()
elif sys.argv[1] == "client":
run_client()
else:
print("参数错误,请输入 server 或 client")
sys.exit(1)
运行与验证
打开两个终端窗口,均进入代码所在目录:
- 启动服务器(终端1):
python udp_demo.py server,看到监听日志即表示服务器就绪。 - 启动客户端(终端2):
python udp_demo.py client。
通过条件:
- 客户端打印“已发送”和“收到应答: 服务器已收到: Hello UDP Server”;
- 服务器端打印收到来自
127.0.0.1随机端口(如('127.0.0.1', 54321))的消息,内容为Hello UDP Server。
若先启动客户端再启动服务器,客户端会在3秒后打印超时提示——这正是UDP无连接的体现:发送方不关心接收方是否在线,数据报发出即完成发送动作。
关键说明
- UDP使用
SOCK_DGRAM套接字类型,sendto/recvfrom每次调用都需携带目标地址或获取来源地址,因为无连接意味着每个数据报独立寻址。 - 服务器端
bind固定端口后,可连续接收任意客户端发来的数据报,无需accept操作。 - 客户端未绑定端口,由操作系统自动分配临时端口,因此服务器回显时使用
recvfrom返回的client_addr作为目标地址。