开始前,会这些就够了
- 理解整数大小比较
- 知道8位可以表示0到255
- 会计算除法的余数
- 需要补基础时:先看网络中的两端如何通信
一个只用8位存储的序号,到了255再加1就回到0。如果简单写“新编号大于旧编号”,255之后的0会被误判为更旧。但把所有0都当作最新也不对:它还可能属于上一次循环。
RFC1982给出一种有限序号空间里的算术规则。本文用8位小刻度讲清比较边界,尤其保留“无法按该规则确定大小”的结果。真实DNS区域序号使用32位,8位只是方便观察的教学尺寸。
把例子带到自己的电脑上
下载完整练习包
需要 Python 3.10 或更新版本,无第三方依赖;制作时实测 Windows / Python 3.14.0。使用标准库,不需要安装第三方包。下载 ZIP 后选择“全部提取”,进入能看到 README.md 的文件夹。
下载 ZIP · 5 个文件包里有什么
- serial_wrap.py:完整可运行示例
- README.md:运行说明与独立练习
- observed.stdout.txt:本次实际输出
- verification.json:源码和产物验证记录
- SHA256SUMS.txt:内容摘要
在这个文件夹打开 PowerShell,逐行运行
python --version
python -B serial_wrap.py解压到独立练习文件夹,在含 serial_wrap.py 的目录打开终端运行。RFC1982序号算术的2—8位穷举与8位展示模型,半空间差显式未定义;不发送DNS/TCP包,不实现窗口、重传、重放防护或跨重启代次识别。
找不到命令或文件时,先按 README 检查环境和所在目录。文中标出的阅读时间只估算文章篇幅,安装环境和动手练习可按自己的节奏进行。
编号绕了一圈,还能怎样比较?
按参考编号 b 到候选编号 a 的正向距离比较。先看跨过最大值,再试恰好半圈;四种结果都保留下来。
■ 参考 b = 255 ● 候选 a = 0;沿顺时针增大,超过 255 后回到 0。
按本规则,候选较新
d = (a − b) mod 256 = 1;半圈为 128。
从参考向前不到半圈,就能到达候选;实际使用还需要保证允许的比较范围。
页面演示 RFC 1982 的有限序号比较,2—8 位方便观察;真实 DNS 序号使用 32 位。它不是全局排序器,也不能仅靠编号识别跨重启或过期消息。8 位时,100 比 0 新、200 比 100 新、0 又比 200 新,说明跨任意范围比较会形成循环。
先把直线上的数字改看成一圈刻度
8位共有256个刻度,编号加法按256取余:250加10得到4。这里丢掉的是绕了几整圈的信息,不是把实际发生的10次变化取消。看见编号4,单凭这个数本身并不知道系统已经运行了多久。
为了比较候选a相对参考b的位置,计算 d=(a-b) mod 256。d为0表示序号值相等;0<d<128时,按这套规则a较新;128<d<256时,a较旧。这样0相对255的正向距离是1,可以跨过边界继续比较。
相差128时,正走和反走一样远
0与128刚好相隔半圈。从任何一端向前走128步都能到另一端,没有哪一侧更接近。RFC1982没有给这一对规定通常的大于或小于结果,使用者不应依赖实现随意返回哪边。
本文relation函数明确返回undefined-half-range,避免把含糊状态压成True或False。增量也限制在0到127;直接要求一次增加128或更大,会被increment拒绝。连续很多次合法小增量仍可能累计跨过半圈,因此必须结合比较双方允许相隔多远来使用。
能做局部比较,不代表能给整圈排出唯一顺序
考虑0、100、200:100被判断为比0新,200比100新,而0又比200新。这不是代码漏了一个分支,而是把整圈刻度当成一条无限历史直线后出现的循环关系。不能直接拿这个比较器给任意跨度的一整批记录排序。
如果额外知道参考锚点是250,且所有记录都在它向前不足128步的范围内,就可以按相对锚点的距离排序。本例[0,251,1,250]的普通数字排序与锚点排序不同;order_with_anchor会先检查这个范围前提,不满足就拒绝。
完整程序把半空间与反向比较都检查一遍
保存为serial_wrap.py,执行python -B serial_wrap.py。报告会展示250加10、0对255、255对0、128对0,以及普通排序和锚点排序的结果。其中半空间状态必须保持明确的undefined-half-range。
程序遍历2到8位范围内的全部序号对,把模差写法与RFC中按普通整数分支表达的参考规则比较,再检查交换双方后关系是否反转;还核对非法增量、超出锚点窗口和循环关系,共174757项有限核对。数量来自小空间枚举,不意味着已经验证真实协议的所有网络状态。
展开收起代码 · 小实验
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
"""RFC 1982 arithmetic teaching model with explicit half-range ambiguity."""
import json
def validate(value, bits):
if type(bits) is not int or bits < 2 or type(value) is not int or not 0 <= value < (1 << bits):
raise ValueError("value must fit the declared serial number width")
def relation(candidate, reference, bits=8):
validate(candidate, bits)
validate(reference, bits)
space = 1 << bits
distance = (candidate - reference) % space
if distance == 0:
return "equal"
if distance == space // 2:
return "undefined-half-range"
return "newer" if distance < space // 2 else "older"
def increment(value, amount, bits=8):
validate(value, bits)
if type(amount) is not int or not 0 <= amount < (1 << (bits - 1)):
raise ValueError("RFC 1982 increment must be less than half the serial space")
return (value + amount) % (1 << bits)
def order_with_anchor(values, anchor, bits=8):
validate(anchor, bits)
for value in values:
validate(value, bits)
if (value - anchor) % (1 << bits) >= (1 << (bits - 1)):
raise ValueError("every value must be inside the declared forward half-window")
return sorted(values, key=lambda value: (value - anchor) % (1 << bits))
def reference_relation(a, b, bits):
# Written directly as the RFC's ordinary-integer comparison conditions.
half = 1 << (bits - 1)
if a == b:
return "equal"
if (a < b and b - a > half) or (a > b and a - b < half):
return "newer"
if (a < b and b - a < half) or (a > b and a - b > half):
return "older"
return "undefined-half-range"
def main():
checks = 0
inverse = {"newer": "older", "older": "newer", "equal": "equal",
"undefined-half-range": "undefined-half-range"}
for bits in range(2, 9):
for a in range(1 << bits):
for b in range(1 << bits):
result = relation(a, b, bits)
assert result == reference_relation(a, b, bits)
assert inverse[result] == relation(b, a, bits)
checks += 2
assert increment(250, 10) == 4
checks += 1
try:
increment(10, 128)
raise AssertionError("half-range increment accepted")
except ValueError:
checks += 1
try:
order_with_anchor([0, 128], 0)
raise AssertionError("ambiguous window accepted")
except ValueError:
checks += 1
values = [0, 251, 1, 250]
ordered = order_with_anchor(values, 250)
assert ordered == [250, 251, 0, 1]
checks += 1
cyclic = [relation(100, 0), relation(200, 100), relation(0, 200)]
assert cyclic == ["newer"] * 3
checks += 1
print(json.dumps({"checks_passed": checks, "increment_250_by_10": increment(250, 10),
"candidate_0_reference_255": relation(0, 255),
"candidate_255_reference_0": relation(255, 0),
"candidate_128_reference_0": relation(128, 0),
"ordinary_numeric_sort": sorted(values), "anchored_order": ordered,
"cyclic_comparisons_100_after_0_200_after_100_0_after_200": cyclic,
"boundary": "8-bit illustration; finite serial values do not identify epochs"}, indent=2))
if __name__ == "__main__":
main()
怎样运行:在独立练习文件夹中执行 python -B serial_wrap.py。2026-10-17 已在 Windows、Python 3.14.0 运行此份源码并通过自带检查。RFC1982序号算术的2—8位穷举与8位展示模型,半空间差显式未定义;不发送DNS/TCP包,不实现窗口、重传、重放防护或跨重启代次识别。
运行后对照结果
{
"checks_passed": 174757,
"increment_250_by_10": 4,
"candidate_0_reference_255": "newer",
"candidate_255_reference_0": "older",
"candidate_128_reference_0": "undefined-half-range",
"ordinary_numeric_sort": [
0,
1,
250,
251
],
"anchored_order": [
250,
251,
0,
1
],
"cyclic_comparisons_100_after_0_200_after_100_0_after_200": [
"newer",
"newer",
"newer"
],
"boundary": "8-bit illustration; finite serial values do not identify epochs"
}32位更大,仍然没有变成无限整数
DNS的SOA SERIAL是会回绕的32位无符号版本字段;RFC1982专门明确了它的序号算术。TCP规范也强调32位序号的模运算,但TCP还有接收窗口、确认和连接状态等完整协议条件。本文没有把一个比较函数当成TCP接收器。
如果设备重启把序号重置,或旧消息在外部保存了很久,单独的有限序号可能无法恢复真正历史。通常需要协议保证有效比较范围,或另外提供会话/代次等信息;具体选哪种方式取决于场景,本程序没有实现跨重启识别或重放防护。
在接口里保留无法判断,才不会把信息缺失伪装成结论
调用方应区分较新、较旧、值相等和半空间未定义四种结果。值相等也只证明两份有限字段相等,不证明它们一定来自同一次事件。若业务需要精确事件身份,还必须有额外标识与生命周期约束。
决定缓存是否更新、快照是否落后之前,先写明序号宽度、允许的最大变化跨度与未定义状态如何处理。把这些前提留在文档之外,代码即使在255到0的演示中表现正确,也仍可能在长时间离线后做出错误决定。
记住这三点
- 有限序号的新旧要结合回绕规则,不能只比较十进制数字。
- 恰差半个序号空间时,本文显式保留未定义状态。
- 局部新旧关系不构成任意跨度的全局排序;历史代次需要额外信息。
别把例子用过头
- 用candidate>reference判断跨边界的新旧。
- 用小于等于半圈把半空间歧义强行归到一边。
- 把比较函数直接用于全局排序,或据此宣称完成TCP接收、重放防护与跨重启识别。
自己试一小步
改成4位序号后,请自己设计一个接近边界、但完全落在有效锚点窗口里的批次。
- 先给出锚点、输入编号和每项正向距离,再决定应该怎样排序。
- 加入一个恰差半空间的编号,检查接口是否保留或拒绝歧义。
- 构造两段不同历史却产生相同最终有限编号,写明还缺少哪类信息。
做到这些,就算完成
- 宽度改变后,空间大小、半空间与增量上限一起改变。
- 没有用一个True
/False吞掉未定义状态。 - 明确锚点窗口来自额外事实;本节不提供编号或答案。
查原始资料
想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。
- RFC1982:Serial Number Arithmetic (新标签页)↗
有限空间加法、比较规则、半空间未定义、8位例子以及DNS32位适用范围。
- RFC1035:SOA SERIAL字段 (新标签页)↗
SOA SERIAL是会回绕的32位区域版本字段,比较需使用序号空间算术。
- RFC9293:TCP序号空间 (新标签页)↗
TCP序号空间有限,运算按2^32取模;协议还需要确认与窗口等判断。