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, 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
81 MP_MAP (body), size 1
5a Body Map Item #1 IPROTO_VCLOCK_SYNC
14 MP_UINT = 20 (vclock sync value)
Byte code for the heartbeat example. The replica might send back this body:
81 MP_MAP, size 1
00 Main-Map Item #1 IPROTO_REQUEST_TYPE
00 MP_UINT = 0 = IPROTO_OK
83 MP_MAP (body), size 3
26 Body Map Item #1 IPROTO_VCLOCK
81 MP_MAP, size 1 (vclock of 1 component)
01 MP_UINT = 1 = id (part 1 of vclock)
06 MP_UINT = 6 = lsn (part 2 of vclock)
5a Body Map Item #2 IPROTO_VCLOCK_SYNC
14 MP_UINT = 20 (vclock sync value)
53 Body Map Item #3 IPROTO_TERM
31 MP_UINT = 49 (term value)