为什么选 Base64


前提条件

在做 ws2s 项目的时候, 遇到这么个问题: “如何在网络中传输字节数组”? 回顾了一下 TCP 的知识, 参考了一下 HTTP 协议、redis 序列化协议. 发现, 这个问题是有很严格的前提条件的:

我们站在建立好的 TCP 链接之上来考虑数据的传输. 那么, 所有的数据都是以字节数组的形式在传输, 就不存在”如何”的问题了, 正常传就是了. 拿 redis 序列化协议举个例子:

假设, 肉眼在控制台里看到 redis 服务端返回了这么一个字符串:

$7\r\nchen焰\r\n

实际上, TCP 之上的程序看到的是这么个东西 (8个8个看, 就是完美的字节数组):

00100100001101110000110100001010011000110110100001100101011011101110011110000100101100000000110100001010

为什么 ws2s 会认为自己传输字节数组有困难呢? 因为它与 TCP 之间, 隔着一层 json 格式. 这层 json 格式, 可以被叫作”应用层协议”, 它与 HTTP 协议、redis 序列化协议是同一层次的存在.

也就是说, 前提条件是: 在"应用层" + "应用层协议"不靠谱咯?

隔着一层 JSON 格式

从某些角度上来看, ws2s 基于 json 来规定自己的协议, 是一个错误的、失败的决定. 但是作者(也就是我)在写这段话的时候, 就已经决定了, 死不悔改, 一错到底.

一个工作了两年半的 java 工程师, web 后端方向. 平时都是在 应用层之上 写代码, 考虑的是业务逻辑的处理、模块的划分、代码结构的设计、系统架构的设计. ws2s 项目初期, 会选用 json 作为客户端与服务端之间的交互方式是理所当然的.

基于 json 来传输数据, 就限制死了思考问题的”层”.
我们经常用 json 作为 HTTP 响应体的内容, 可以不负责任的依此推测 json 在表示内容的能力上比 HTTP 更有局限性. 然而作者却把 json 放到了跟 HTTP 相同的”层”, 作为 ws2s 应用层协议的基础.
关键问题是: json 不是二进制安全的. 反观 redis 的序列化协议: $7\r\nchen焰\r\n, 先指明了接下来是一个7字节长度的东西, 然后就只需要无脑读7个字节.

基于 json 来传输数据, 也是有好处的.
首先,现成的解析工具, 使用起来非常方便.
其次, 虽然 json 在这里只用字符串表示数据, 但是纯字符串是几乎所有应用层协议的”hello world”. 有些人是可以用复制黏贴走完码农的一生的, 请相信”hello world”也是够实现所有功能的.
最后, 最重要的, 因为懒.

json 格式的不靠谱主要在于"不是二进制安全的", web后端工程师的执念让 json 这个方案活了下来, 但是传输二进制数据的问题怎么解决呢?

懒的代价

在基于 ws2s 开发 redis 客户端的过程中, 需要解析 redis 协议, 需要 1个字节、1个字节 地解析数据.

ws2s 也不得不把原始的字节数组从服务端传输到客户端. 初期为了赶着验证项目的可行性, 直接把字节数组, 转换成了10进制数字的列表, 这样就能安全的通过 json 来传输这个列表对象. 就像这样: [255,255] 表示的是: 2个“所有位上都是1的字节”. 可以看到, 这个 json 字符串花了9个字节的流量, 却只传递了2个字节的内容, 粗略估计效率为 1:4.

如果有256进制存在, 1个字节对应1个字符, 那就能 1:1了. 可是有那么多字符吗?还真的有, asscii 能有256个. 只是太多特殊字符了. 就只出来个引号, json 就废了. 就算给引号转译, 还有各种空白符、删除、响铃也是很烦人(这个真不是懒, 太危险, 工程师的直觉).

假设用16进制, 1个字符能表示4个比特, FFFF 就能表示2个“所有位上都是1的字节”, 不需要分隔符, 效率是精确的 1:2.
32进制, 1个字符能表示5个比特.
64进制, 1个字符能表示6个比特.
128进制, 1个字符能表示7个比特.

等等, 问题又回到了256进制, 普通的字符没有那么多啊.

算一下 26个字母大小写 + 10个数字 + 零零散散的标点符号, 最多也就七八十个. 那就 “64进制, 1个字符能表示6个比特” 最符合要求了. 可是16进制, 刚好能用2个字符表示8个比特. 64进制1个字符表示6个比特, 还剩下2个比特怎么办? 不要了?

哇靠! 就不要了呗! 6个6个的也能表示完整的比特流啊, 6个6个的补2个0, 不就刚好都在64进制内了吗? 简直就是天才啊! 算一下效率有 3:4.

天才? 没过几秒你就会发现, 这个想法早就有人想到了, 提出来, 还取了名字, 叫 base64.

还差个等号

6个6个的分组, 到最后不整除, 不够分怎么办?