1963年ASCII字符集前32个编码原本是通信协议控制符
来源标题:The message format that was already in ASCII
1963年ASCII字符集最前面的32个编码并非用于显示字符,而是一套机器间通信协议,包含SOM(消息开始)、EOA(地址结束)、WRU(你是谁)等控制信号。1967年修订版为这些编码确定了沿用至今的名称,如SOM改为SOH、EOA改为STX、WRU改为ENQ,末尾8个分隔符也被命名为FS、GS、RS、US。这套编码在此后约15年间支撑着机器之间的通信。
以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
打开1963年的ASCII表,读第一列。不是字母,是最前面那三十二个位置——你的键盘至今仍能用Control键敲出来的那些,而任何编辑器都不会显示给你看。
0x01 SOM 消息开始 0x02 EOA 地址结束 0x03 EOM 消息结束 0x04 EOT 传输结束 0x05 WRU 你是谁 0x06 RU 你是吗 0x07 BELL 响铃 ... 0x18 S0 0x19 S1 ... 0x1F S7
那不是一套字符集,那是一个协议。在ASCII成为文件和终端的字母表之前,它先是机器之间通过一根线对话的字母表,而委员会把最前面的三十二个编码全花在了这场对话上:你是谁、消息来了、地址在此、正文在此、完毕、再见。末尾那八个未命名的分隔符,则留给消息内部承载的任何结构。
1967年的修订给了所有东西沿用至今的名字。SOM变成了SOH,即Start of Heading(标题开始)。EOA变成了STX,即Start of Text(正文开始)。EOM变成了ETX。WRU变成了ENQ,即Enquiry(询问)。最后四个分隔符获得了一套层级和与之匹配的名字:FS、GS、RS、US。即File、Group、Record、Unit(文件、组、记录、单元)。最前面四个变成了CAN、EM、SUB、ESC,即Cancel、End of Medium、Substitute、Escape(取消、介质结束、替换、转义)。
在大约十五年的时间里,两台计算机就是这样交换一条记录的。相关流程写在ANSI X3.28里,而IBM的Binary Synchronous Communications——1967年随2780终端一同发布——是大多数人实际接触到的版本。
SYN SYN ENQ 我可以发送吗? SYN SYN ACK 请讲 SYN SYN SOH 标题 STX 正文 ETB BCC 第一个块 SYN SYN ACK SYN SYN SOH 标题 STX 正文 ETX BCC 最后一个块 SYN SYN ACK SYN SYN EOT 线路释放
两个SYN是为了让接收方能在比特流中找到字符边界。SOH,然后是一个标题,说明这条消息发往何处、是什么内容。STX,然后是正文。ETB结束一个后面还有内容的块,ETX结束最后一个块,每个块后面都跟一个块校验字符,好让接收方能够回ACK或NAK(BISYNC把它的确认拼成一个DLE对,交替使用ACK0和ACK1,这样就能把丢失的回复和重复的回复区分开来)。EOT释放线路。
如果正文里必须携带一个看起来像控制码的字节,在它前面放一个DLE就表示“这一个是数据”,而DLE DLE则是一个字面意义上的DLE。这种透明性自成一个模式,用DLE STX为一个块开启,再用DLE ETX关闭,于是一条为文本而建的通道也能承载二进制;普通的文本块从来不需要它。
在正文内部,四个分隔符各司其名。一个文件容纳组,一个组容纳记录,一条记录容纳单元。标准只固定了顺序,别的什么也没说:组是什么、单元是什么,那是用户自己的事。在九轨磁带或一叠打孔卡上,这恰好是恰到好处的标准化程度。
后来发生了三件事,没有一件是刻意决定的结果。
ASCII成了终端的字母表。终端会对其做出反应的控制码在日常使用中留存了下来:BEL、BS、HT、LF、CR、FF、ESC,以及作为流控的DC1和DC3。而那些用来框定机器之间对话的编码,在屏幕上无事可做。
成帧下移到了协议栈的更底层。HDLC用一个标志字节和比特填充把一帧包起来;后来的协议把长度放在帧前面;TCP提供了可靠的字节流,于是“一条消息从哪里开始”这个问题成了别人的问题。当数据包头已经说明了长度,就没人再需要STX来找到正文的起点了。
而数据格式是人用编辑器写出来的,所以它们用的是人打得出来的分隔符。逗号、制表符、尖括号、花括号和引号。先是CSV,然后是SGML和XML,再是JSON。每一个都是那种分隔符可能出现在数据里的格式,而每一个都为此付出了引用规则的代价:双写引号、实体、反斜杠。那些分隔符从来不存在这个问题,因为没人会去敲它们,而没人敲它们是因为没有编辑器显示它们。
于是它们沉寂了。不是消失了,是沉寂了。六十年来,每一个操作系统、每一种语言、每一个网络都毫发无损地携带着0x1C到0x1F,因为它们只是字节,无人认领。少数格式仍在继续使用它们。MARC格式的图书馆目录用0x1D结束一条记录、用0x1E结束一个字段、用0x1F结束一个子字段,自1970年代以来一直如此。2015年的RFC 7464用RS分隔一串JSON文本。GNU Info文件用US标记每个节点。差不多就这些了。
这句话就是全部论点。随便挑一个文本格式,问一问当一个值里包含了分隔值的那个东西时会发生什么。CSV有两个互不兼容的答案。JSON有一套转义语法,每个解析器都能做对,而每个手写生成器都会做错一次。XML有五个实体和一个CDATA段。这一切之所以存在,是因为分隔符是一个可打印字符,而可打印字符正是数据的构成材料。
ASCII委员会在1963年就解决了这个问题,办法是把分隔符放到可打印范围之外。数据是0x20及以上的字节,结构是0x20以下的字节。它们不可能冲突,所以什么都不需要引用。读取方只需扫描一个字节就能找到记录边界,而那种扫描是CPU能做的最快的事。
C0DATA就是这个消息格式的延续。三十二个编码中的十六个,每一个都做它名字所说的事,或者名字所允许的最接近的事,其余十六个则原封不动。
␖ SYN 开启一个文档;标题紧随其后 ␁ SOH 标记一个键,位于标题或记录中 ␜ FS 文件 ␝ GS 组 ␞ RS 记录 ␟ US 单元 ␂ STX 在某个值需要时,开始正文 ␃ ETX 结束正文 ␐ DLE 在正文内,转义;在正文外,开启一个嵌套层级 ␘ CAN 关闭该层级 ␇ BEL 一条注释,或标记下一个元素作废 ␅ ENQ 对别处定义的数据的引用 ␗ ETB 在日志中提交一个块 ␙ EM 介质结束:后面跟着一个原始二进制块 ␚ SUB 替换 ␄ EOT 结束文档
两个用户,姓名和金额,作为一张带表头行的表:
␖␁shape␟tabular␝users␁␞name␟amount␞Alice␟1502.30␞Bob␟340.00␄
(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。