1. UDP回显服务端最小实现

UDP回显服务端最小实现 学习卡片

UDP 回显服务端要解决的核心问题

UDP 是无连接协议:服务端不需要先与客户端握手,只要在本地绑定一个端口,就能持续接收任意来源的数据报。回显服务端的任务非常明确——收到什么,就原样发回给发送方。这正好用来验证 UDP 最基本的收发流程。

本案例使用 Python 实现,因为它内置 socket 模块,无需额外依赖,适合观察 UDP 的原始行为。关键点有三个:

  • 创建 AF_INET + SOCK_DGRAM 套接字,表示 IPv4 上的 UDP;
  • 用 bind() 绑定本地地址和端口,服务端必须绑定,客户端通常不需要;
  • 用 recvfrom() 接收数据报,它同时返回数据和发送方地址,再用 sendto() 把原数据发回该地址。
服务端与客户端之间没有连接状态。每次 recvfrom() 都可能来自不同客户端,因此回送时必须使用本次接收到的 addr,不能缓存上一次的地址。

最小实现代码

下面代码绑定 127.0.0.1:9999,循环接收数据报并原样回送。收到空数据报或异常时退出循环。

import socket

HOST = "127.0.0.1"   # 仅本机测试;如需局域网访问可改为 "0.0.0.0"
PORT = 9999
BUF_SIZE = 2048      # 单次接收缓冲区大小,足够容纳常见文本数据报

def main():
    # 1. 创建 UDP 套接字
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
        # 2. 绑定本地地址与端口
        s.bind((HOST, PORT))
        print(f"[UDP echo] listening on {HOST}:{PORT}")

        # 3. 循环接收并回送
        while True:
            data, addr = s.recvfrom(BUF_SIZE)
            if not data:
                print(f"[UDP echo] empty datagram from {addr}, stop")
                break
            text = data.decode("utf-8", errors="replace")
            print(f"[UDP echo] recv {len(data)} bytes from {addr}: {text!r}")

            # 4. 原样回送给发送方
            s.sendto(data, addr)
            print(f"[UDP echo] sent back {len(data)} bytes to {addr}")

if __name__ == "__main__":
    main()

收发流程与地址关系

下图展示服务端从绑定端口到回送数据报的完整信号链。重点观察:recvfrom() 返回的 addr 是本次发送方的地址,sendto() 必须使用它,而不是服务端自己的地址。

客户端 临时端口 如 127.0.0.1:54321 sendto / recvfrom UDP 回显服务端 绑定 127.0.0.1:9999 SOCK_DGRAM recvfrom / sendto ① 数据报 "hello" 源地址 = 客户端临时端口 ② 原样回送 "hello" 目标地址 = recvfrom 返回的 addr 服务端内部处理链 bind() 绑定端口 recvfrom() 收数据+地址 sendto() 原样回送 循环 等待下一报 无连接 无握手

运行与验证

先启动服务端,再用任意 UDP 工具或 Python 客户端发送一条消息。服务端应打印接收到的字节数和来源地址,并立即回送相同内容。

# 终端 1:启动服务端
python udp_echo_server.py
# 输出:[UDP echo] listening on 127.0.0.1:9999

# 终端 2:用 Python 快速验证
python -c "
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(2)
s.sendto(b'hello udp', ('127.0.0.1', 9999))
data, addr = s.recvfrom(2048)
print('echo:', data, 'from', addr)
s.close()
"
# 预期输出:echo: b'hello udp' from ('127.0.0.1', 9999)
服务端打印的 addr 是客户端的临时端口,不是 9999。回送时 sendto(data, addr) 的目标就是这个临时端口,客户端才能收到回显。

常见问题与判断依据

现象可能原因判断与处理
启动时报 Address already in use 9999 端口已被占用,或上次进程未退出 换端口,或确认旧进程已结束;UDP 没有 TIME_WAIT,通常可直接复用
客户端收不到回显 服务端未运行、地址写错、防火墙拦截 先确认服务端已打印 listening;再检查客户端目标 IP/端口;本机测试用 127.0.0.1
收到乱码 发送方编码与 decode 不一致 统一用 UTF-8;本案例用 errors="replace" 避免解码异常中断服务
大消息被截断 BUF_SIZE 小于数据报长度 UDP 一次接收对应一个数据报;把缓冲区调大,或约定消息长度上限
UDP 不保证可靠交付。本案例只验证“收到即回送”的基本流程,不处理丢包、乱序或重复。若客户端未收到回显,先排除服务端是否收到,再排查网络路径。