qinghuan
← 回到知识探索

计算机趣闻 / 可下载小项目

编号从255回到0,是旧消息又来了,还是新消息?

有限位数的序号会绕回开头。用模差判断局部新旧,并明确相差半个空间时为什么不能随便选一边。

展开本文目录
  1. 下载完整练习包
  2. 先动手看看
  3. 先把直线上的数字改看成一圈刻度
  4. 相差128时,正走和反走一样远
  5. 能做局部比较,不代表能给整圈排出唯一顺序
  6. 完整程序把半空间与反向比较都检查一遍
  7. 32位更大,仍然没有变成无限整数
  8. 在接口里保留无法判断,才不会把信息缺失伪装成结论
  9. 记住这三点
  10. 别把例子用过头
  11. 自己试一小步
  12. 查原始资料
开始前,会这些就够了
遇到不熟悉的词,先看这 2 个解释
回绕
字段能表示的数字有限,到达上限后按约定回到开头。
半空间未定义
两个序号正好隔着半圈,规则没有指定哪一个更大。
打开完整术语小抄 ↗

一个只用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 的正向距离比较。先看跨过最大值,再试恰好半圈;四种结果都保留下来。

064128192正向距离1

■ 参考 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项有限核对。数量来自小空间枚举,不意味着已经验证真实协议的所有网络状态。

展开收起代码 · 小实验python · 90 行

长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。

小实验 · python
"""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位序号后,请自己设计一个接近边界、但完全落在有效锚点窗口里的批次。

  1. 先给出锚点、输入编号和每项正向距离,再决定应该怎样排序。
  2. 加入一个恰差半空间的编号,检查接口是否保留或拒绝歧义。
  3. 构造两段不同历史却产生相同最终有限编号,写明还缺少哪类信息。

做到这些,就算完成

  • 宽度改变后,空间大小、半空间与增量上限一起改变。
  • 没有用一个True/False吞掉未定义状态。
  • 明确锚点窗口来自额外事实;本节不提供编号或答案。

查原始资料

想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。

  • RFC1982:Serial Number Arithmetic (新标签页)↗IETF / Robert Elz、Randy Bush · 英文

    有限空间加法、比较规则、半空间未定义、8位例子以及DNS32位适用范围。

    2026-10-17 实际打开核对:第3节加法与比较、第4节推论、第5.2节8位例子和第7节DNS序号;本文没有把未定义比较强行补成全序。

  • RFC1035:SOA SERIAL字段 (新标签页)↗IETF / Paul Mockapetris · 英文

    SOA SERIAL是会回绕的32位区域版本字段,比较需使用序号空间算术。

    2026-10-17 实际打开核对:3.3.13的SERIAL字段定义;与DNS缓存TTL是不同问题。

  • RFC9293:TCP序号空间 (新标签页)↗IETF / RFC Editor · 英文

    TCP序号空间有限,运算按2^32取模;协议还需要确认与窗口等判断。

    2026-10-17 实际打开核对:3.4中的有限序号及比较说明;只作为真实协议背景,不声称实现完整TCP状态机。