初探Erlang的term_to_binary数据封包机制

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的官方文档链接。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值