3

Hessian 协议解释与实战(二)

 1 year ago
source link: https://www.diguage.com/post/hessian-protocol-interpretation-and-practice-2/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Hessian 协议解释与实战(二)

2022-05-03

Hessian 协议解释与实战(二)

前段时间,翻译了 Hessian 2.0 的序列化协议,发布在了 Hessian 2.0 序列化协议(中文版)。但是,其中有很多言语不详之处。所以,接下来会用几篇文章来详细解释并实践一下 Hessian 序列化协议,以求做到知其然知其所以然。目录如下:

  1. Hessian 协议解释与实战(一) — 介绍布尔型数据、日期类型、浮点类型数据和整数类型数据等四种类型的数据的处理。

  2. Hessian 协议解释与实战(二) — 介绍长整数类型数据和字符串等两种类型的数据的处理。

在上一篇文章 Hessian 协议解释与实战(一) 中研究了布尔型数据、日期类型、浮点类型数据、整数类型数据等四种数据类型的处理方式。接下来,我们再来介绍长整数类型数据和字符串的处理情况。

基础工具方法

基础工具方法就不再赘述,请直接参考 Hessian 协议解释与实战(一):基础工具方法 中提到的几个方法。

长整数类型数据

Hessian 2.0 序列化协议(中文版):长整数类型数据 中对长整型类型的数据做了描述,处理思路与整型数据类型的处理非常类似。所以,对长整型数据的验证思路也与 Hessian 协议解释与实战(一):整数类型数据 类似,在分割点进行实验和验证。

这里有几点需要特别说明:

  1. 首先,需要特别强调的一点,协议中有一处是错误的:五个字节表示的数字的前缀是 0x59Y),而不是 0x4CL)。这里也可以从另外一个角度来看这个问题:九个字节表示数字的前缀是 0x4CL),如果五个字节的数字是正确的,则这两个冲突,哪该怎么区分这两种数字呢?

  2. 对于 -8 ~ 15 的数字,使用字节中的后六位来表示;

  3. 在编码 -2048 ~ 2047 时,使用两个字节表示。其中,后面的 12 位用于表示数值。111100000xF0000000000x00) 表示 -2048,之后就在后十二位上逐渐加 1,直到 111111110xFF111111110xFF) 表示 2047

  4. 在编码 -262144 ~ 262143 时,使用三个字节表示。其中,后面的十九位用于表示数值。001110000x38000000000x00000000000x00) 表示 -262144,之后就在后十九位上逐渐加 1,直到 001111110x3F111111110xFF111111110xFF) 表示 262143

  5. 对于 Integer.MIN_VALUE ~ -20492048 ~ Integer.MAX_VALUE 这两个区间的数字,则直接取数字对应的最后 32 位二进制,然后在前面加一个前缀 0x59 来作为序列化的结果。

  6. 除上述之外的所有数字,则都是将其二进制位,并且在前面加一个前缀 0x4CL)来作为序列化结果。

  7. 有一点需要说明一下:在处理长整数时,在程序中是按照区间范围来处理的,基本原则是用尽可能少的字节来完整表示数字。这样的话,在下一个更大范围的数字是要去除上一个区间能表示的数。这点对于整数和长整数的处理方式都是一样的。画了一个图来更详细说明情况。

hessian long

Hessian 2.0 序列化协议(中文版):字符串类型数据 中对字符串类型的数据做了描述。总得来说,还算比较清楚。但是一些细节不是特别清楚,比如“以 UTF-8 编码的 16 位 Unicode 字符串”,再比如,四个字节的 UTF-8 怎么被 16 位 Unicode 字符串表示等。这里深究一下。

在讲述 Hessian 如果处理字符串之前,我们先简要介绍一些编码与字符串的基础知识,方便后续内容展开。

编码与字符集概述

关于字符串编码有非常非常多的计算机底层知识。在探究的过程中,还是费了不少力气。关于 Unicode、UTF-8、UTF-16(以及相关变种 UTF-16BE 和 UTF-166LE)等相关知识有非常非常多,这里不展开讲解。那里提到了相关知识,会简单诉说一下,只是做些铺垫工作。后续有机会,再发文细讲。

关于编码与字符集的问题,专门去查了一些资料,越了解越心惊,没想到这里面的水是如此之深,不是简单几句就能说明白了。干脆给自己挖个坑,专门写一篇文章来详细说明吧。先把坑位选好: 细说编码与字符集

ASCII 码

ASCII 是 American Standard Code for Information Interchange 的简称,定义了128个字符的编码规则,其字符集合叫 ASCII 字符集。完整列表如下:

ASCII Table

Unicode

ASCII 码是美国制定出来针对英语的编码标准;后来,中国发展出来自己的 GB2312,后来为了增加对繁体字的支持,又扩展出来了 GB18030。其他国家也发展出来自己的编码标准。为了解决不同国家间却经常出现编码不相容的情况,发展出了 Unicode 编码。

在文字处理方面,Unicode 为每一个字符而非字形定义唯一的代码(即一个整数)。换句话说,统一码以一种抽象的方式(即数字)来处理字符,并将视觉上的演绎工作(例如字体大小、外观形状、字体形态、文体等)留给其他软件来处理。D瓜哥的理解就是给每个字符分配了一个身份证号。

在表示一个 Unicode 的字元时,通常会用 “U+” 然后紧接着一组十六进位的数字来表示这一个字元。

Unicode 的实现方式称为 Unicode转换格式(Unicode Transformation Format,简称为UTF)。目前,常用的为 UTF-8 和 UTF-16。

UTF-8 编码

Unicode 和 UTF-8 的转换关系比较统一。用表格展示:

Unicode 与 UTF-8 的转换

UTF-16 编码

UTF-16 目前可以分为两种转化格式:

  1. U ∈ [U+0000, U+D7FF] or U ∈ [U+E000, U+FFFF],则 UTF-16 和 Unicode 相同

  2. 如果 U ∈ [U+010000, U+10FFFF],则转化关系略复杂,具体如下:

UTF-16 surrogate decoder

铺垫工作基本够用了,下面开始介绍 Hessian 对字符串的处理。

Hessian 的处理方式

坦白讲,Hessian 对字符串处理的描述一脸懵逼。所以,还是直接结合 Hessian 的代码,来说明一下 Hessian 中对单个字符怎么处理的。直接上代码:

Hessian 中 Hessian2Output#printString 的代码

这段代码中,关于字符(char)的处理有三个分支,分开来说明一下:

  1. 第一个分支条件 ch < 0x80,这里的 0x80 等价于 8*16 + 0 = 128,正好是 ASCII 编码范围内的字符。所以,这个分支的意思就很明确了: ASCII 编码范围内的字符直接使用其编码来作为序列化的结果。另外,UTF-8 在 ASCII 编码范围内,与之相同。所以,这和标准中提到的使用 UTF-8 编码是没有冲突的。

  2. 第二个分支 ch < 0x800,坦白讲,最初看到这个数字是懵逼的。不知道这个 0x800。在查相关资料时,看到了 UTF-8 编码的氛围划分,在 UTF-8 - Wikipedia 中看到有 U+0800。在其上的一行内容显示为两个字节的 UTF-8 编码范围是 U+0080 ~ U+07FF ,其二进制表示是 110xxxxx + 10xxxxxx。这里的 U+07FF0x800 正好相邻,结合序列化的结果来看,两个字节表示的 UTF-8 的字符直接是使用 UTF-8 编码来作为其序列化结果。所以,从这点可以看出,这里的 0x800 就是两个字节表示的 UTF-8 的字符的上限。另外, UTF-8 编码范围的 U+0080 和上面的 0x80 也是相吻合的。

    code point utf8 conversion
  3. 第三个情况就比较复杂了。我们先来看看 《The Java® Language Specification》 中怎么来定义字符的。这里直接摘录规范原文:

    The Unicode standard was originally designed as a fixed-width 16-bit character encoding. It has since been changed to allow for characters whose representation requires more than 16 bits. The range of legal code points is now U+0000 to U+10FFFF, using the hexadecimal U+n notation. Characters whose code points are greater than U+FFFF are called supplementary characters. To represent the complete range of characters using only 16-bit units, the Unicode standard defines an encoding called UTF-16. In this encoding, supplementary characters are represented as pairs of 16-bit code units, the first from the high-surrogates range (U+D800 to U+DBFF), and the second from the low-surrogates range (U+DC00 to U+DFFF).

    The Java programming language represents text in sequences of 16-bit code units, using the UTF-16 encoding.

    — The Java® Language Specification
    Java SE 17 Edition

    从这个规范中可以看出,Java 使用 UTF-16 编码来表示文本。

    另外,在 UTF-16 - Wikipedia 中有如下描述:

    Code points from the other planes (called Supplementary Planes) are encoded as two 16-bit code units called a surrogate pair。

    Java originally used UCS-2, and added UTF-16 supplementary character support in J2SE 5.0.

    — UTF-16
    Wikipedia

    从这些描述中,可以看出,在 Java 中,在表示 BMP (Basic Multilingual Plane) 的字符时,使用一个 char 字符来表示,而且 char 值等于字符的 UTF-16 编码;在表示除 BMP 之外的 supplementary 字符时,使用两个 char 表示,两个 char 的值是 UTF-16 编码。

    基本的铺垫工作已经够了,我们来结合示例看一下 Hessian 对字符串的处理过程。

这里对于 Unicode 值大于等于 0x800 的字符的处理过程做个总结:

  1. 第一步,先将 Unicode 转换成 UTF-16 编码;对于超过 BMP 的字符,UTF-16 会将其拆分成两个字符来处理。由于 Java 内部, char 类型的数据就是使用 UTF-16 编码的,所以,这一步已经提前完成,无需再做处理。

  2. 第二步,char 值大于等于 0x800char,会将其“值”当做 Unicode 然后转换成“3个字节的UTF-8”。如果是需要两个 char 表示的字符,则当做两个“Unicode 值”处理,则 会转成两个“3个字节的UTF-8”,就是六个字节。

针对长字符串的“切割”,后续再补充。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK