Top.Mail.Ru
Бинарный протокол | Tarantool
 
Справочники / Детали реализации / Бинарный протокол
Справочники / Детали реализации / Бинарный протокол

Бинарный протокол

Бинарный протокол

Бинарный протокол передает данные по схеме «запрос-ответ», то есть он предназначен для отправки запросов на сервер Tarantool и получения ответов. Протокол предоставляет полный доступ к функциям Tarantool, включая:

  • мультиплексирование запросов, т.е. возможность асинхронной отправки множества запросов по одному соединению;
  • формат ответа, который поддерживает запись в режиме без копирования (zero-copy).

Протокол можно назвать «бинарным», поскольку наиболее частые обращения к базе данных выполняются с помощью бинарного кода, а не текста запроса на языке Lua. Специалисты по Tarantool используют его, чтобы писать собственные коннекторы, понимать сетевые сообщения, поддерживать новые функции, которые их любимый коннектор еще не поддерживает, или чтобы избежать повторного разбора на сервере.

Раздел Описание
Обозначения и термины Условные обозначения в бинарном протоколе
Заголовок и тело сообщения Заголовок запроса
Запросы:
IPROTO_SELECT
IPROTO_INSERT
IPROTO_REPLACE
IPROTO_UPDATE
IPROTO_DELETE
IPROTO_CALL_16
IPROTO_AUTH
IPROTO_EVAL
IPROTO_UPSERT
IPROTO_CALL
IPROTO_EXECUTE
IPROTO_NOP
IPROTO_PREPARE
IPROTO_PING
IPROTO_JOIN
IPROTO_SUBSCRIBE
IPROTO_VOTE_DEPRECATED
IPROTO_VOTE
IPROTO_FETCH_SNAPSHOT
IPROTO_REGISTER
Тело запроса
Ответы на ошибки и запросы без SQL Ответы на запросы без SQL
Ответы на ошибки Ответы на ошибки
Ответы на запросы с SQL Ответы на запросы с SQL
Аутентификация Аутентификация после установления соединения
Репликация Запрос репликации
Примеры Примеры использования
XLOG/SNAP Формат файлов .xlog и .snap

Если слово начинается с MP_, это указывает на тип MessagePack или ряд типов MessagePack, включая сигнал и, возможно, включая значение, с небольшими изменениями:

  • MP_NIL nil
  • MP_UINT unsigned integer
  • MP_INT integer или unsigned integer
  • MP_STR string
  • MP_BIN binary string
  • MP_ARRAY array
  • MP_MAP map
  • MP_BOOL boolean
  • MP_FLOAT float
  • MP_DOUBLE double
  • MP_EXT extension (включая тип DECIMAL и тип UUID)
  • MP_OBJECT любой объект формата MessagePack

Краткое описание приводится на странице «спецификации» MessagePack.

Если же слово начинается с IPROTO_, это указывает на константу Tarantool, которая либо определена, либо упоминается в файле iproto_constants.h.

В этом разделе мы будем упоминать следующие константы IPROTO, которые идентифицируют запросы:

IPROTO_SELECT=0x01
IPROTO_INSERT=0x02
IPROTO_REPLACE=0x03
IPROTO_UPDATE=0x04
IPROTO_DELETE=0x05
IPROTO_CALL_16=0x06
IPROTO_AUTH=0x07
IPROTO_EVAL=0x08
IPROTO_UPSERT=0x09
IPROTO_CALL=0x0a
IPROTO_EXECUTE=0x0b
IPROTO_NOP=0x0c
IPROTO_PREPARE=0x0d
IPROTO_CONFIRM=0x28
IPROTO_ROLLBACK=0x29
IPROTO_PING=0x40
IPROTO_JOIN=0x41
IPROTO_SUBSCRIBE=0x42
IPROTO_VOTE_DEPRECATED=0x43
IPROTO_VOTE=0x44
IPROTO_FETCH_SNAPSHOT=0x45
IPROTO_REGISTER=0x46

В этом разделе мы опишем такие константы IPROTO, которые встречаются в запросах или ответах:

IPROTO_OK=0x00
IPROTO_REQUEST_TYPE=0x00
IPROTO_SYNC=0x01
IPROTO_REPLICA_ID=0x02
IPROTO_LSN=0x03
IPROTO_TIMESTAMP=0x04
IPROTO_SCHEMA_VERSION=0x05
IPROTO_FLAGS=0x09
IPROTO_SPACE_ID=0x10
IPROTO_INDEX_ID=0x11
IPROTO_LIMIT=0x12
IPROTO_OFFSET=0x13
IPROTO_ITERATOR=0x14
IPROTO_INDEX_BASE=0x15
IPROTO_KEY=0x20
IPROTO_TUPLE=0x21
IPROTO_FUNCTION_NAME=0x22
IPROTO_USER_NAME=0x23
IPROTO_INSTANCE_UUID=0x24
IPROTO_CLUSTER_UUID=0x25
IPROTO_VCLOCK=0x26
IPROTO_EXPR=0x27
IPROTO_OPS=0x28
IPROTO_BALLOT=0x29
IPROTO_BALLOT_IS_RO=0x01
IPROTO_BALLOT_VCLOCK=0x02
IPROTO_BALLOT_GC_VCLOCK=0x03
IPROTO_BALLOT_IS_LOADING=0x04
IPROTO_BALLOT_IS_ANON=0x05
IPROTO_TUPLE_META=0x2a
IPROTO_OPTIONS=0x2b
IPROTO_DATA=0x30
IPROTO_ERROR_24=0x31
IPROTO_METADATA=0x32
IPROTO_BIND_METADATA=0x33
IPROTO_BIND_COUNT=0x34
IPROTO_SQL_TEXT=0x40
IPROTO_SQL_BIND=0x41
IPROTO_SQL_INF O=0x42
IPROTO_STMT_ID=0x43
IPROTO_ERROR=0x52
IPROTO_FIELD_NAME=0x00
IPROTO_FIELD_TYPE=0x01
IPROTO_FIELD_COLL=0x02
IPROTO_FIELD_IS_NULLABLE=0x03
IPROTO_FIELD_IS_AUTOINCREMENT=0x04
IPROTO_FIELD_SPAN=0x05

Для обозначения описаний сообщений мы будем вызывать msgpack(...), а внутри него будем использовать модифицированный ``YAML <https://en.wikipedia.org/wiki/YAML>`_ таким образом:

{...} в фигурные скобки заключают ассоциативный массив, в MsgPack это MP_MAP,
k: v — это пара «ключ-значение», также называемая элементом ассоциативного массива, в этом разделе k всегда является беззнаковым целым = одной из констант IPROTO,
italics используются для обозначения заменяемого текста в этом руководстве. Обычно это тип данных, но мы не показываем типы констант IPROTO, которые всегда будут беззнаковыми 8-битными целыми числами,
[...] — для неассоциативных массивов,
# начинает комментарий, особенно в начале раздела,
все остальное приводится «как есть».
Элементы ассоциативного массива могут появляться в любом порядке, но в примерах мы обычно используем тот порядок, который используется в net_box.c.

Кроме как во время соединения (что включает в себя приветствие сервера и необязательную аутентификацию, которую мы обсудим далее в этом разделе), протокол идет по схеме «запрос-ответ» (клиент запрашивает, сервер отвечает). Пакет может содержать более одного запроса.

Почти все запросы и ответы состоят из трех частей: размер, заголовок и тело. Размер — это unsigned integer (MP_UINT), обычно 32-битное беззнаковое целое число. Заголовок и тело — это ассоциативные массивы (MP_MAP).

# <size>
{MP_UINT unsigned integer}
# <header>
{MP_MAP with <header> map-items}
# <body>
{MP_MAP with <body> map-items}

<size> — это размер заголовка плюс размер тела сообщения. Есть смысл сравнивать его с количеством байтов, оставшихся в пакете.

Заголовок <header> может содержать такие данные в любом порядке:

msgpack({
    IPROTO_REQUEST_TYPE: {MP_UINT unsigned integer},
    IPROTO_SYNC: {MP_UINT unsigned integer},
    IPROTO_SCHEMA_VERSION: {MP_UINT unsigned integer}
})

IPROTO_REQUEST_TYPE или индикатор кода ответа (Response-Code-Indicator) = 0x00. Это беззнаковое число, которое указывает на то, что будет находиться в теле сообщения <body>. В запросах за IPROTO_REQUEST_TYPE будет следовать IPROTO_SELECT и т. д. В ответах за Response-Code-Indicator будет следовать IPROTO_OK и т. д.

IPROTO_SYNC = 0x01. Это беззнаковое целое число должно увеличиваться так, чтобы оно было уникальным в каждом запросе. Это целое число также возвращается в результате box.session.sync(). Значение IPROTO_SYNC в ответе должно быть таким же, как значение IPROTO_SYNC в запросе.

IPROTO_SCHEMA_VERSION = 0x05. Беззнаковое число, иногда называемое SCHEMA_ID, которое увеличивается при значительных изменениях. В заголовке запроса IPROTO_SCHEMA_VERSION указываеть необязательно, поэтому версия не будет проверяться, если она отсутствует. В заголовке ответа IPROTO_SCHEMA_VERSION присутствует всегда, и клиент должен сам проверить, не изменилась ли версия.

Чтобы понять, как Tarantool кодирует заголовок, обратите внимание на функцию xrow_header_encode в файле xrow.c. Чтобы увидеть, как Tarantool декодирует заголовок, посмотрите функцию netbox_decode_data в файле net_box.c. Например, при успешном ответе на box.space:select() значение индикатора кода ответа будет 0 = IPROTO_OK, а массив будет содержать все кортежи из результата.

Тело сообщения <body> содержит детали запроса или ответа. В запросе оно может отсутствовать или представлять собой пустой ассоциативный массив. И то, и другое будет интерпретироваться одинаково. Ответы будут содержать <body> в любом случае, даже после запроса IPROTO_PING.

Запрос содержит размер, заголовок, который содержит ключ IPROTO, и тело сообщения, как описано в этом разделе.

IPROTO_SELECT = 0x01.

См. space_object:select(). Тело сообщения представляет собой ассоциативный массив из 6 элементов.

# <size>
msgpack({MP_UINT unsigned integer = size(<header>) + size(<body>)})
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_SELECT,
    IPROTO_SYNC: {MP_UINT unsigned integer}
})
# <body>
msgpack({
    IPROTO_SPACE_ID: {MP_UINT unsigned integer},
    IPROTO_INDEX_ID: {MP_UINT unsigned integer},
    IPROTO_LIMIT: {MP_UINT unsigned integer},
    IPROTO_OFFSET: {MP_UINT unsigned integer},
    IPROTO_ITERATOR: {MP_UINT unsigned integer},
    IPROTO_KEY: {MP_ARRAY array of key values}
})

Пример: если ID спейса „tspace“ = 512 и это пятое сообщение,
conn.space.tspace:select({0},{iterator='GT',offset=1,limit=2}) вызовет:

<size>
msgpack(21)
# <header>
msgpack({
    IPROTO_SYNC: 5,
    IPROTO_REQUEST_TYPE: IPROTO_SELECT
})
# <body>
msgpack({
    IPROTO_SPACE_ID: 512,
    IPROTO_INDEX_ID: 0,
    IPROTO_ITERATOR: 6,
    IPROTO_OFFSET: 1,
    IPROTO_LIMIT: 2,
    IPROTO_KEY: [1]
})

Далее, в разделе Бинарный протокол — примеры, мы покажем фактические байт-коды сообщения IPROTO_SELECT.

IPROTO_INSERT = 0x02.

См. space_object:insert(). Тело сообщения представляет собой ассоциативный массив из 2 элементов:

# <size>
msgpack({MP_UINT unsigned integer = size(<header>) + size(<body>)})
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_INSERT,
    IPROTO_SYNC: {MP_UINT unsigned integer}
})
# <body>
msgpack({
    IPROTO_SPACE_ID: {MP_UINT unsigned integer},
    IPROTO_TUPLE: {MP_ARRAY array of field values}
})

Пример: если ID спейса „tspace“ = 512 и это пятое сообщение,
conn.space.tspace:insert{1, 'AAA'} вызовет:

# <size>
msgpack(17)
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_INSERT,
    IPROTO_SYNC: 5
})
# <body>
msgpack({
    IPROTO_SPACE_ID: 512,
    IPROTO_TUPLE: [1, 'AAA']
})

IPROTO_REPLACE = 0x03, см. space_object:replace(). Тело сообщения представляет собой ассоциативный массив из 2 элементов, как для IPROTO_INSERT:

# <size>
msgpack({MP_UINT unsigned integer = size(<header>) + size(<body>)})
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_REPLACE,
    IPROTO_SYNC: {MP_UINT unsigned integer}
})
# <body>
msgpack({
    IPROTO_SPACE_ID: {MP_UINT unsigned integer},
    IPROTO_TUPLE: {MP_ARRAY array of field values}
})

IPROTO_UPDATE = 0x04.

См. space_object:update().

Тело сообщения обычно представляет собой ассоциативный массив из 4 элементов:

# <size>
msgpack({MP_UINT unsigned integer = size(<header>) + size(<body>)})
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_UPDATE,
    IPROTO_SYNC: {MP_UINT unsigned integer}
})
# <body>
msgpack({
    IPROTO_SPACE_ID: {MP_UINT unsigned integer},
    IPROTO_INDEX_ID: {MP_UINT unsigned integer},
    IPROTO_KEY: {MP_ARRAY array of index keys},
    IPROTO_TUPLE: {MP_ARRAY array of update operations}
})

Если в операции не указаны значения, то IPROTO_TUPLE — это массив из 2 элементов:
[MP_STR OPERATOR = „#“, {MP_INT FIELD_NO = номер поля, начиная с 1]. Обычно номера полей начинаются с 1.

Если в операции задано одно значение, то IPROTO_TUPLE — это массив из трех элементов:
[MP_STR string OPERATOR = „+“ or „-„ or „^“ or „^“ or „|“ or „!“ or „=“, MP_INT FIELD_NO, MP_OBJECT VALUE].

В остальных случаях IPROTO_TUPLE — это массив из 5 элементов:
[MP_STR string OPERATOR = „:“, MP_INT integer FIELD_NO, MP_INT POSITION, MP_INT OFFSET, MP_STR VALUE].

Пример: если ID спейса „tspace“ = 512 и это пятое сообщение, br| conn.space.tspace:update(999, {{'=', 2, 'B'}}) вызовет:

# <size>
msgpack(17)
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_UPDATE,
    IPROTO_SYNC: 5
})
# <body> ... the map-item IPROTO_INDEX_BASE is optional
msgpack({
    IPROTO_SPACE_ID: 512,
    IPROTO_INDEX_ID: 0,
    IPROTO_INDEX_BASE: 1,
    IPROTO_TUPLE: [['=',2,'B']],
    IPROTO_KEY: [999]
})

Далее, в разделе Бинарный протокол — примеры, мы покажем фактические байт-коды сообщения IPROTO_UPDATE.

IPROTO_DELETE = 0x05.

См. space_object:delete(). Тело сообщения представляет собой ассоциативный массив из 3 элементов:

# <size>
msgpack({MP_UINT unsigned integer = size(<header>) + size(<body>)})
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_DELETE,
    IPROTO_SYNC: {MP_UINT unsigned integer}
})
# <body>
msgpack({
    IPROTO_SPACE_ID: {MP_UINT unsigned integer},
    IPROTO_INDEX_ID: {MP_UINT unsigned integer},
    IPROTO_KEY: {MP_ARRAY array of key values}
})

IPROTO_CALL_16 = 0x06.

См. conn:call(). _16 в конце подсказывает, что константа используется для call() до версии Tarantool 1.6. Эта константа объявлена устаревшей. Вместо нее используйте IPROTO_CALL. Тело сообщения представляет собой ассоциативный массив из 2 элементов:

# <size>
msgpack({MP_UINT unsigned integer = size(<header>) + size(<body>)})
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_CALL_16,
    IPROTO_SYNC: {MP_UINT unsigned integer}
})
# <body>
msgpack({
    IPROTO_FUNCTION_NAME: {MP_STR string},
    IPROTO_TUPLE: {MP_ARRAY array of arguments}
})

Возвращается массив кортежей.

IPROTO_AUTH = 0x07.

См. раздел Аутентификация. См. раздел Бинарный протокол — аутентификация ниже.

IPROTO_EVAL = 0x08.

См. conn:eval(). В качестве аргумента выступает выражение на Lua — так Tarantool обрабатывает небинарный код с помощью бинарного протокола. Любой запрос без собственного кода, например box.space.space-name:drop(), будет обработан с помощью либо IPROTO_CALL, либо IPROTO_EVAL. Административная утилита tarantoolctl активно использует eval. Тело сообщения представляет собой ассоциативный массив из 2 элементов:

# <size>
msgpack({MP_UINT unsigned integer = size(<header>) + size(<body>)})
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_EVAL,
    IPROTO_SYNC: {MP_UINT unsigned integer}
})
# <body>
msgpack({
    IPROTO_EXPR: {MP_STR string},
    IPROTO_TUPLE: {MP_ARRAY array of arguments}
})

Пример: если это пятое сообщение,
conn.:code:`eval('return 5;') вызовет:

# <size>
msgpack(19)
# <header>
msgpack({
    IPROTO_SYNC: 5
    IPROTO_REQUEST_TYPE: IPROTO_EVAL
})
# <body>
msgpack({
    IPROTO_EXPR: 'return 5;',
    IPROTO_TUPLE: []
})

IPROTO_UPSERT = 0x09.

См. space_object:upsert().

Тело сообщения обычно представляет собой ассоциативный массив из 4 элементов:

# <size>
msgpack({MP_UINT unsigned integer = size(<header>) + size(<body>)})
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_UPSERT,
    IPROTO_SYNC: {MP_UINT unsigned integer}
})
# <body>
msgpack({
    IPROTO_SPACE_ID: {MP_UINT unsigned integer},
    IPROTO_INDEX_BASE: {MP_UINT unsigned integer},
    IPROTO_OPS: {MP_ARRAY array of update operations},
    IPROTO_TUPLE: {MP_ARRAY array of primary-key field values}
})

IPROTO_OPS — это то же самое, что и IPROTO_TUPLE в IPROTO_UPDATE.

IPROTO_CALL = 0x0a.

См. conn:call(). Тело сообщения представляет собой ассоциативный массив из 2 элементов:

# <size>
msgpack({MP_UINT unsigned integer = size(<header>) + size(<body>)})
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_CALL,
    IPROTO_SYNC: {MP_UINT unsigned integer}
})
# <body>
msgpack({
    IPROTO_FUNCTION_NAME: {MP_STR string},
    IPROTO_TUPLE: {MP_ARRAY array of arguments}
})

Вернется список значений, наподобие ответа IPROTO_EVAL.

IPROTO_EXECUTE = 0x0b.

См. box.execute(), используется только для SQL. Тело сообщения представляет собой ассоциативный массив из 3 элементов:

# <size>
msgpack(:samp:`{{MP_UINT unsigned integer = size(<header>) + size(<body>)}}`)
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_EXECUTE,
    IPROTO_SYNC: :samp:`{{MP_UINT unsigned integer}}`
})
# <body>
msgpack({
    IPROTO_STMT_ID: :samp:`{{MP_INT integer}}` или IPROTO_SQL_TEXT: :samp:`{{MP_STR string}}`,
    IPROTO_SQL_BIND: :samp:`{{MP_INT integer}}`,
    IPROTO_OPTIONS: :samp:`{{MP_ARRAY array}}`
})

При выполнении подготовленного оператора используйте IPROTO_STMT_ID (0x43) + ID оператора (MP_INT), при работе со строками SQL — IPROTO_SQL_TEXT (0x40) + текст оператора (MP_STR), а затем IPROTO_SQL_BIND (0x41) + массив значений параметров для подстановочных знаков ? или :name, IPROTO_OPTIONS (0x2b) + массив параметров (обычно пустой).

Например, предположим, что мы создаем подготовленный оператор с двумя подстановочными знаками ? и выполняем его с двумя параметрами таким образом:
n = conn:prepare([[VALUES (?, ?);]])
conn:execute(n.stmt_id, {1,'a'})
Тогда тело сообщения будет выглядеть так:

# <body>
msgpack({
    IPROTO_STMT_ID: 0xd7aa741b,
    IPROTO_SQL_BIND: [1, 'a'],
    IPROTO_OPTIONS: []
})

Далее, в разделе Бинарный протокол — примеры, мы покажем фактические байт-коды сообщения IPROTO_EXECUTE.

Чтобы вызвать подготовленный оператор с именованными параметрами из коннектора, передайте параметры в массиве ассоциативных массивов. Клиент должен поместить каждый элемент в ассоциативный массив, где ключ содержит имя параметра (с двоеточием), а значение — фактическое значение. Так, чтобы связать foo и bar с 42 и 43, клиент должен отправить IPROTO_SQL_TEXT: <...>, IPROTO_SQL_BIND: [{"foo": 42}, {"bar": 43}].

Если оператор содержит как именованные, так и неименованные параметры, поместите в ассоциативный массив только именованные параметры. Остальные параметры являются позиционными и будут подставляться по порядку.

IPROTO_NOP = 0x0c.

Нет такого запроса на Lua, который бы соответствовал IPROTO_NOP. IPROTO_NOP приводит к увеличению LSN. Иногда константу можно использовать для обновления значения, когда старое и новое значения одинаковы, но LSN нужно увеличить, поскольку нужно зарегистрировать изменение данных. Тело сообщения пустое.

IPROTO_PREPARE = 0x0d.

См. box.prepare, используется только для SQL. Тело сообщения представляет собой ассоциативный массив из 1 элемента:

# <size>
msgpack(:samp:`{{MP_UINT unsigned integer = size(<header>) + size(<body>)}}`)
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_PREPARE,
    IPROTO_SYNC: :samp:`{{MP_UINT unsigned integer}}`
})
# <body>
msgpack({
    IPROTO_STMT_ID: :samp:`{{MP_INT integer}}` или IPROTO_SQL_TEXT: :samp:`{{MP_STR string}}`
})

При выполнении подготовленного оператора используйте IPROTO_STMT_ID (0x43) + ID оператора (MP_INT), при работе со строками SQL — IPROTO_SQL_TEXT (0x40) + текст оператора (string). Таким образом, элемент ассоциативного массива IPROTO_PREPARE — это то же самое, что и первый элемент тела сообщения IPROTO_EXECUTE.

IPROTO_PING = 0x40.

См. conn:ping(). В теле сообщения будет пустой ассоциативный массив, потому что IPROTO_PING в заголовке содержит всю информацию, необходимую экземпляру сервера.

# <size>
msgpack(5)
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_PING,
    IPROTO_SYNC: {MP_UINT unsigned integer}
})
IPROTO_JOIN = 0x41 — для репликации
IPROTO_SUBSCRIBE = 0x42 — для репликации SUBSCRIBE
IPROTO_VOTE_DEPRECATED = 0x43 — для устаревшего типа голосования, взамен используется IPROTO_VOTE
IPROTO_VOTE = 0x44 — для выбора мастера
IPROTO_FETCH_SNAPSHOT = 0x45 — для начала анонимной репликации
IPROTO_REGISTER = 0x46 — для выхода из анонимной репликации

Константы Tarantool 0x41-0x46 (в десятичной системе 65-70) предназначены для репликации. Коннекторы и клиенты не должны отправлять репликационные пакеты. См. Бинарный протокол — репликация.

Следующие два сообщения IPROTO используются в соединениях репликации между узлами Tarantool при синхронной репликации. Эти сообщения не должны использоваться клиентскими приложениями при обычном соединении.

IPROTO_CONFIRM = 0x28

Это сообщение подтверждает, что транзакции до LSN = IPROTO_LSN включительно от экземпляра с ID = IPROTO_REPLICA_ID достигли кворума и могут быть зафиксированы.

Тело сообщения представляет собой ассоциативный массив из 2 элементов:

# <size>
msgpack({MP_UINT unsigned integer = size(<header>) + size(<body>)})
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_CONFIRM,
    IPROTO_SYNC: {MP_UINT unsigned integer}
})
# <body>
msgpack({
    IPROTO_REPLICA_ID: {MP_INT integer},
    IPROTO_LSN: {MP_INT integer}
})

IPROTO_ROLLBACK = 0x29

В этом сообщении говорится, что транзакции до LSN = IPROTO_LSN включительно от экземпляра с id = IPROTO_REPLICA_ID, по какой-то причине не смогли достичь кворума — их нужно откатить.

Тело сообщения представляет собой ассоциативный массив из 2 элементов:

# <size>
msgpack({MP_UINT unsigned integer = size(<header>) + size(<body>)})
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_ROLLBACK,
    IPROTO_SYNC: {MP_UINT unsigned integer}
})
# <body>
msgpack({
    IPROTO_REPLICA_ID: {MP_INT integer},
    IPROTO_LSN: {MP_INT integer}
})

В ответе после заголовка идет тело сообщения. Если ошибки не было, оно будет содержать IPROTO_OK (0x00). Если была ошибка, то он будет содержать код ошибки, отличный от IPROTO_OK. Ответы на операторы SQL немного отличаются и будут описаны в последующем разделе Бинарный протокол — ответы на SQL-запросы.

Для IPROTO_OK индикатор кода ответа в заголовке будет 0, а тело сообщения будет представлять собой ассоциативный массив, состоящий из 1 элемента.

# <size>
msgpack({MP_UINT unsigned integer = size(<header>) + size(<body>)})
# <header>
msgpack({
    Response-Code-Indicator: IPROTO_OK,
    IPROTO_SYNC: {MP_UINT unsigned integer, may be 64-bit},
    IPROTO_SCHEMA_VERSION: {MP_UINT unsigned integer}
})
# <body>
msgpack({
    IPROTO_DATA: {any type}
})

В теле сообщения IPROTO_PING будет пустой ассоциативный массив. В теле большинства запросов доступа к данным (IPROTO_SELECT, IPROTO_INSERT, IPROTO_DELETE и т.д.) будет ассоциативный массив IPROTO_DATA с массивом кортежей, содержащих массив полей. Для IPROTO_EVAL и IPROTO_CALL телом обычно будет массив, но поскольку запросы на Lua могут возвращать самые разные структуры, сами тела могут содержать самые разные структуры.

Пример: если это пятое сообщение, запрос такой: box.space.space-name:insert{6}, и предыдущая версия схемы была 100, ответ после успешного выполнения будет выглядеть следующим образом:

# <size>
msgpack(32)
# <header>
msgpack({
    Response-Code-Indicator: IPROTO_OK,
    IPROTO_SYNC: 5,
    IPROTO_SCHEMA_VERSION: 100
})
# <body>
msgpack({
    IPROTO_DATA: [[6]]
})

Далее, в разделе Бинарный протокол — примеры, мы покажем фактические байт-коды ответа на сообщение IPROTO_INSERT.

IPROTO_DATA возвращается в результате использования net_box и модуля buffer. То есть если бы мы использовали net_box, то могли бы интерпретировать результат с помощью msgpack.decode_unchecked() или преобразовать его в строку с помощью ffi.string(pointer,length). Также здесь можно использовать функцию pickle.unpack().

Для ответа, отличного от IPROTO_OK, индикатор кода ответа в заголовке будет 0x8XXX, а тело будет представлять собой ассоциативный массив, состоящий из 1 элемента.

# <size>
msgpack(32)
# <header>
msgpack({
    Response-Code-Indicator: {0x8XXX},
    IPROTO_SYNC: {MP_UINT unsigned integer, may be 64-bit},
    IPROTO_SCHEMA_VERSION: {MP_UINT unsigned integer}
})
# <body>
msgpack({
    IPROTO_ERROR: {MP_STRING string}
})

где 0x8XXX — это индикатор ошибки, а XXX — это значение из файла src/box/errcode.h. В src/box/errcode.h также есть несколько удобных макросов, которые определяют шестнадцатеричные константы для кодов возврата.

Пример: в версии 2.4.0 и раньше, если это пятое сообщение и отправляется запрос на создание дубликата спейса с помощью conn:eval([[box.schema.space.create('_space');]]), то ответ на невыполненный запрос будет выглядеть следующим образом:

# <size>
msgpack(32)
# <header>
msgpack({
    Response-Code-Indicator: 0x800a,
    IPROTO_SYNC: 5,
    IPROTO_SCHEMA_VERSION: 0x78
})
# <body>
msgpack({
    IPROTO_ERROR:  "Space '_space' already exists"
})

Далее, в разделе Бинарный протокол — примеры, мы покажем фактические байт-коды ответа на сообщение IPROTO_EVAL.

Заглянув в errcode.h, по коду ошибки 0x0a (10 в десятичной системе) мы обнаружим ER_SPACE_EXISTS, чему соответствует строка «Space „%s“ already exists» («Спейс „%s“ уже существует»).

Начиная с версии 2.4.1, ответы на ошибки содержат дополнительную информацию, как описано выше. Эта дополнительная информация передается с помощью расширения MP_ERROR. Подробную информацию см. в разделе расширения MessagePack.

В ответе на SQL-запрос после заголовка идет тело сообщения, которое немного отличается от тела сообщения, описанного в разделе Бинарный протокол — ответы на запросы без ошибок и без SQL.

Если SQL-запрос не содержит операторы SELECT, VALUES или PRAGMA, то тело ответа содержит только IPROTO_SQL_INFO (0x42). Обычно IPROTO_SQL_INFO представляет собой ассоциативный массив с одним элементом — SQL_INFO_ROW_COUNT (0x00) — количество измененных строк.

# <size>
msgpack({MP_UINT unsigned integer = size(<header>) + size(<body>)})
# <header>
msgpack({
    Response-Code-Indicator: IPROTO_OK,
    IPROTO_SYNC: {MP_UINT unsigned integer, may be 64-bit},
    IPROTO_SCHEMA_VERSION: {MP_UINT unsigned integer}
})
# <body>
msgpack({
    IPROTO_SQL_INFO: {
        SQL_INFO_ROW_COUNT: {MP_UINT}
    }
})

Например, для запроса INSERT INTO table-name VALUES (1), (2), (3) тело ответа содержит ассоциативный массив IPROTO_SQL_INFO с SQL_INFO_ROW_COUNT = 3. SQL_INFO_ROW_COUNT может быть 0, если оператор не изменяет строку, и может быть 1, если оператор создает новый объект.

Ассоциативный массив IPROTO_SQL_INFO может содержать второй элемент SQL_INFO_AUTO_INCREMENT_IDS (0x01), который представляет собой новое значение (или значения) первичного ключа в операции вставки INSERT в таблицу с автоматическим увеличением первичного ключа PRIMARY KEY AUTOINCREMENT. В этом случае MP_MAP будет содержать два ключа, и одним из двух ключей будет 0x01: SQL_INFO_AUTO_INCREMENT_IDS, который представляет собой массив целых беззнаковых чисел.

Если запрос содержит SQL-оператор SELECT, VALUES или PRAGMA, ответ будет:

# <size>
msgpack(32)
# <header>
msgpack({
    Response-Code-Indicator: IPROTO_OK,
    IPROTO_SYNC: {MP_UINT unsigned integer, may be 64-bit},
    IPROTO_SCHEMA_VERSION: {MP_UINT unsigned integer}
})
# <body>
msgpack({
    IPROTO_METADATA: {array of column maps},
    IPROTO_DATA: {array of tuples}
})
  • IPROTO_METADATA: array of column maps = массив ассоциативных массивов, причем каждый ассоциативный массив содержит по крайней мере IPROTO_FIELD_NAME (0x00) + MP_STR и IPROTO_FIELD_TYPE (0x01) + MP_STR. Кроме того, если задано значение TRUE для sql_full_metadata в системном спейсе _session_settings, то массив будет содержать такие дополнительные ассоциативные массивы, которые соответствуют компонентам, описанным в разделе box.execute():
IPROTO_FIELD_COLL (0x02) + MP_STR
IPROTO_FIELD_IS_NULLABLE (0x03) + MP_BOOL
IPROTO_FIELD_IS_AUTOINCREMENT (0x04) + MP_BOOL
IPROTO_FIELD_SPAN (0x05) + MP_STR or MP_NIL
  • IPROTO_DATA:array of tuples = the result set «rows».

Пример: Если мы запросим полные метаданные, вызвав
conn.space._session_settings:update('sql_full_metadata', {{'=', 'value', true}})
и выберем две строки из таблицы t1, в которой есть столбцы DD и Д, с помощью
conn:execute([[SELECT dd, дд AS д FROM t1;]])
мы можем получить такой ответ:

# <body>
msgpack({
    IPROTO_METADATA: [
        IPROTO_FIELD_NAME: 'DD',
        IPROTO_FIELD_TYPE: 'integer',
        IPROTO_FIELD_IS_NULLABLE: false,
        IPROTO_FIELD_IS_AUTOINCREMENT: true,
        IPROTO_FIELD_SPAN: nil,
         ,
        IPROTO_FIELD_NAME: 'Д',
        IPROTO_FIELD_TYPE: 'string',
        IPROTO_FIELD_COLL: 'unicode',
        IPROTO_FIELD_IS_NULLABLE: true,
        IPROTO_FIELD_SPAN: 'дд'
    ],
    IPROTO_DATA: [
        [1,'a'],
        [2,'b']'
    ]
})

Если бы вместо этого мы вызвали
conn:prepare([[SELECT dd, дд AS д FROM t1;]])
то могли бы получить почти такой же ответ, но без IPROTO_DATA и с двумя дополнительными элементами:
34 00 = IPROTO_BIND_COUNT + MP_UINT = 0 (привязка параметров не нужна),
33 90 = IPROTO_BIND_METADATA + MP_ARRAY, size 0 (привязка параметров не нужна).

# <body>
msgpack({
    IPROTO_STMT_ID: {MP_UINT unsigned integer},
    IPROTO_BIND_COUNT: {MP_INT integer},
    IPROTO_BIND_METADATA: {array of parameter descriptors},
        IPROTO_METADATA: [
            IPROTO_FIELD_NAME: 'DD',
            IPROTO_FIELD_TYPE: 'integer',
            IPROTO_FIELD_IS_NULLABLE: false
            IPROTO_FIELD_IS_AUTOINCREMENT: true
            IPROTO_FIELD_SPAN: nil,
            ,
            IPROTO_FIELD_NAME: 'Д',
            IPROTO_FIELD_TYPE: 'string',
            IPROTO_FIELD_COLL: 'unicode',
            IPROTO_FIELD_IS_NULLABLE: true,
            IPROTO_FIELD_SPAN: 'дд'
        ]
    })

Теперь обратитесь к файлу исходного кода net_box.c, где функция «decode_metadata_optional» показывает, как Tarantool сам интерпретирует дополнительные элементы.

Далее, в разделе Бинарный протокол — примеры, мы покажем фактические байт-коды ответов на вышеприведенные SQL сообщения.

Когда клиент подключается к экземпляру сервера, тот выдает в ответе 128-байтовое текстовое сообщение приветствия не в формате MsgPack:
64 байта — строка 1 текста приветствия
64 байта — строка 2 текста приветствия
44 байта —закодированное в формате base-64 значение соль
20 байтов — NULL

Приветствие содержит две 64-байтные строки текста в формате ASCII. Каждая строка заканчивается символом разрыва строки (\n). Первая строка описывает версию экземпляра и тип протокола. Вторая строка содержит случайную строку в кодировке base-64 размером до 44 байтов для использования в пакете аутентификации и заканчивается на пробелы (до 23).

Часть приветствия представляет собой закодированное в формате base-64 значение соль для сессии (случайная строка), которое можно использовать для аутентификации. Максимальная длина закодированного значения соль (44 байта) больше, чем размер создаваемого сообщения аутентификации. Остаток предназначается для будущих схем аутентификации.

Аутентификация необязательна: если аутентификация не проводится, то пользователем в сеансе будет 'guest' (пользователю 'guest' пароль не нужен).

Если аутентификация проводится, то в любое время может быть подготовлен пакет аутентификации с использованием приветствия, имени и пароля пользователя и функции sha-1, как показано ниже.

PREPARE SCRAMBLE:

    size_of_encoded_salt_in_greeting = 44;
    size_of_salt_after_base64_decode = 32;
     /* sha1() will only use the first 20 bytes */
    size_of_any_sha1_digest = 20;
    size_of_scramble = 20;

prepare 'chap-sha1' scramble:

    salt = base64_decode(encoded_salt);
    step_1 = sha1(password);
    step_2 = sha1(step_1);
    step_3 = sha1(first_20_bytes_of_salt, step_2);
    scramble = xor(step_1, step_3);
    return scramble;

IPROTO_AUTH = 0x07

Клиент отправляет пакет аутентификации в виде сообщения IPROTO_AUTH:

# <size>
msgpack({MP_UINT unsigned integer = size(<header>) + size(<body>)})
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_AUTH,
    IPROTO_SYNC: {MP_UINT unsigned integer, usually = 1}
})
# <body>
msgpack({
    IPROTO_USER_NAME: {MP_STRING string <key>},
    IPROTO_TUPLE: ['chap-sha1', {MP_STRING 20-byte string}]
})

<key> содержит имя пользователя. <tuple> должен представлять собой массив из 2 полей: механизм аутентификации (на данный момент поддерживается только механизм «chap-sha1») и сообщение, зашифрованное в соответствии с указанным механизмом.

На пакет аутентификации экземпляр сервера отправляет стандартный ответ с 0 кортежей.

Чтобы понять, как Tarantool это обрабатывает, обратите внимание на функцию netbox_encode_auth` в файле net_box.c.

IPROTO_JOIN = 0x41. Сначала нужно отправить первоначальный запрос IPROTO_JOIN.

# <size>
msgpack({MP_UINT unsigned integer = size(<header>) + size(<body>)})
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_JOIN,
    IPROTO_SYNC: {MP_UINT unsigned integer}
})
# <body>
msgpack({
    IPROTO_INSTANCE_UUID: {uuid}
})

Затем экземпляр, к которому вы хотите подключиться, отправит свой последний SNAP-файл, просто создав несколько запросов типа INSERT (с дополнительными LSN и ServerID) (не отвечайте на них). Затем этот экземпляр отправит MP_MAP vclock и закроет сокет.

# <size>
msgpack({MP_UINT unsigned integer = size(<header>) + size(<body>)})
# <header>
msgpack({
    Response-Code-Indicator: 0,
    IPROTO_SYNC: {MP_UINT unsigned integer}
})
# <body>
msgpack({
    IPROTO_VCLOCK: {MP_INT SRV_ID, MP_INT SRV_LSN}
})

IPROTO_SUBSCRIBE = 0x42. Затем нужно отправить запрос IPROTO_SUBSCRIBE.

# <size>
msgpack({MP_UINT unsigned integer = size(<header>) + size(<body>)})
# <header>
msgpack({
    IPROTO_REQUEST_TYPE: IPROTO_SUBSCRIBE,
    IPROTO_SYNC: {MP_UINT unsigned integer},
    IPROTO_INSTANCE_UUID: {uuid},
    IPROTO_CLUSTER_UUID: {uuid},
})
# <body>
msgpack({
    IPROTO_VCLOCK: {MP_INT SRV_ID, MP_INT SRV_LSN}
})

Затем нужно обработать каждый запрос, который может прийти через других мастеров. В каждом запросе между мастерами будут дополнительные LSN и SERVER_ID.

КОНТРОЛЬНЫЕ СИГНАЛЫ

Часто мастер отправляет реплике сообщение контрольного сигнала. Например, если есть реплика с ID = 2 и метка с моментом времени в 2020 году, мастер может послать такое сообщение:

# <header>
msgpack({
    IPROTO_REQUEST_TYPE: 0
    IPROTO_REPLICA_ID: 2
    IPROTO_TIMESTAMP: {Float 64 MP_DOUBLE 8-byte timestamp}
})

и реплика может отправить в ответ следующее:

# <header>
msgpack({
    Response-Code-Indicator: IPROTO_OK
    IPROTO_REPLICA_ID: 2
    IPROTO_VCLOCK: {1, 6}
})

Далее, в разделе Бинарный протокол — примеры, мы покажем фактические байт-коды вышеприведенных сообщений контрольного сигнала.

ГОЛОСОВАНИЕ

При подключении для репликации экземпляр отправляет запрос с заголовком IPROTO_VOTE (0x44). Обычно ответом будет ER_OK и IPROTO_BALLOT (0x29). Поля в IPROTO_BALLOT — это элементы ассоциативного массива:

IPROTO_BALLOT_IS_RO (0x01) + MP_BOOL
IPROTO_BALLOT_VCLOCK (0x02) + vclock
IPROTO_BALLOT_GC_VCLOCK (0x03) + vclock
IPROTO_BALLOT_IS_LOADING (0x04) + MP_BOOL
IPROTO_BALLOT_IS_ANON = 0x05 + MP_BOOL

IPROTO_BALLOT_IS_ANON соответствует box.cfg.replication_anon.

Элементы, отличные от IPROTO_BALLOT_IS_ANON, были добавлены в версии 2.6.1. PROTO_BALLOT_IS_ANON был добавлен в версии 2.7.1.

ФЛАГИ

При репликации синхронных транзакций заголовок может содержать ключ = IPROTO_FLAGS и значение MP_UINT = один или несколько битов: IPROTO_FLAG_COMMIT, IPROTO_FLAG_WAIT_SYNC или IPROTO_FLAG_WAIT_ACK.

# <size>
msgpack(:samp:`{{MP_UINT unsigned integer = size(<header>) + size(<body>)}}`)
# <header>
msgpack({
    # ... другие элементы заголовка ...,
    IPROTO_FLAGS: :samp:`{{MP_UINT unsigned integer}}`
})
# <body>
msgpack({
    # ... message for a transaction ...
})

IPROTO_FLAG_COMMIT (0x01) указывает на последнее сообщение для транзакции. IPROTO_FLAG_WAIT_SYNC (0x02) указывает на последнее сообщение для транзакции, которую нельзя завершить немедленно. IPROTO_FLAG_WAIT_ACK (0x04) указывает на последнее сообщение для синхронной транзакции.

Чтобы выполнить примеры, приведенные в этом разделе, возьмите компьютер с Linux и запустите три командных оболочки («терминалы»).

– На терминале №1 запустите мониторинг порта 3302 с помощью tcpdump:

sudo tcpdump -i lo 'port 3302' -X

На терминале №2 запустите сервер так:

box.cfg{listen=3302}
box.schema.space.create('tspace')
box.space.tspace:create_index('I')
box.space.tspace:insert{280}
box.schema.user.grant('guest','read,write,execute,create,drop','universe')

На терминале №3 запустите ещё один сервер, который будет выступать в качестве клиента, так:

box.cfg{}
net_box = require('net.box')
conn = net_box.connect('localhost:3302')
conn.space.tspace:select(280)

Теперь посмотрите, что tcpdump покажет для запроса — задания подключения к порту 3302. После слов «length 32» идет пакет, который заканчивается этими 32 байтами (мы добавили комментарии после отступов):

ce 00 00 00 1b   MP_UINT = decimal 27 = number of bytes after this
82               MP_MAP, size 2 (we'll call this "Main-Map")
01                 IPROTO_SYNC (Main-Map Item#1)
04                 MP_INT = 4 = number that gets incremented with each request
00                 IPROTO_REQUEST_TYPE (Main-Map Item#2)
01                 IPROTO_SELECT
86                 MP_MAP, size 6 (we'll call this "Select-Map")
10                   IPROTO_SPACE_ID (Select-Map Item#1)
cd 02 00             MP_UINT = decimal 512 = id of tspace (could be larger)
11                   IPROTO_INDEX_ID (Select-Map Item#2)
00                   MP_INT = 0 = id of index within tspace
14                   IPROTO_ITERATOR (Select-Map Item#3)
00                   MP_INT = 0 = Tarantool iterator_type.h constant ITER_EQ
13                   IPROTO_OFFSET (Select-Map Item#4)
00                   MP_INT = 0 = amount to offset
12                   IPROTO_LIMIT (Select-Map Item#5)
ce ff ff ff ff       MP_UINT = 4294967295 = biggest possible limit
20                   IPROTO_KEY (Select-Map Item#6)
91                   MP_ARRAY, size 1 (we'll call this "Key-Array")
cd 01 18               MP_UINT = 280 (Select-Map Item#6, Key-Array Item#1)
                       -- 280 is the key value that we are searching for

Теперь в файле исходного кода net_box.c перейдите к строке netbox_encode_select(lua_State *L). Из комментариев и из простых вызовов функций типа mpstream_encode_uint(&stream, IPROTO_SPACE_ID); можно понять, как net_box собирает воедино содержимое пакета, описанного выше с помощью tcpdump.

Существуют библиотеки для чтения и записи объектов в формате MessagePack. Программисты на языке C иногда включают msgpuck.h.

Теперь вы знаете, как сам Tarantool выполняет запросы по бинарному протоколу. Если какие-то детали остаются неясными, обратитесь к файлу net_box.c, где описаны процедуры для каждого запроса. Некоторые коннекторы написаны аналогично.

Рассмотрим пример IPROTO_UPDATE. Предположим, пользователь изменяет поле №2 кортежа №2 в спейсе №256 на „BBBB“`. Тело будет выглядеть так (обратите внимание, что в этом случае дополнительный необязательный элемент ассоциативного массива IPROTO_INDEX_BASE подчеркивает, что номера полей начинаются с 1 — это можно опустить):

04               IPROTO_UPDATE
85               IPROTO_MAP, size 5
10                 IPROTO_SPACE_ID, Map Item#1
cd 02 00           MP_UINT 256
11                 IPROTO_INDEX_ID, Map Item#2
00                 MP_INT 0 = primary-key index number
15                 IPROTO_INDEX_BASE, Map Item#3
01                 MP_INT = 1 i.e. field numbers start at 1
21                 IPROTO_TUPLE, Map Item#4
91                 MP_ARRAY, size 1, for array of operations
93                   MP_ARRAY, size 3
a1 3d                   MP_STR = OPERATOR = '='
02                      MP_INT = FIELD_NO = 2
a5 42 42 42 42 42       MP_STR = VALUE = 'BBBB'
20                 IPROTO_KEY, Map Item#5
91                 MP_ARRAY, size 1, for array of key values
02                   MP_UINT = primary-key value = 2

Byte codes for the IPROTO_EXECUTE example:

0b               IPROTO_EXECUTE
83               MP_MAP, size 3
43                 IPROTO_STMT_ID Map Item#1
ce d7 aa 74 1b     MP_UINT value of n.stmt_id
41                 IPROTO_SQL_BIND Map Item#2
92                 MP_ARRAY, size 2
01                   MP_INT = 1 = value for first parameter
a1 61                MP_STR = 'a' = value for second parameter
2b                 IPROTO_OPTIONS Map Item#3
90                 MP_ARRAY, size 0 (there are no options)

Byte codes for the response to the box.space.space-name:insert{6} example:

ce 00 00 00 20                MP_UINT = HEADER + BODY SIZE
83                            MP_MAP, size 3
00                              Response-Code-Indicator
ce 00 00 00 00                  MP_UINT = IPROTO_OK
01                              IPROTO_SYNC
cf 00 00 00 00 00 00 00 53      MP_UINT = sync value
05                              IPROTO_SCHEMA_VERSION
ce 00 00 00 68                  MP_UINT = schema version
81                            MP_MAP, size 1
30                              IPROTO_DATA
dd 00 00 00 01                  MP_ARRAY, size 1 (row count)
91                              MP_ARRAY, size 1 (field count)
06                              MP_INT = 6 = the value that was inserted

Byte codes for the response to the conn:eval([[box.schema.space.create('_space');]]) example:

ce 00 00 00 3b                  MP_UINT = HEADER + BODY SIZE
83                              MP_MAP, size 3 (i.e. 3 items in header)
   00                              Response-Code-Indicator
   ce 00 00 80 0a                  MP_UINT = hexadecimal 800a
   01                              IPROTO_SYNC
   cf 00 00 00 00 00 00 00 26      MP_UINT = sync value
   05                              IPROTO_SCHEMA_VERSION
   ce 00 00 00 78                  MP_UINT = schema version value
   81                              MP_MAP, size 1
     31                              IPROTO_ERROR_24
     db 00 00 00 1d 53 70 61 63 etc. MP_STR = "Space '_space' already exists"

Byte codes, if we use the same net.box connection that we used for Binary protocol – illustration and we say
conn:execute([[CREATE TABLE t1 (dd INT PRIMARY KEY AUTOINCREMENT, дд STRING COLLATE "unicode");]])
conn:execute([[INSERT INTO t1 VALUES (NULL, 'a'), (NULL, 'b');]])
and we watch what tcpdump displays, we will see two noticeable things: (1) the CREATE statement caused a schema change so the response has a new IPROTO_SCHEMA_VERSION value and the body includes the new contents of some system tables (caused by requests from net.box which users will not see); (2) the final bytes of the response to the INSERT will be:

81   MP_MAP, size 1
42     IPROTO_SQL_INFO
82     MP_MAP, size 2
00       Tarantool constant (not in iproto_constants.h) = SQL_INFO_ROW_COUNT
02       1 = row count
01       Tarantool constant (not in iproto_constants.h) = SQL_INFO_AUTOINCREMENT_ID
92       MP_ARRAY, size 2
01         first autoincrement number
02         second autoincrement number

Byte codes for the SQL SELECT example, if we ask for full metadata by saying
conn.space._session_settings:update('sql_full_metadata', {{'=', 'value', true}})
and we select the two rows from the table that we just created
conn:execute([[SELECT dd, дд AS д FROM t1;]])
then tcpdump will show this response, after the header:

82                       MP_MAP, size 2 (i.e. metadata and rows)
32                         IPROTO_METADATA
92                         MP_ARRAY, size 2 (i.e. 2 columns)
85                           MP_MAP, size 5 (i.e. 5 items for column#1)
00 a2 44 44                    IPROTO_FIELD_NAME + 'DD'
01 a7 69 6e 74 65 67 65 72     IPROTO_FIELD_TYPE + 'integer'
03 c2                          IPROTO_FIELD_IS_NULLABLE + false
04 c3                          IPROTO_FIELD_IS_AUTOINCREMENT + true
05 c0                          PROTO_FIELD_SPAN + nil
85                           MP_MAP, size 5 (i.e. 5 items for column#2)
00 a2 d0 94                    IPROTO_FIELD_NAME + 'Д' upper case
01 a6 73 74 72 69 6e 67        IPROTO_FIELD_TYPE + 'string'
02 a7 75 6e 69 63 6f 64 65     IPROTO_FIELD_COLL + 'unicode'
03 c3                          IPROTO_FIELD_IS_NULLABLE + true
05 a4 d0 b4 d0 b4              IPROTO_FIELD_SPAN + 'дд' lower case
30                         IPROTO_DATA
92                         MP_ARRAY, size 2
92                           MP_ARRAY, size 2
01                             MP_INT = 1 i.e. contents of row#1 column#1
a1 61                          MP_STR = 'a' i.e. contents of row#1 column#2
92                           MP_ARRAY, size 2
02                             MP_INT = 2 i.e. contents of row#2 column#1
a1 62                          MP_STR = 'b' i.e. contents of row#2 column#2

Byte code for the SQL PREPARE example. If we said
conn:prepare([[SELECT dd, дд AS д FROM t1;]])
then tcpdump would should show almost the same response, but there would be no IPROTO_DATA and there would be two additional items:
34 00 = IPROTO_BIND_COUNT + MP_UINT = 0 (there are no parameters to bind),
33 90 = IPROTO_BIND_METADATA + MP_ARRAY, size 0 (there are no parameters to bind).

84                       MP_MAP, size 4
43                         IPROTO_STMT_ID
ce c2 3c 2c 1e             MP_UINT = statement id
34                         IPROTO_BIND_COUNT
00                         MP_INT = 0 = number of parameters to bind
33                         IPROTO_BIND_METADATA
90                         MP_ARRAY, size 0 = there are no parameters to bind
32                         IPROTO_METADATA
92                         MP_ARRAY, size 2 (i.e. 2 columns)
85                           MP_MAP, size 5 (i.e. 5 items for column#1)
00 a2 44 44                    IPROTO_FIELD_NAME + 'DD'
01 a7 69 6e 74 65 67 65 72     IPROTO_FIELD_TYPE + 'integer'
03 c2                          IPROTO_FIELD_IS_NULLABLE + false
04 c3                          IPROTO_FIELD_IS_AUTOINCREMENT + true
05 c0                          PROTO_FIELD_SPAN + nil
85                           MP_MAP, size 5 (i.e. 5 items for column#2)
00 a2 d0 94                    IPROTO_FIELD_NAME + 'Д' upper case
01 a6 73 74 72 69 6e 67        IPROTO_FIELD_TYPE + 'string'
02 a7 75 6e 69 63 6f 64 65     IPROTO_FIELD_COLL + 'unicode'
03 c3                          IPROTO_FIELD_IS_NULLABLE + true
05 a4 d0 b4 d0 b4              IPROTO_FIELD_SPAN + 'дд' lower case

Byte code for the heartbeat example. The master might send this body:

83                      MP_MAP, size 3
00                        Main-Map Item #1 IPROTO_REQUEST_TYPE
00                          MP_UINT = 0
02                        Main-Map Item #2 IPROTO_REPLICA_ID
02                          MP_UINT = 2 = id
04                        Main-Map Item #3 IPROTO_TIMESTAMP
cb                          MP_DOUBLE (MessagePack "Float 64")
41 d7 ba 06 7b 3a 03 21     8-byte timestamp

Byte code for the heartbeat example. The replica might send back this body

81                       MP_MAP, size 1
00                         Main-Map Item #1 Response-code-indicator
00                         MP_UINT = 0 = IPROTO_OK
81                         Main-Map Item #2, MP_MAP, size 1
26                           Sub-Map Item #1 IPROTO_VCLOCK
81                           Sub-Map Item #2, MP_MAP, size 1
01                             MP_UINT = 1 = id (part 1 of vclock)
06                             MP_UINT = 6 = lsn (part 2 of vclock)

Файлы форматов .xlog и .snap выглядят практически одинаково. Заголовок выглядит так:

<type>\n                  SNAP\n или XLOG\n
<version>\n               в данный момент 0.13\n
Server: <server_uuid>\n   где UUID -- это 36-байтная строка
VClock: <vclock_map>\n    например, {1: 0}\n
\n

После файла заголовка идут кортежи с данными. Кортежи начинаются с маркера строки 0xd5ba0bab, а после последнего кортежа может стоять маркер конца файла 0xd510aded. Таким образом, между заголовком файла и маркером конца файла могут быть кортежи с данными в следующем виде:

0            3 4                                         17
+-------------+========+============+===========+=========+
|             |        |            |           |         |
| 0xd5ba0bab  | LENGTH | CRC32 PREV | CRC32 CUR | PADDING |
|             |        |            |           |         |
+-------------+========+============+===========+=========+
   MP_FIXEXT2    MP_INT     MP_INT       MP_INT      ---

+============+ +===================================+
|            | |                                   |
|   HEADER   | |                BODY               |
|            | |                                   |
+============+ +===================================+
     MP_MAP                     MP_MAP

См. пример в разделе Форматы файлов.