数据报保留消息边界,缺失、重复或乱序消息仍需要应用定义处理方式。

UDP 保留数据报边界,恢复和时效由应用负责

UDP 在传输端点之间携带独立数据报,八字节首部包含来源端口、目的端口、长度和校验和字段。它不会加入 TCP 的可靠有序字节流或拥塞控制行为,让应用可以选择自己的交付策略,但不承诺所有负载下延迟都更低。应用确实需要某条内容时,丢失的数据报可能比建立连接的开销更昂贵,必须按完整请求过程评估。 坏掉的样子 发送成功不证明目标应用已经收到或处理消息。UDP 没有内建确认、重传、去重或顺序保证,因此超时不能告诉支付客户端操作是否发生。允许重试时,应用需要能够处理重复请求和结果不确定的协议,不能把没有响应直接解释成业务失败。消息是否到达、动作是否执行、结果是否持久化,也应具有不同含义。 即使本地路径健康,接收缓冲区也能制造失败。数据报大于本次调用提供的缓冲区时,内容可能截断,缺失后缀不会像字节流那样留给下一次读取。Linux 的 recvmsg 可以通过 MSG_TRUNC 报告这一情况。如果把缩短的字节当成完整命令或测量值,就可能在没有明显进程错误的情况下改变业务含义,因此必须检测协议要求的完整性。 UDP 没有内建拥塞控制,意味着应用承担责任,并不意味着可以无限制发送。需要根据部署选择发送节奏、拥塞行为、消息大小和截止时间。分片可能降低大数据报的交付稳健性,过了截止时间的数据也可能已经没有价值。QUIC 等协议能够在 UDP 之上提供可靠流,说明底层载体本身不能决定全部应用可靠性。 快速验证 将脚本保存为 check-udp.py,准备 Docker 后运行 python3 check-udp.py。固定 Python 3.11.16 Linux 容器没有外部网络路径和宿主端口发布。第一部分检查真实套接字接收语义;第二部分使用刻意编写的中继扣留、重复和重排消息,不声称测量自然发生的网络丢包。实验的每个故障输入都在代码中明确可见: import json,subprocess,uuid image="python@sha256:528257d48c1da0dcecc2e725d1ae34498d60c965f1241e39cd6a85a8859bdf84" program=r""" import json,socket with socket.socket(socket.AF_INET,socket.SOCK_DGRAM) as sender,socket.socket(socket.AF_INET,socket.SOCK_DGRAM) as receiver: receiver.bind(('127.0.0.1',0));receiver.settimeout(.25);sender.settimeout(1);target=receiver.getsockname() sender.connect(target) for body in [b'A',b'BC',b'']:sender.send(body) boundaries=[receiver.recvfrom(16)[0] for _ in range(3)] assert boundaries==[b'A',b'BC',b''] sender.send(b'abcdefgh');data,ancillary,flags,peer=receiver.recvmsg(3) assert data==b'abc' and flags&socket.MSG_TRUNC try:receiver.recvfrom(16);raise AssertionError('truncated suffix survived') except TimeoutError:tail='discarded' sender.send(b'next');assert receiver.recvfrom(16)[0]==b'next' # A deliberate application relay changes delivery; this is not measured network loss. received=[];applied=[];seen=set();forwarded=[] with socket.socket(socket.AF_INET,socket.SOCK_DGRAM) as relay,socket.socket(socket.AF_INET,socket.SOCK_DGRAM) as output: relay.bind(('127.0.0.1',0));relay.settimeout(1);output.settimeout(1) sender.connect(relay.getsockname()) for seq in [1,2,3]:sender.send(str(seq).encode()) inputs=[relay.recvfrom(16)[0] for _ in range(3)] for index in [2,0,0]: output.sendto(inputs[index],target);forwarded.append(int(inputs[index])) value=int(receiver.recvfrom(16)[0]);received.append(value) if value not in seen:seen.add(value);applied.append(value) assert received==[3,1,1] and applied==[3,1] and 2 not in seen try:receiver.recvfrom(16);raise AssertionError('missing message arrived by itself') except TimeoutError:no_retransmit=True print(json.dumps({'datagram_lengths':[len(x) for x in boundaries],'empty_datagram_is_message':boundaries[2]==b'', 'short_buffer':data.decode(),'truncation_flag':bool(flags&socket.MSG_TRUNC),'unread_tail':tail, 'relay_inputs':[int(x) for x in inputs],'relay_outputs':forwarded,'receiver':received,'deduplicated':applied, 'missing_sequence':2,'no_automatic_retransmission_during_deadline':no_retransmit})) """ name='xbcatm-socket-'+uuid.uuid4().hex[:10] try: result=subprocess.run(['docker','run','--rm','-i','--name',name,'--network','none', '--memory','128m','--pids-limit','64',image,'python3','-'],input=program, capture_output=True,text=True,timeout=60) assert result.returncode==0,result.stderr print(json.dumps(json.loads(result.stdout),ensure_ascii=False,sort_keys=True)) finally: subprocess.run(['docker','rm','-f',name],capture_output=True,text=True,timeout=10) 三次发送分别包含 A、BC 和空载荷,三次接收得到长度一、二和零。每次读取消费一条数据报,没有把相邻发送合并成字节流。空结果是合法零长度消息,不代表发送方关闭连接。发送套接字使用 UDP connect 选定对端,这个接口调用不会把 UDP 变成 TCP,也不会凭空增加确认和重传机制。 ...

十一月 11, 2026 · 1 分钟 · XB Field Notes
OPT 允许更大的 UDP 回答,服务器限制和网络交付仍然约束实际交换。

EDNS 宣告 UDP 容量,不保证沿途网络交付

EDNS 通过 OPT 伪记录扩展 DNS,其中一项作用是让请求发送方宣告自己准备接收的 UDP 载荷大小,从而允许回答超过传统五百一十二字节 DNS 限制。这是 DNS 交换中的容量宣告,不是网络路径 MTU 发现协议,也不承诺所有中间设备都会交付相同大小的数据报。理解这一区别,才能准确解释大回答为何仍可能失败。 坏掉的样子 客户端宣告四千零九十六字节,不会强迫服务器发送同样大小的响应。服务器可以实施更小的发送上限;需要的回答放不下时,可以返回 TC,客户端仍需可工作的 TCP 路径。增加客户端数字,不能修复服务器有意采用较小预算的策略。应查看两端实际配置与返回内容,而不是把请求里出现的大数值当成双方已承诺一定使用的大小。 反过来,即使回答没有超过宣告容量,也可能在途中丢失。分片、隧道、包过滤和本地网络约束,与 DNS 参与者宣告的能力是不同因素。客户端若根本没有收到响应,也就可能看不到 TC 提示。载荷配置需要按互通性验证,不能当作越大越好的统一性能参数。一次超时无法仅凭宣告值就归因于某一台服务器或某个固定网络环节。 EDNS 还承载不能当作普通缓存记录解释的信息。OPT 是描述本次交换的伪记录,其字段编码载荷容量、版本、扩展响应码和标志;外形对应 TTL 的字段,并不是普通记录寿命。DNSSEC OK 标志 DO 用于请求相关 DNSSEC 数据,却不证明任何一方完成了验证。诊断工具若丢弃这些元信息,可能把不同故障简化成相同空回答。 快速验证 将脚本保存为 check-edns.py,准备 Docker 后执行 python3 check-edns.py。固定 BIND 9.18.49 权威运行在隔离回环网络,明确把 edns-udp-size 和 max-udp-size 都设为一千二百三十二。区域中分别准备四条和八条 TXT 的记录集,以便独立观察容量与截断。实验不依赖公网默认值,也不修改现有解析器的载荷策略: ...

十月 28, 2026 · 3 分钟 · XB Field Notes