ubuntu美化
- NUMIX: Numix 一个现代化的、扁平的 GTK3 主题
- docky: Docky是一个dock工具栏
mac下使用brew安装(来自cask)
1 | brew cask install ngork |
比较坑,需要翻墙。代理的域名也要翻墙访问。。
ngrok.com会提供一个authtoken
挖一个坑
在客户端终端执行
1
ngrok authtoken xxxxxxxxxxxxxxxxxxxxxxxxxxxx
绑定本地端口,并且启动
1
ngrok http 80
访问http://127.0.0.1:4040/可以看到ngrok运行状态
group接受这几个属性,namespace、prefix、where、as。同时group可以嵌套使用。
以下两段代码功能相同
1 |
|
1 |
|
这段时间使用Hessian传递数据,发现有很多大坑。年久失修的Hessian的PHP实现库在64位下传递数字错误连连。 之前还发现了传递emoji表情出现了问题,这几天终于追踪到问题所在。
使用PHP作为client调用Java开发的Server,返回的字符串中含有不能被json编码的字符,表现为实际的字符 串为「你好🌍,abc!」,经过HessianJava和HessianPHP后,结果输出为「你好������,abc!」, 除了这个「🌍」emoji表情外其它的字符都能正确输出。比较奇怪的是Java到Java没有问题。
我先在数据库中写了一堆emoji表情,然后在hessian2parse解析字符串的地方拦截其收到内容,打印它们的字节
| 表情 | Unicode | UTF-16 | UTF-8 bytes | 服务器收到bytes |
|---|---|---|---|---|
| 🌍 | 1 F3 0D | D8 3C DF 0D | F0 9F 8C 8D | ED A0 BC ED BC 8D |
| 🐁 | 1 F4 01 | D8 3D DC 01 | F0 9F 90 81 | ED A0 BD ED B0 81 |
这里选择部分打印出来
Hessian协议在传递数据时用的字符编码是UTF-8,可变长度,有效的压缩了数据长度,从上面的表格可以发现应 该是4字节的UTF-8,结果变成6字节,而HessianPHP这边对utf8字符串几乎没有什么处理,这么搞,字符串肯 定是乱码的。
HessianPHP这边对字符几乎的0处理,当然如果当前环境是别的字符集,会将字符串转码。但发现
HessianPHP读字符串时,只对1-3字节的UTF-8字符有识别,对4字节的UTF-8没有处理。如果把PHP这边同
时作为服务和客户端测试时,也出现bug,长度读取不对。
1 |
|
这么修改后,php之间的传递就没有问题了。详细的UTF-8说明参考这, wiki:UTF-8
再看Java部分前,有一个背景需要知道,从1.5版开始,Java储存字符使用UTF-16的方式,
每个char长度为2Bytes。可以参考wiki:UTF-16,
了解其编码方式。
在Unicode的零号平面(BMP)中,UTF-16数值等价于对应的码位。 Unicode中除了BMP外,还有16个辅助平面,码位为U+10000到U+10FFFF。 在UTF-16中被编码为一对16比特长的码元(即32bit,4Bytes)。
简而言之,就是像ASCII字符、中文字符等这些在零号平面中的字符在Java中由一个char(2Bytes)表示, 而emoji这样在辅助平面上的字符由2个char(4Bytes)表示,理论上能实现所有的Unicode字符编码了。
看看Java这边的处理,以下是写入部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35/**
* Prints a string to the stream, encoded as UTF-8
*
* @param v the string to print.
*/
public void printString(String v, int strOffset, int length)
throws IOException
{
int offset = _offset;
byte []buffer = _buffer;
for (int i = 0; i < length; i++) {
if (SIZE <= offset + 16) {
_offset = offset;
flushBuffer();
offset = _offset;
}
char ch = v.charAt(i + strOffset);
if (ch < 0x80)
buffer[offset++] = (byte) (ch);
else if (ch < 0x800) {
buffer[offset++] = (byte) (0xc0 + ((ch >> 6) & 0x1f));
buffer[offset++] = (byte) (0x80 + (ch & 0x3f));
}
else {
buffer[offset++] = (byte) (0xe0 + ((ch >> 12) & 0xf));
buffer[offset++] = (byte) (0x80 + ((ch >> 6) & 0x3f));
buffer[offset++] = (byte) (0x80 + (ch & 0x3f));
}
}
_offset = offset;
}
这部分代码的目的是将字符串从Java下的字符串(UTF-16)转为UTF-8编码,但明显它只能满足在BMP上码位的转码,不能支持辅助平面。
再来看看读的部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26/**
* Parses a single UTF8 character.
*/
private int parseUTF8Char()
throws IOException
{
int ch = _offset < _length ? (_buffer[_offset++] & 0xff) : read();
if (ch < 0x80)
return ch;
else if ((ch & 0xe0) == 0xc0) {
int ch1 = read();
int v = ((ch & 0x1f) << 6) + (ch1 & 0x3f);
return v;
}
else if ((ch & 0xf0) == 0xe0) {
int ch1 = read();
int ch2 = read();
int v = ((ch & 0x0f) << 12) + ((ch1 & 0x3f) << 6) + (ch2 & 0x3f);
return v;
}
else
throw error("bad UTF-8 encoding at " + codeName(ch));
}
Hessian是按UTF-8编码传输,在这个方法中应该是将UTF-8转为UTF-16,按照UTF-8编码方案这里没有处理4字节的字符。
由于写入的函数只是将每一个字符单独转成UTF-8字符,可能由两个char表示一个字符的,也这样分别被转成两个 UTF-8字符。读取部分,也是这么分别读,最后获取到的char没有问题,弄拙成巧,Java-Java部分就正常传输了。
需要java那边把读写的方法修改为支持U+10000到U+10FFFF的码位
写入修复
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59/**
* Prints a string to the stream, encoded as UTF-8
*
* @param v the string to print.
*/
public void printString(String v, int strOffset, int length)
throws IOException
{
int offset = _offset;
byte []buffer = _buffer;
for (int i = 0; i < length; i++) {
if (SIZE <= offset + 16) {
_offset = offset;
flushBuffer();
offset = _offset;
}
char ch = v.charAt(i + strOffset);
if (ch < 0x80)
buffer[offset++] = (byte) (ch);
else if (ch < 0x800) {
buffer[offset++] = (byte) (0xc0 + ((ch >> 6) & 0x1f));
buffer[offset++] = (byte) (0x80 + (ch & 0x3f));
}
else
// three Bytes, D800-DFFF do not allow the existence characters
if (ch < 0xD800 || ch >= 0xE000)) {
buffer[offset++] = (byte) (0xe0 + ((ch >> 12) & 0xf));
buffer[offset++] = (byte) (0x80 + ((ch >> 6) & 0x3f));
buffer[offset++] = (byte) (0x80 + (ch & 0x3f));
}
else
{
// 0xD800 - 0xE000 Unicode characters, Supplementary Planes, need four bytes
// take the next char
i++;
char ch1 = v.charAt(i + strOffset);
// get unicode code point from two chars
// int code = ((ch - 0xD800) << 10) + ch1 - 0xDC00 + 0x10000;
int code = ((ch - 0xD800) << 10) + ch1 + 0x2400;
// unicode code to utf-8
buffer[offset++] = (byte) (0xf0 + ((code >> 18) & 0x7));
buffer[offset++] = (byte) (0x80 + ((code >> 12) & 0x3f));
buffer[offset++] = (byte) (0x80 + ((code >> 6) & 0x3f));
buffer[offset++] = (byte) (0x80 + (code & 0x3f));
}
else {
// throw
}
}
_offset = offset;
}
读取修复,有点蛋疼,在这不能修复,除了parseUTF8Char方法修复,调用此方法的也需要跟踪修复,在此只做了对parseUTF8Char方法的修复。
1 | /** |
java端不进行修改,php端修复根据问题规则修改解析。
1 |
|
本文使用的HessianPHP版本为v2.0.3,源码来源于此
HessianPHP
Java的Hessian包版本为4.0.37,源码来源于此
hessian-4.0.37-src.jar
本文从wiki和其它博文中搬了一些定义
字节(Octet): 是一个八位的存储单元,也称为Byte
字符(Character): 逻辑上的字,像「A」,「是」,「😝」都是一个字符
Unicode是为了解决传统的字符编码方案的局限而产生的。类似所有字符的集合,每一个字符在Unicode中都有唯一『编码』,这个值称为代码点(code point),通常会用“U+”然后紧接着一组十六进制的数字来表示这一个字符。在基本多文种平面(英文:Basic Multilingual Plane,简写BMP。又称为“零号平面”、plane 0)里的所有字符,要用四个数字(即两个char,16bit ,例如U+4AE0,共支持六万多个字符);在零号平面以外的字符则需要使用五个或六个数字。
一个字符的Unicode编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(Unicode/UCS Translation Format,简称为UTF)。
Unicode.org定义了百万个以上的字符,如果将所有的字符用大小表示,需要的是4个字节。“a“的Unicode表示就会变成0x00000061,而“一“的Unicode值是0x00004E00。实际上,这就是UTF32,Linux操作系统上所使用的Unicode方案。而Windows平台下默认的Unicode编码方式为Little Endian的UTF-16。
UTF-32用四个字节表示代码点,这样就可以完全表示UCS-4的所有代码点,而无需像UTF-16那样使用复杂的算法。与UTF-16类似,UTF-32也包括UTF-32、UTF-32BE、UTF-32LE三种编码,UTF-32也同样需要BOM字符。仅用’ABC’举例:
但是,仔细分析可以发现,其实绝大部分字符只使用2个字节就可以表示了。英文的Unicode范围是0x0000-0x007F,中文的Unicode范围是0x4E00-0x9F**,真正需要扩展到4个字节来表示的字符少之又少,所以有些系统直接使用2个字节来表示Unicode。比如Windows系统上,Unicode就是两个字节的。对于那些需要4个字节才能表示的字符,使用一种代理的手法来扩展(其实就是在低两个字节上做一个标记,表示这是一个代理,需要连接上随后的两个字节,才能组成一个字符)。这样的好处是大量的节约了存取空间,也提高了处理的速度。这种Unicode表示方法就是UTF16。一般在Windows平台上,提到Unicode,那就是指UTF16了。
UTF-16由RFC2781规定,它使用两个字节来表示一个代码点。
不难猜到,UTF-16是完全对应于UCS-2的,即把UCS-2规定的代码点通过Big Endian或Little Endian方式直接保存下来。UTF-16包括三种:UTF-16,UTF-16BE(Big Endian),UTF-16LE(Little Endian)。
UTF-16BE和UTF-16LE不难理解,而UTF-16就需要通过在文件开头以名为BOM(Byte Order Mark)的字符来表明文件是Big Endian还是Little Endian。BOM为U+FEFF这个字符。
其实BOM是个小聪明的想法。由于UCS-2没有定义U+FFFE,因此只要出现 FF FE 或者 FE FF 这样的字节序列,就可以认为它是U+FEFF,并且可以判断出是Big Endian还是Little Endian。
举个例子。“ABC”这三个字符用各种方式编码后的结果如下:
Windows平台下默认的Unicode编码为Little Endian的UTF-16(即上述的 FF FE 41 00 42 00 43 00)。你可以打开记事本,写上ABC,然后保存,再用二进制编辑器看看它的编码结果。
UTF-16和UTF-32的一个缺点就是它们固定使用两个或四个字节,这样在表示纯ASCII文件时会有很多00字节,造成浪费。而RFC3629定义的 UTF-8则解决了这个问题。UTF-8用1~4个字节来表示代码点。表示方式如下:
|
代码范围 十六进制 |
标量值(scalar value) 二进制 |
UTF-8
十六进制 |
注释 |
|---|---|---|---|
|
000000 - 00007F
128个代码 |
00000000 00000000 0zzzzzzz | 0zzzzzzz(00-7F) | ASCII字符范围,字节由零开始 |
| 七个z | 七个z | ||
|
000080 - 0007FF 1920个代码 |
00000000 00000yyy yyzzzzzz | 110yyyyy(C0-DF) 10zzzzzz(80-BF) | 第一个字节由110开始,接着的字节由10开始 |
| 三个y;二个y;六个z | 五个y;六个z | ||
|
000800 - 00D7FF 00E000 - 00FFFF 61440个代码 注1 |
00000000 xxxxyyyy yyzzzzzz | 1110xxxx(E0-EF) 10yyyyyy 10zzzzzz | 第一个字节由1110开始,接着的字节由10开始 |
| 四个x;四个y;二个y;六个z | 四个x;六个y;六个z | ||
|
010000 - 10FFFF 1048576个代码 |
000wwwxx xxxxyyyy yyzzzzzz | 11110www(F0-F7) 10xxxxxx 10yyyyyy 10zzzzzz | 将由11110开始,接着的字节由10开始 |
| 三个w;二个x;四个x;四个y;二个y;六个z | 三个w;六个x;六个y;六个z |
可见,ASCII字符(U+0000~U+007F)部分完全使用一个字节,避免了存储空间的浪费。而且UTF-8不再需要BOM字节。
另外,从上表中可以看出,单字节编码的第一字节为[00-7F],双字节编码的第一字节为[C2-DF],三字节编码的第一字节为[E0-EF]。这样只要看到第一个字节的范围就可以知道编码的字节数。这样也可以大大简化算法。
[wiki:UTF-8]a
字符编码问题,UNICODE\UTF-8\UTF-16\UTF-32\UCS\ANSI\GBK\GB2312等乱七八糟的名词 – 浅显易懂的说明字符编码,还有Big Endian和Little Endian的说明
| 符合 | 说明 |
|---|---|
| ~ | 波浪线表示执行一个正则匹配,区分大小写 |
| ~* | 表示执行一个正则匹配,不区分大小写 |
| ^~ | ^~表示普通字符匹配,如果该选项匹配,只匹配该选项,不匹配别的选项,一般用来匹配目录 |
| = | 进行普通字符精确匹配 |
| @ | “@” 定义一个命名的 location,使用在内部定向时,例如 error_page, try_files |
= 精确匹配会第一个被处理。如果发现精确匹配,nginx停止搜索其他匹配。 普通字符匹配,正则表达式规则和长的块规则将被优先和查询匹配,也就是说如果该项匹配还需去看有没有正则表达式匹配和更长的匹配。
^~ 则只匹配该规则,nginx停止搜索其他匹配,否则nginx会继续处理其他location指令。 最后匹配理带有”~”和”~*”的指令,如果找到相应的匹配,则nginx停止搜索其他匹配;当没有正则表达式或者没有正则表达式被匹配的情况下,那么匹配程度最高的逐字匹配指令会被使用。
Directives with the = prefix that match the query exactly. If found, searching stops. All remaining directives with conventional strings, longest match first. If this match used the ^~ prefix, searching stops.
Regular expressions, in order of definition in the configuration file. If #3 yielded a match, that result is used. Else the match from #2 is used.
=前缀的指令严格匹配这个查询。如果找到,停止搜索。
所有剩下的常规字符串,最长的匹配。如果这个匹配使用^〜前缀,搜索停止。
正则表达式,在配置文件中定义的顺序。
如果第3条规则产生匹配的话,结果被使用。否则,如同从第2条规则被使用。
例如
1 | location = / { |
请求URI例子:
/ -> 符合configuration A
/documents/document.html -> 符合configuration B
/images/1.gif -> 符合configuration C
/documents/1.jpg ->符合 configuration D
@location 例子
1
2
3
4
5error_page 404 = @fetch;
location @fetch(
proxy_pass http://fetch;
)
转载请保留: http://www.nginx.cn/115.html
1 | sudo apt-get install tmux |
1 | sudo apt-get install rake |
终端配色方案修改为solarized
编辑 > 编辑配置文件 > 颜色
1 | TMUX_POWERLINE_PATH=~/.tmux-powerline |
编辑.tmux.conf
1 | set-option -g status on |
…
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重 载的方法(或者构造函数)都有一个独一无二的参数类型列表。
Hessian的客户端(调用者,如PHP)不能也不会识别调用的参数类型,从而传递相应的参数列表。另一方面java 端接受请求后,只通过方法名和参数数量来取得方法。
在使用像php这样的弱类型语言通过Hessian调用java端服务时,重载方法的使用存在问题。
下面来看服务端的源码:
1 | // com.caucho.services.server.AbstractSkeleton |
在上面这段程序中,我们可以知道hessian按以下三种方式作为key存了方法
1 | // com.caucho.hessian.server.HessianSkeleton extends AbstractSkeleton |
当有以下一个接口时
1 | public interface TestService { |
将产生以下方法
如果我们在客户端中调用时
1 |
|
这样调用,java端会选择同一个方法,getName__1,可能是getName(User user)或者
getName(int uid),很有可能就报unsafeDeserialize异常了。
在使用这样参数数量相同的重载方法时,按照java端的获取规则,我们可以这么调用
1 |
|
这样就可以完美调用重载的方法了。
翻译: Edison peng
Hessian是一个轻量级的,自定义描述的二进制RPC协议。Hessian主要用作面向对象的消息通信。
Hessian的初衷是支持动态类型,格式紧凑,跨语言.
Hessian协议的设计目标如下:
Hessian语法序列化语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 #starting production
top ::=value
#分割成64k每chunk的8-bit二进制数据
binary ::= 'b' b1 b0 <binary-data> binary #不是最后一个chunk
::= 'B' b1 b0 <binary-data> #最后一个chunk
::= [x20-x2f] <binary-data> #长度范围为 0-15
#boolean true/false
boolean ::= 'T'
::= 'F'
#对象的定义(compact map)
class-def ::= 'O' type int string*
#time in UTC encoded as 64-bit long milliseconds since epoch
date ::= 'd' b7 b6 b5 b4 b3 b2 b1 b0
#64-bit IEEE double
double ::= 'D' b7 b6 b5 b4 b3 b2 b1 b0
::= x67 #0.0
::= x68 #1.0
::= x69 b0 #byte表示的double(-128.0 to 127.0)
::= x6a b1 b0 #short表示的double
::= x6b b3 b2 b1 b0 #32-bit float表示的double
#32-bit 有符号整型
int ::= 'I' b3 b2 b1 b0
::= [x80-xbf] #-x10 to x3f
::= [xc0-xcf] b0 #-x800 to x7ff
::= [xd0-xd7] b1 b0 #-x40000 to x3ffff
# list/vector length
length ::= 'l' b3 b2 b1 b0
::= x6e int
# list/vector
list ::= 'V' type? length? value* 'z'
::= 'v' int int value* #第一个int表示类型引用, 第二个int表示长度
#64-bit有符号long
long ::= 'L' b7 b6 b5 b4 b3 b2 b1 0
::= [xd8-xef] #-x08 to x0f
::= [xf0-xff] b0 #-x800 to x7ff
::= [x38-x3f] b1 b0 #-x40000 to x3ffff
::= x77 b3 b2 b1 b0 #32-bit 整型表示的long
#map/object
map ::= 'M' type? (value value)* 'z' #key, value map pairs
# null value
null ::= 'N'
#对象实例
object ::= 'o' int value*
#值引用
ref ::= 'R' b3 b2 b1 b0 # 对流中第n个map/list/object的引用
::= x4a b0 # 对map/list/object的引用,范围为1-255th
::= x4b b1 b0 # 对map/list/object 的引用,范围为1-65535th
#UTF-8 编码的字符串,分割成64k大小的chunks
string ::= 's' b1 b0 <utf8-data> string #非末尾chunk
::= 'S' b1 b0 <utf8-data> #长度范围为(0-65535)的字符串
::=[x00-x1f] <utf8-data> #长度范围为(0-31) 的字符串
#map/list 的类型(针对面向对象语言)
type ::= 't' b1 b0 <type-string> #类型名称
::= x75 int #类型引用值(用整数表示)
#main production
value ::=null
::= binary
::= boolean
::= date
::= double
::= int
::= list
::= long
::= map
::= class-def value
::= ref
::= string
Hessian的对象序列化机制包含8种基本类型:
另外包括3种递归类型:
最后,它还包含一个特殊的类型:
Hessian 2.0有3个内部的引用表:
二进制数据语法
binary ::= b b1 b0
二进制数据被分割成chunk. 十六进制数x42(‘B’)标识最后一个chunk,x62(‘b’)标识普通的chunk. 每个chunk有一个16-bit的长度值.
1 | len = 256*b1 + b0 |
长度小于15的二进制数据只需要用单个十六进制数字来表示[x20-x2f].
1 | len = code - 0x20 |
x20 #零长度的二进制数据 x23 x01 x02 x03 #长度为3的数据 B x10 x00 …. #4k大小的final chunk b x04 x00 …. #1k大小的non-final chunk
Boolean语法
1 | boolean ::= T |
用16进制’F’来表示false,用’T’表示true.
T # true F # false
date ::=d b7 b6 b5 b4 b3 b2 b1 b0
Date采用64-bit来表示距1970 00:00H, UTC以来经过的milliseconds.
D x00 x00 xd0 x4b x92 x84 xb8 #2:51:31 May 8, 1998 UTC
1 | double ::= D b7 b6 b5 b4 b3 b2 b1 b0 |
符合IEEE标准的 64-bit浮点数.
0.0用十六进制x67来表示 (对应ascii中字符g的ascii值)
1.0用十六进制x68来表示
介于-128.0和127.0之间的无小数位的double型可以用两个十六进制来表示(如x3b表示的),也即相当于一个byte值转换成double:
1 | value = (double)b0 |
介于-32768.0和32767.0之间的无小数位的double型可以用3个十六进制数来表示,也即相当于一个short值转换成double:
1 | value=(double)(256*b1 + b0) |
和32-bit float型等价的double能够用4个十六进制的 float来表示.
1 | x67 # 0.0 |
1 | int ::= ’I’ b3 b2 b1 b0 |
32-bit的有符号整型. 一个整型由跟随在x49(‘I’)之后的4个大端序(big-endian)排位的十六进制数来表示。
1 | value = (b3<<24) + (b2<<16) + (b1<<8) + b0; |
介于-16和47之间的整型可以用单个字节来表示,用十六进制来表示范围为x80到xbf.
1 | value = code – 0x90 # 这里是0x90, 如果code=x80,则value = x80 – x90 = -16.:-) |
介于-2048和2047之间的整型可以用两个字节来表示,头字节的范围从xc0到xcf.
1 | value = ((code – 0xc8)<<8) + b0; |
介于- 262144和262143之间的整型可以用三个字节来表示,头字节的范围从xd0到xd7.
1 | x90 # 0 |
1 | list ::= V type? length? value* z |
一个有序列表.每个list都包含一个type字符串,长度length和一个值列表,以十六进制x7a(‘z’)作为结尾。Type可以是任意的UTF-8编码的字符串。Length指定了list值列表的长度。
list的每个值都被添加到一个引用列表中,这样,所有list中的相同条目都共享同一份引用以节省空间。
Any parser expecting a list must also accept a null or a shared ref.
Type的有效取值在文档中并没有详细指定,这依赖于特定的应用. 比如, 在一个由静态类型语言实现的server所暴露的Hessian接口可以使用类型信息来实例化特定的数组类型,反之,在一个由动态类型语言(e.g.: python)实现的server中,将会忽略类型信息。
Hessian2.0 制定了一个格式紧凑的list,其中list元素类型type和元素个数length都用整型来编码,其中类型type是对先前定义的原始数据类型的引用。
1 | 强类型int数组的序列化: int[] = {0, 1} |
1 | long ::= L b7 b6 b5 b4 b3 b2 b1 b0 |
一个64-bit的有符号整数. 起头由十六进制x4c(‘L’)标识, 后面为8字节的大端(big-endian)序的整数。
介于-8和15之间的long由单个字节表示,范围为xd8到xef.
1 | value = (code – 0xe0) |
介于-2048和2047之间的long由两个字节标识, 起头字节的取值范围为xf0到xff.
1 | value = ((code – 0xf8)<<8) + b0 |
介于-262144和262143之间的long由3个字节编码,起头字节的取值范围为x38到x3f.
1 | value = ((code – 0x3c)<<16) + (b1<<8) + b0 |
可以用32-bit的整数来标识的long在这里需要用5个字节作编码,起头字节由x77标识.
1 | value = (b3<<24) + (b2<<16) + (b1<<8) + b0 |
1 | xe0 # 0 |
map ::= M type? (value value)* z
用来表示序列化map和对象. Type字段用来表示map类型,type可能为空(例如在length为0的情况下)。如果类型未指定,则由解析器来负责选择类型。对于对象而言,不识别的key将会被忽略. 所有map元素也被存入一个引用列表. 在解析map时,可以同时支持空类型和引用类型。 类型由服务具体来进行选择。
1 | Map map = new HashMap(); |
1 | M |
由java对象表示的Map对象:
1 | Public class Car implements Serializable{ |
1 | M |
1 | null ::= N |
null表示一个空指针。字符’N’用来标识null值。
1 | object ::= ‘o’ int value* |
Hessian 2.0制定了一个紧凑的对象格式,其中字段名只需要序列化一次。对于对象而言,仅需要按次序地序列化这些字段的取值.
类定义包括类型字符串,字段个数,所有的字段名. 类定义被存放在一个对象定义表中,每个类定义由一个唯一整数作为key标识,之后被对象实例通过key来引用。
对象实例基于先前定义来创建一个新的对象实例. 类定义通过一个整型key在类定义表中进行查询。
对象序列化
1 | Class Car{ |
1 | O # 类型定义 (假定在类型表中对应key为 0) |
1 | enum Color{ |
1 | O # 类型定义 (假定在类型表中对应key为 0) |
1 | ref ::= R b3 b2 b1 b0 |
以一个整数作为key,引用之前所定义的list, map或者对象实例. 对于从输入流中读取出来的每个list, map或对象, 都在流中按位置次序作为标识, i.e. 第一个list或者map为’0’, 下一个为’1’等等. 之后的引用能够使用先前的对象. Writers MAY生成引用. Parsers MUST 识别引用.
ref can refer to incompletely-read items. For example, a circular linked-list will refer to the first link before the entire list has been read.
A possible implementation would add each map, list, and object to an array as it is read. The ref will return the corresponding value from the array. To support circular structures, the implementation would store the map, list or object immediately, before filling in the contents.
Each map or list is stored into an array as it is parsed. ref selects one of the stored objects. The first object is numbered ‘0’.
介于0和255之间的引用编号可以用两个字节来编码
1 | value = b0 |
介于0和255之间的引用编号可以用三个字节来编码
1 | value = (b1<<8) + b0 |
循环链表
1 | list = new LinkedList(); |
1 | O |
1 | string ::= s b1 b0 <utf8-data> string |
16-bit字符,UTF-8编码的字符串。字符串分块(chunk)编码. x53(‘S’)起头来标识最后一个chunk, x73(‘s’)起头标识非最终chunk. 每个chunk有一个16-bit的长度值字段。 length用来标识字符串的长度,而非字节数量. String chunks may not split surrogate pairs.
长度小于32的字符串可以用单字节长的length来标识,范围为[x00-x1f].
1 | value = code |
1 | x00 # "", 空字符串 |
1 | type ::= ‘t’ b1 b0 <type-string> |
在面向对象语言中,一个map或list可能包含一个type属性以标识map或list的类型名称。 每个type都被加入到类型表(type map)中,以供将来被引用。
Repeated type strings MAY use the type map (type reference) to refer to a previously used type. The type reference is zero-based over all the types encountered during parsing.
Hessian 2.0包含3个内建的引用表:
值引用表使得Hessian支持arbitrary graphs,还有递归和循环数据结构。
class和type表通过消除重复字符串而提高了Hessian的效率。
Hessiansupports arbitrary graphs by adding list (list), object (object), and map (map) as it encounters them in the bytecode stream。
解析器必须在引用表中存储每个list, 对象和map。
被存储的对象能够被引用。
每个类型定义被自动添加到类型表(class-map)中。解析器必须在初次使用一个类型时把它添加到类型表中,之后对象实例将通过引用来追踪自身的类型。
map和list的值类型字符串被存储在type map中。解析器必须在初次碰到一个type字符串时把它加入到type map中。
Hessian被制定成一个字节码协议。Hessian解析器本质上是一段针对头字节的选择分支语句.
字节编码
| 值范围 | 说明 |
|---|---|
| x00 - x1f | utf-8字符串,长度范围 0-32 |
| x20 - x2f | 二进制数据,长度范围 0-16 |
| x30 - x37 | 保留 |
| x38 - x3f | 长整型long 范围从-x40000 到 x3ffff |
| x40 - x41 | 保留 |
| x42 | 8-bit 二进制数据,表示末尾chunk(‘B’) |
| x43 | 保留(‘C’ streaming call) |
| x44 | 64-bit IEEE 规范编码的双精度浮点double (‘D’) |
| x45 | 保留(‘E’ envelope) |
| x46 | boolean false (‘F’) |
| x47 | 保留 |
| x48 | 保留 (‘H’ header) |
| x49 | 32-bit有符号整型signed integer (‘I’) |
| x4a | 引用(ref),范围为1-256th |
| x4b | 引用(ref),范围为1-65536th |
| x4c | 64-bit有符号长整型long integer (‘L’) |
| x4d | 具有可选类型的map (‘M’) |
| x4e | null (‘N’) |
| x4f | 类型定义(‘O’) |
| x50 | 保留(‘P’ streaming message/post) |
| x51 | 保留 |
| x52 | 引用(ref),取值范围对应于整型int (‘R’) |
| x53 | utf-8字符串,末尾chunk (‘S’) |
| x54 | boolean true (‘T’) |
| x55 | 保留 |
| x56 | list/vector (‘V’) |
| x57 - x62 | 保留 |
| x62 | 8-bit二进制数据,非末尾chunk (‘b’) |
| x63 | 保留 (‘c’ call for RPC) |
| x64 | UTC time encoded as 64-bit long milliseconds since epoch (‘d’) |
| x65 | 保留 |
| x66 | 保留(‘f’ for fault for RPC) |
| x67 | double 0.0 |
| x68 | double 1.0 |
| x69 | double represented as byte (-128.0 to 127.0) |
| x6a | double represented as short (-32768.0 to 327676.0) |
| x6b | double represented as float |
| x6c | list/vector length (‘l’) |
| x6d | 保留 (‘m’ method for RPC call) |
| x6e | list/vector compact length |
| x6f | 对象实例(‘o’) |
| x70 | 保留 (‘p’ - message/post) |
| x71 | 保留 |
| x72 | 保留(‘r’ reply for message/RPC) |
| x73 | utf-8字符串,非末尾chunk (‘s’) |
| x74 | map/list type (‘t’) |
| x75 | type-ref |
| x76 | 压缩格式的vector (‘v’) |
| x77 | 以32-bit整型编码的long |
| x78 - x79 | 保留 |
| x7a | list/map 终止符(‘z’) |
| x7b - x7f | 保留 |
| x80 - xbf | 单字节压缩格式的整型int(-x10 to x3f, x90 is 0) |
| xc0 - xcf | 双字节压缩格式的整型int(-x800 to x3ff) |
| xd0 - xd7 | 三字节压缩格式的整型int(-x40000 to x3ffff) |
| xd8 - xef | 单字节压缩格式的长整型long(-x8 to x10, xe0 is 0) |
| xf0 - xff | 双字节压缩格式的长整型long (-x800 to x3ff, xf8 is 0) |