初探Erlang的term_to_binary数据封包机制

来源:互联网 发布:绘地图软件 编辑:程序博客网 时间:2024/05/17 03:50

1、引言


读本文之前,建议先读本博客《Erlang数据类型的内部实现》一文。

erlang:term_to_binary/1,2函数返回值是Erlang扩展term格式(Erlang external term format)的binary,即ext_binary(),
这个函数能把Erlang数据封装成二进制流,是一种存储和传输Erlang数据的有效途径,
甚至可以用这种封包/解包方式用作Socket的通信协议(某页游项目就是这么干的)。

读懂Erlang扩展term格式的二进制数据,你就可以用任意语言解包Erlang数据。

直接分析二进制流,语言之间沟通无限。

2、初识Erlang的ext_binary


2.1、从简单实例说起...


Eshell V5.10.1  (abort with ^G)1> term_to_binary({}).<<131,104,0>>2> term_to_binary({1}).<<131,104,1,97,1>>3> term_to_binary({1,2}).<<131,104,2,97,1,97,2>>4> term_to_binary([]).<<131,106>>5> term_to_binary([1]).<<131,107,0,1,1>>6> term_to_binary([1,2]).<<131,107,0,2,1,2>>

2.2、从简单实例思考...


    (1) 为什么第1字节都是131?    (2) 为什么tuple的第2字节都是104?    (3) 为什么空list第2字节是106?    (4) 为什么非空list第2字节都是107?    (5) ...

2.3、在C源码中找答案...


源码太多,我就不贴了,主要是这两个文件:
$ERL_TOP/erts/emulator/beam/external.h
$ERL_TOP/erts/emulator/beam/external.c

从函数erts_term_to_binary(Process* p, Eterm Term, int level, Uint flags)中可以看到,
首先是在第一字节压入一个版本号:
#define VERSION_MAGIC 131   /* 130 in erlang 4.2 */              /* Increment this when changing the external format. */              /* ON the other hand, don't change the external format */              /* since that breaks other people's code! *//* * Get a pointer to the binary bytes, for a heap or refc binary * (NOT sub binary). */#define binary_bytes(Bin)\    (*binary_val(Bin) == HEADER_PROC_BIN ?\     ((ProcBin *) binary_val(Bin))->bytes :\     (ASSERT_EXPR(thing_subtag(*binary_val(Bin)) == HEAP_BINARY_SUBTAG),\      (byte *)(&(((ErlHeapBin *) binary_val(Bin))->data))))bin = new_binary(p, (byte *)NULL, size);bytes = binary_bytes(bin);bytes[0] = VERSION_MAGIC;
然后进入函数enc_term,来看一下enc_term中的重要片断: 
switch(tag_val_def(obj)) {    case NIL_DEF:        // 如果是空列表,则压入NIL_EXT标识        *ep++ = NIL_EXT;        break;    // ......case LIST_DEF:    {int is_str;i = is_external_string(obj, &is_str);if (is_str) {            // 如果是字符list,则压入STRING_EXT标识    *ep++ = STRING_EXT;    put_int16(i, ep);    ep += 2;    while (is_list(obj)) {Eterm* cons = list_val(obj);*ep++ = unsigned_val(CAR(cons));obj = CDR(cons);    }} else {            // 如果是list,则压入LIST_EXT标识    *ep++ = LIST_EXT;    put_int32(i, ep);    ep += 4;    goto encode_one_cons;}    }    break;case TUPLE_DEF:    ptr = tuple_val(obj);    i = arityval(*ptr);    ptr++;    if (i <= 0xff) {        // 如果是小tuple,则压入SMALL_TUPLE_EXT标识*ep++ = SMALL_TUPLE_EXT;put_int8(i, ep);ep += 1;    } else  {*ep++ = LARGE_TUPLE_EXT;put_int32(i, ep);ep += 4;    }    if (i > 0) {WSTACK_PUSH(s, ENC_LAST_ARRAY_ELEMENT+i-1);WSTACK_PUSH(s, (UWord) ptr);    }    break;    // ......}
从这里可以了解到,ext_binary数据封包其实就是不同的数据类型前面给予不同的标识,

这些标识究竟是怎么定义的?请结合Erlang内部数据结构,往下看...


2.4、从简单实例答疑...


要读懂ext_binary数据流,下面这些定义是关键:
/* Same order as the ordering of terms in erlang *//* Since there are 255 different External tag values to choose from   There is no reason to not be extravagant.   Hence, the different tags for large/small tuple e.t.c*/#ifdef ERTS_WANT_EXTERNAL_TAGS#ifndef ERTS_EXTERNAL_TAGS#define ERTS_EXTERNAL_TAGS#define SMALL_INTEGER_EXT 'a'#define INTEGER_EXT       'b'#define FLOAT_EXT         'c'#define ATOM_EXT          'd'#define SMALL_ATOM_EXT    's'#define REFERENCE_EXT     'e'#define NEW_REFERENCE_EXT 'r'#define PORT_EXT          'f'#define NEW_FLOAT_EXT     'F'#define PID_EXT           'g'#define SMALL_TUPLE_EXT   'h'#define LARGE_TUPLE_EXT   'i'#define NIL_EXT           'j'#define STRING_EXT        'k'#define LIST_EXT          'l'#define BINARY_EXT        'm'#define BIT_BINARY_EXT    'M'#define SMALL_BIG_EXT     'n'#define LARGE_BIG_EXT     'o'#define NEW_FUN_EXT       'p'#define EXPORT_EXT        'q'#define FUN_EXT           'u'#define ATOM_UTF8_EXT     'v'#define SMALL_ATOM_UTF8_EXT 'w'#define DIST_HEADER       'D'#define ATOM_CACHE_REF    'R'#define ATOM_INTERNAL_REF2 'I'#define ATOM_INTERNAL_REF3 'K'#define BINARY_INTERNAL_REF 'J'#define BIT_BINARY_INTERNAL_REF 'L'#define COMPRESSED        'P'
我们先把问题中的几个ASCII码转换成对应字符:
Eshell V5.10.1  (abort with ^G)1> [104,106,107]."hjk"
根据这些字符,可以从在上面找到相应的宏,现在可解答上面的问题了:
    (1) 为什么第1字节都是131?       答:它是一个版本号。    (2) 为什么tuple的第2字节都是104?       答:它是一个SMALL_TUPLE_EXT标识。    (3) 为什么空list第2字节是106?       答:它是一个NIL_EXT标识,表示一个空list。    (4) 为什么非空list第2字节都是107?       答:它是一个STRING_EXT标识,表示一个字符串,在Erlang中字符串其实就是list,但list不一定是字符串。    (5) ...

3、小结


研究Erlang的ext_binary,可以从term_to_binary和binary_to_term这两个函数的内部实现入手,一层层深入,本文只是抛砖引玉,有兴趣的同学可以继续深入研究,有成果别忘了通知一声,分享分享,共同进步!

关于External Term Format,更多内容可参阅官方文档:http://www.erlang.org/doc/apps/erts/erl_ext_dist.html

PS:感谢@蓝色心情 告知关于External Term Format的官方文档链接。

原创粉丝点击