External Term Format

    Если какой-либо программе необходимо передать данные эрланговсому серверу, эти данные предварительно должны быть сериализованы. То есть преобразованы в бинарный вид, так, чтобы эрланг мог потом распаковать их обратно. Обычно это делают с помощью ASN.1, google protobuf, thrift и т.д. Все это без сомнения достойные продукты.

    В качестве альтернативного варианта можно рассмотреть использование эрланговского external term format. В исполняемой системе эрланга есть две функции - term_to_binary() и binary_to_term(), которые эффективно и быстро могут запаковать/распаковать любые значения в этот формат, а сам формат хорошо описан в документации — www.erlang.org/doc/apps/erts/erl_ext_dist.html

    Как это все работает.


    Ext. format по своей структуре очень прост. Обычно данные в нем имеют вид «тэг, данные» или «тэг, длина, данные». Тэг описывает какого типа данные запакованы.

    Для основных типов данных тэги такие

    • Tuple — 104, кол-во элементов(1 байт), все элементы
    • List — 108, кол-во элементов(4 байта), все элементы, 106
    • Атом — 115, длина(1 байт), текст атома
    • Целое — 98, значение (4 байта), или для короткого целого (меньше 255) — 97, значение(1 байт)
    • Строка — 107, длина(2 байта), текст. В принципе строка может быть закодирована как список целых, но если надо передать короткую строку с 8битовым текстом, этот тэг — то что доктор прописал


    Перед всеми запакованными данными должен стоять тэг 131. Это номер версии текущего ext. term format.

    То есть эрланговское значение [{banknote, 100, rub}] упакуется в стркутуру:



    В качестве proof of concept напишем, например, на питоне простую процедуру, которая запаковывет структуры питона в ext term format, и распакуем результат на эрланге…

    Будем упаковывать питоновский список в эрланговский список, питоновский tuple в эрланговский tuple, целые числа, питоновские строки. Для атомов… ну… например создадим наследника от строки в питоне, чтобы их легко можно было отличить в процедуре упаковки от обычных строк.

    Получается что-то типа:

    from types import IntType, StringType, TupleType, ListType
    from struct import pack
    import socket
    
    class atom(str): pass
    
    def _eterm(x,accum):
        if type(x) is IntType:
            accum.append(pack('>Bi',98,x))
            return
        if type(x) is StringType:
            accum.append(pack(">BH",107,len(x)))
            accum.append(x)
            return
        if type(x) is TupleType:
            accum.append(pack("BB",104,len(x)))
            for term in x: _eterm(term,accum)
            return
        if type(x) is ListType:
            accum.append(pack(">BI",108,len(x)))
            for term in x:_eterm(term,accum)
            accum.append(chr(106))
            return
        if isinstance(x,atom):
            accum.append(pack("BB",115,len(x)))
            accum.append(x)
            return
        
        raise AssertionError("Cannot convert that type to erlang term %s"%(x))
    
    def binary(X):
        accum = [chr(131)]
        _eterm(X,accum)
        return "".join(accum)    
    


    Отлично. Теперь упакуем какую нибудь сложную структуру и передадим ее в эрланг самым простым способом.

    pterm = (atom("vcard"),[(atom("firstname"),"Odobenus"),
                              (atom("lastname"),"Rosmarus"),
                              (atom("age"),48),
                              (atom("children"),[
                                                 ("Dimon",1988),
                                                 ("Natashka",1990),
                                                 ("Katka",2000),
                                                 ("Anka",2003)] )
    
    erlterm = binary(pterm)
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.sendto(erlterm,("localhost",10000))
    


    То есть упаковали и послали udp пакет. В эрланге принимаем и расшифровываем:

    1> gen_udp:open(10000,[binary]).
    {ok,#Port<0.585>}
    2> R=receive {udp,_ ,_,_,Bin} -> Bin end.
    <<131,104,2,115,5,118,99,97,114,100,108,0,0,0,4,104,2,115,
      9,102,105,114,115,116,110,97,109,101,107,...>>
    3> binary_to_term( R ).
    {vcard,[{firstname,"Odobenus"},
            {lastname,"Rosmarus"},
            {age,48},
            {children,[{"Dimon",1988},
                       {"Natashka",1990},
                       {"Katka",2000},
                       {"Anka",2003}]}]}
    


    Выводы


    Мы передали (малой кровью) сложную структуру из питона в эрланг. Достоинства такого подходы —
    • Гибкость. Можно запаковать все что угодно.
    • Производительность. На эрланге binary_to_term() и term_to_binary() реализованы как BIF, на C, и хорошо оптимизированны по скорости и памяти…


    Недостаток ext. term formаt-а: Всякая гибкость имеет обратную сторону. В случае кривых рук или бестолкового программирования можно напаковать таких структур, с которыми эрланговский сервер просто не будет знать что делать.

    За пределами статьи остались форматы double чисел, безразмерных целых, передача сжатых данных, упаковка binary в этот формат и т.д. и т.п. Но все это хорошо описано в документации.

    Главное понимать идею.
    • +31
    • 1,9k
    • 4
    Поделиться публикацией

    Комментарии 4

      +1
      Уже есть реализаций полно: bert-rpc.org/
        +4
        есть реализации — хорошо, а знать как устроено — лучше.

        Позволяет формировать термы откуда угодно, в том числе и в языках, для которых нет ничего в берт. В ocaml например. В Паскале. В шелл-скрипте. В тикле. Да мало ли где.

        Кроме того во многих случаях такие посторонние пакеты просто избыточны. Ставить что-то левое там, где можно обойтись процедурой в полстраницы — неразумно.

          +1
          Ну для OCaml давно уже есть библиотека.

          Ничего страшного в «левом» не вижу. 5-10 контрибьюторов быстрее (ну или с той же скоростью) найдут баг и исправят его в пакете, от которого они зависят, по моему скромному мнению.
            +1
            Ну для OCaml давно уже есть библиотека работы с ETF: github.com/echoteam/ocaml-erlang-port

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое