Understanding the binary protocol | Tarantool
Документация на русском языке
поддерживается сообществом
Примеры и руководства More guides Understanding the binary protocol

Understanding the binary protocol

To communicate with each other, Tarantool instances use a binary protocol called iproto. To learn more, see the Binary protocol section.

In this set of examples, the user will be looking at binary code transferred via iproto. The code is intercepted with tcpdump, a monitoring utility.

Чтобы выполнить примеры, приведенные в этом разделе, запустите на компьютере с 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')

On terminal #3, run the following:

conn.space.tspace:select(280)

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

ce 00 00 00 1b   MP_UINT = 27, десятичное число = число байт после этого
82               MP_MAP, размер 2 (назовем это Main-Map)
01                 IPROTO_SYNC (1-й элемент Main-Map)
04                 MP_INT = 4 = число, которое увеличивается на 1 с каждым запросом
00                 IPROTO_REQUEST_TYPE (2-й элемент Main-Map)
01                 IPROTO_SELECT
86                 MP_MAP, размер 6 (назовем это Select-Map)
10                   IPROTO_SPACE_ID (1-й элемент Select-Map)
cd 02 00             MP_UINT = 512, десятичное число = id tspace (может быть больше)
11                   IPROTO_INDEX_ID (2-й элемент Select-Map)
00                   MP_INT = 0 = id индекса в tspace
14                   IPROTO_ITERATOR (3-й элемент Select-Map)
00                   MP_INT = 0 = константа Tarantool iterator_type.h ITER_EQ
13                   IPROTO_OFFSET (4-й элемент Select-Map)
00                   MP_INT = 0 = смещение
12                   IPROTO_LIMIT (5-й элемент Select-Map)
ce ff ff ff ff       MP_UINT = 4294967295 = наибольший возможный предел
20                   IPROTO_KEY (6-й элемент Select-Map)
91                   MP_ARRAY, размер 1 (назовем это Key-Array)
cd 01 18               MP_UINT = 280 (6-й элемент Select-Map, 1-й элемент Key-Array)
                       -- 280, ключевое значение, которое мы ищем

Теперь в файле исходного кода 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, размер 5
10                 IPROTO_SPACE_ID, 1-й элемент ассоциативного массива
cd 02 00           MP_UINT 256
11                 IPROTO_INDEX_ID, 2-й элемент ассоциативного массива
00                 MP_INT 0 = номер индекса первичного ключа
15                 IPROTO_INDEX_BASE, 3-й элемент ассоциативного массива
01                 MP_INT = 1, т.е. нумерация полей начинается с 1
21                 IPROTO_TUPLE, 4-й элемент ассоциативного массива
91                 MP_ARRAY, размер 1, для массива операций
93                   MP_ARRAY, размер 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, 5--й элемент ассоциативного массива
91                 MP_ARRAY, размер 1, для массива ключей
02                   MP_UINT = значение первичного ключа = 2

Пример байт-кода IPROTO_EXECUTE:

0b               IPROTO_EXECUTE
83               MP_MAP, размер 3
43                 IPROTO_STMT_ID 1-й элемент ассоциативного массива
ce d7 aa 74 1b     MP_UINT значение n.stmt_id
41                 IPROTO_SQL_BIND 2-й элемент ассоциативного массива
92                 MP_ARRAY, размер 2
01                   MP_INT = 1 = значение первого параметра
a1 61                MP_STR = 'a' = значение второго параметра
2b                 IPROTO_OPTIONS 3-й элемент ассоциативного массива
90                 MP_ARRAY, размер 0 (никакие опции не выбраны)

Пример байт-кода ответа на запрос box.space.space-name:insert{6}:

ce 00 00 00 20                MP_UINT = HEADER AND BODY SIZE
83                            MP_MAP, size 3
00                              IPROTO_REQUEST_TYPE
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

Пример байт-кода ответа на запрос conn:eval([[box.schema.space.create('_space');]]):

ce 00 00 00 3b                  MP_UINT = HEADER AND BODY SIZE
83                              MP_MAP, size 3 (i.e. 3 items in header)
   00                              IPROTO_REQUEST_TYPE
   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 in the beginning 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, размер 1
42     IPROTO_SQL_INFO
82     MP_MAP, размер 2
00       константа Tarantool (не из iproto_constants.h) = SQL_INFO_ROW_COUNT
02       1 = число строк
01       константа Tarantool (не из iproto_constants.h) = SQL_INFO_AUTOINCREMENT_ID
92       MP_ARRAY, размер 2
01         первое число с автоинкрементом
02         второе число с автоинкрементом

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

82                       MP_MAP, размер 2 (метаданные и строки)
32                         IPROTO_METADATA
92                         MP_ARRAY, размер 2 (2 столбца)
85                           MP_MAP, размер 5 (5 элементов для столбца 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, размер 5 (5 элементов для столбца 2)
00 a2 d0 94                    IPROTO_FIELD_NAME и 'Д' в верхнем регистре
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 и 'дд' в нижнем регистре
30                         IPROTO_DATA
92                         MP_ARRAY, размер 2
92                           MP_ARRAY, размер 2
01                             MP_INT = 1: содержимое строки 1, столбца 1
a1 61                          MP_STR = 'a': содержимое строки 1, столбца 2
92                           MP_ARRAY, размер 2
02                             MP_INT = 2: содержимое строки 2, столбца 1
a1 62                          MP_STR = 'b': содержимое строки 2, столбца 2

Пример байт-кода SQL PREPARE. Если вызвать conn:prepare([[SELECT dd, дд AS д FROM t1;]]), вывод tcpdump будет почти таким же, но исчезнет IPROTO_DATA. Вместо этого появятся дополнительные байты:

34                       IPROTO_BIND_COUNT
00                       MP_UINT = 0

33                       IPROTO_BIND_METADATA
90                       MP_ARRAY, размер 0

MP_UINT = 0. Массив MP_ARRAY имеет размер 0, поскольку параметров нет. Вывод целиком:

84                       MP_MAP, размер 4
43                         IPROTO_STMT_ID
ce c2 3c 2c 1e             MP_UINT = ID инструкции
34                         IPROTO_BIND_COUNT
00                         MP_INT = 0 = число привязываемых параметров
33                         IPROTO_BIND_METADATA
90                         MP_ARRAY, размер 0 = нет привязываемых параметров
32                         IPROTO_METADATA
92                         MP_ARRAY, размер 2 (2 столбца)
85                           MP_MAP, размер 5 (5 элементов для столбца 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, размер 5 (5 элементов для столбца 2)
00 a2 d0 94                    IPROTO_FIELD_NAME + 'Д' в верхнем регистре
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 и 'дд' в нижнем регистре

Пример байт-кода контрольного сигнала. Мастер может отправить следующее тело:

83                      MP_MAP, размер 3
00                        1-й элемент Main-Map IPROTO_REQUEST_TYPE
00                          MP_UINT = 0
02                        2-й элемент Main-Map IPROTO_REPLICA_ID
02                          MP_UINT = 2 = id
04                        3-й элемент Main-Map IPROTO_TIMESTAMP
cb                          MP_DOUBLE (MessagePack "Float 64")
41 d7 ba 06 7b 3a 03 21     8-байтовая временная отметка

Пример байт-кода ответа на контрольный сигнал. Реплика может вернуть следующее тело:

81                       MP_MAP, size 1
00                         Main-Map Item #1 IPROTO_REQUEST_TYPE
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)
Нашли ответ на свой вопрос?
Обратная связь