摘要

  • RFC 1921 在一条 Telnet 连接里定义了屏幕、打印机和屏幕拷贝三个地址流;同一地址一次只能有一个尚未完成的请求。
  • 第二个同地址请求会被直接删除,但它对应的 PROTOCOL-VIOLATION 必须排在第一个请求的响应之后;协议以次序代替事务编号。
  • Terminal-Type 与 EOR 只建立共同语法和记录边界。ACK、BUSY、PURGED 或 READY 仍是局部状态,不能证明用户获权、主机业务完成或纸张已经印出。

两位响应者不能争抢同一个问句

如果一条消息没有事务号,响应靠什么找到自己的请求?最朴素的办法不是猜,而是保证任何时刻只有一个候选。TNVIP 正是这样做的,只不过它没有把整条连接锁成一列,而是按设备地址分别排队。

RFC 1921 于 1996 年 3 月以 Informational 文档发布,用 Telnet 承载 Bull 的 VIP 终端仿真。VIP 终端按块工作,还可能带有打印机。协议因此没有把所有内容视为一串无差别字符,而是在两字节头部里先写明地址:屏幕是 0x60,打印机是 0x68,屏幕拷贝打印是 0x69。

命令字的低两位再说明消息角色:无需应答的 indication、等待应答的 request、关闭当前请求的 response,以及一边肯定回答旧请求、一边提出新请求的 response-and-request。响应的意义来自“哪个地址当前挂着哪个请求”,而不是来自 ACK 这三个字母本身。

三个地址,三本未结账簿

RFC 1921 禁止在某个地址的前一请求尚未回应时,再向同一地址发送新请求。接收方遇到这种情况,要删除第二个请求。它又不能马上回 PROTOCOL-VIOLATION,因为此时按约定下一份响应仍属于第一个请求。违规回执必须等到首份响应发出之后。

这条看似别扭的顺序,保住了证据归属。若违规先到,发送方可能把它当成第一个请求的结果;若第二个请求被暗中排队,发送方又会误以为重试与执行由接收方负责。协议选择了第三条路:明确丢失,延后报告,维持一问一答的可重建性。

限制的单位是地址而不是连接。屏幕流正在等待,不等于打印机流必然没有自己的状态;屏幕拷贝还另有一套许可与结果。监控若只看 TCP 顺序,会看见一条整齐字节流,却不知道三个局部会话各自欠着什么。

这与后来 LDAP 的 Message ID 设计形成鲜明对照。LDAP 允许多项操作交错,用编号把多条响应归回原操作。TNVIP 不付出编号成本,而以每地址一个未结请求换取简单性。两种设计都能明确归属,但共同状态和并发边界不同。

协商成功没有替任何人验明身份

进入 TNVIP 之前,双方必须成功协商 Terminal-Type 与 Telnet End-of-Record。终端类型字符串可以只写型号,也可附上 @Mailbox-name。在 DSA 网关中,Mailbox 指向一个特定接入点;没有它时,服务器可以给出通用接入。名称最长 12 个字符,小写按大写处理。

这些字段能选择表现形式和逻辑入口,却没有认证含义。知道某个 Mailbox 的名字,不等于有权使用它;服务器接受一个型号,也不等于确认了某台物理设备。RFC 的安全章节直说没有处理安全问题,并设想将来用认证回答“某用户是否可以使用某终端”。

Binary Transmission 也是按需要协商的工具:八位字符要保留 255 时,数据中的该字节必须成对转义,避免被当成 Telnet 的 IAC。Suppress-Go-Ahead 则决定是否还需用 GA 配合 DSA 的传输轮次。它们调节比特和时序,不能替代主体、权限或结果证据。

EOR 只说一条记录结束了

VIP 通信以块为单位,所以每条 TNVIP 消息都以 IAC EOR 收尾。RFC 885 提供记录结束选项,RFC 854 提供 Telnet 基础。收到 EOR 可以证明接收方找到了块边界,却不能证明屏幕已更新、打印机已工作或应用接受了内容。

RFC 1921 甚至暴露了边界内部的歧义。除转义后的 IAC IAC 外,若把 Telnet 命令塞进 TNVIP 消息,接收实现可以立刻处理,也可以等当前消息处理完再做。因此文档强烈建议把 Telnet 命令放在 TNVIP 记录之间。字节次序相同,控制动作相对于数据的时刻仍可能不同;单靠边框无法消除这种差异。

同一个状态码,在不同账本里不是同一句话

TNVIP 没有一个笼统的“失败”。它区分 ERROR、BUSY、ABORTED、PURGED、NOT-AVAILABLE、PROTOCOL-VIOLATION 和 UNKNOWN-COMMAND。请求的地址未知,与设备不存在不同;命令未知,与发送次序违法也不同。未知 indication 可以被删掉且不回应,未知 request 则需要回执,因为发送方已经打开了一个等待结果的槽位。

ACK 也必须结合上下文。屏幕地址上的 ACK 可能说明前一屏幕数据处理良好,也可能只是确认进入 LOCAL 状态。打印机的 READY 是对状态查询的回答,并非打印数据请求的完成回执。屏幕的 BUSY 表示数据因终端处于 LOCAL 状态而被删除;打印机的 BUSY 则表示前一打印尚未结束,新数据被删除。

如果日志只留下状态码、不留下地址与当前请求,它会把协议原本分开的事实重新揉成一句模糊的“设备成功”或“设备忙”。

LOCAL 状态说明:送达也可以依法归零

VIP 客户端可转入 LOCAL 状态做本地测试或改配置。切换前,它向服务器发送 LOCAL-STATE 请求;服务器确认后暂停屏幕和打印机下行流,直到收到 ONLINE-STATE indication 才恢复。

若服务器仍在 LOCAL 期间发来屏幕或打印机数据,客户端会删除这些消息。连接可以健康,EOR 可以准确,地址和命令也可以合法,最终结果仍是 BUSY 与丢弃。这不是传输故障,而是已声明状态下的处理决定。

LOCAL 也不是全局冻结。客户端的屏幕上行消息,以及其他地址的消息,仍可能被允许。由此可见,“终端忙”不足以描述现实;必须说明方向、设备地址、状态请求是否确认,以及被删的是 indication 还是 request。

本地拷贝把许可与完成拆成两次判断

打印机可能同时受服务器调度。用户想把屏幕内容在本地打印时,客户端必须先通过屏幕拷贝地址发送 COPY-REQ。服务器若同意,返回 LOCAL-COPY。它不是普通 ACK,而是 response-and-request:一方面批准开始,另一方面立即等待打印结果。

客户端随后仍要用 ACK、ERROR、BUSY、ABORTED、PURGED 或 NOT-AVAILABLE 说明后续状态。许可不是执行,执行状态也不是纸面见证。服务器有权协调共享设备,客户端有权报告本地动作;没有任何一个协议字段能证明某人最终拿到并读过纸张。

这与 RFC 1318 的既有报道也不重复。后者只讨论并口硬件的 Power、Online、Busy、PaperOut 与 Fault 信号,强调信号不是打印结果;TNVIP 讨论的是远端请求如何进入设备流、如何被接受或删除、以及回执如何归属。两层相邻,但控制面不同。

清除请求的权力有一个时间边界

屏幕、打印机和屏幕拷贝流都定义了 purge indication。请求尚未确认时,接收方尝试中止处理并回 PURGED;如果已经答复,purge 就被忽略和删除。

这条规则不允许一份迟到的取消消息重写已经结清的协议历史。它也没有承诺把设备世界倒回原点。PURGED 只能报告仍在请求路径内的中止;纸张、屏幕、主机应用或人的动作若已越过这条边界,需要别的证据与补救。

后来的 Running-Code Primacy 可用来检验这种边界,但不能被冒充为 1996 年作者的动机:文档定义可验证的状态,运行实现产生实际转移,设备产生效果。三层相关,却不能互相代写。

能证明的是协议设计,不是历史采用率

RFC 1921 是主要事实来源。RFC 1091、RFC 856 与 RFC 858 分别提供 Terminal-Type、Binary 与 Suppress-Go-Ahead 的语义。RFC 1576 记录的是另一个终端体系 TN3270,用来划清而不是混合同类历史。

这些文档没有给出 TNVIP 安装数量、真实企业案例、产品一致性测试或当前使用情况。它们也没有证明用户身份、应用结果或打印成品。Minimum Initial Specification, Localized Future Decision, and Voluntary Adoption 提供的只是今日分析尺度:共同规则应薄而确定,后续设备决策和采用现实留给能在本地观察它们的参与者。

RFC 1921 的价值正在这种克制里。它没有用一个绿色连接概括所有事情,而是保存三本小账,公开第二个请求已被删除,并让错误回执遵守它所解释的次序。