-
Notifications
You must be signed in to change notification settings - Fork 3
[RUS] TheTunnel Protocol Implementation
Протокол TheTunnel (TT) состоит из трёх слоёв
- транспортный слой Light(T)
- логистический слой Cord(C)
- презентационный слой (D)
TT предназначен для работы поверх стека Tcp/Ip.
Light выполняет несколько задач:
- пакетная передача данных (в отличие от пакетно-потоковой в Tcp/Ip)
- возможность передавать большие посылки (более 1 кб )
- возможность одновременной передачи нескольких посылок.
Посылка переданная через Light, называется lightMsg
Light - симметричен для серверной и клиентской сторон.
lighMessage, в общем случае передаётся несколькими пакетами.
Эти пакеты называются Quant. Максимальная длинна одного кванта - MaxQuantLenght.
Существует 3 типа квантов:
- start-quant.
- Data-quant.
- Abort-quant.
Схема работы такова: При посылке lightMessage, первым отправляется startQuant, несущий информацию о передаваемом lightMessage и его данные. Если посылка lightMessage не влеза в startQuant, то остатки передаются посылкой последовательности Data-quant. Процесс передачи можно отменить посылкой AbortQuant (как передающей, так и принимающей стороной).
Start-Quant [11+ n bytes]
- [2b] quant-lenght //полная длинна кванта
- [4b] message Id //идентификатор передаваемого сообщения
- 0x01 quant -type //тип кванта (1)
- [4b] lightMessageLenght //длинна lightMessage в байтах
- [Nb] quantMsg // собственно содержимое пакета
первый фрейм в передаче некоторого сообщения. В поле lightMessageLenght указывается длинна исходного сообщения. Это сообщение несёт qMsg. В случае если длинна переносимого qMsg = значению typeArgument, считается что сообщение полностью передано.
Data-Quant. [7+ n bytes]
- [2b] quant-lenght //полная длинна кванта
- [4b] message Id //идентификатор передаваемого сообщения
- 0x02 quant -type //тип кванта (2)
- [Nb] quantMsg // cодержимое пакета
Фреймы, идущие после Start-фрейма и несущие в себе продолжение сообщения. Имеют тотже messageId что и старт фрейм. Вообще все фреймы, касающиеся передачи конкретного сообщения имеют одинаковый message Id.
AbortSend-Quant [8 bytes]
- [2b] quant-lenght //полная длинна кванта
- [4b] message Id //идентификатор передаваемого сообщения
- 0x03 quant -type //тип кванта (3)
- [1b] Cause //причина
Отменяет передачу сообщения с указанным messageId. Может передаётся исключительно от отправляющей стороны к принимающей. Несёт в себе причину отмены передачи Однако эти причины ещё не закреплены (тоесть пока можно игнорировать это поле). Известно лишь что значение 0 - обозначает ХЗ-причину.
Одновременная передача нескольких сообщений
Поле messageId делает возможной одновременую передачу несколько сообщений. Например: Вы передаёте громоздкий файл конфигурации, одновременно с этим записывая и получая текущие данные от системы. Различные передаваемые сообщения будут иметь различный messageId.
Порядок следования квантов от различных сообщений в случае одновременной передачи - произвольный, однако, если по некоторому messageID не поступало ни одного кванта в течение Twait (например 10 секунд), то принимающая сторона имеет право отменить приём этого сообщения (отчистить буфер).
Корд-слой начинается когда light (или любой другой транспорт) передал нам lightMessage.
При общение клиента и сервера, как правило участвуют десятки различных типов сообщения.Тип сообщения называется кордой. Имя типа сообщения (имя корды) является int16 числом.
Корды делятся на три типа:
- [SAY] Сообщение не требующее ответа
- [ASK] Сообщение требующее ответа
- [ANS] Ответное сообщение на соответствующий ASK
Имя корды указывается в начале пакета и является числом int16 (2 байта, знаковое) Имена SAY и ASK корд всегда > 0 Имена ANS корд всегда отрицательны и всегда совпадают по модулю с соответствующими именами ASK корд. Имена корд лежат в диапазоне [-16382 : 16383] Большие по модулю значению зарезрвированы.
Головы ASK и ANS , помимо имени корды имеют 2х байтовый идентификатор вопроса, уникальный внутри связки вопрос-ответ. Это необходимо для возможности посылки нескольких вопросов одновременно. Например: пересылка нескольких файлов.
Формат корд -пакета
- SAY: [2b CordName][N CordMessage]
- ASK: [2b CordName][2b AskID][N CordMessage]
- ANS: [2b CordName][2b AskID][N CordMessage]
Когда мы хотим отправить объект - необходимо превратить его в поток байтов. Когда мы хотим принять объект - необходимо разобрать его из потока байтов.
На мой взгляд для этой задачи лучше всего использовать Google.Protobuf по следующим причинам:
- кроссплатформенность
- реализации на почти всех известных языках программирования
- скорость работы
- компактность результирующих посылок
- отлаженность.
Однако тянуть эту библиотеку и разбираться с ней не всегда оправдано, особенно если речь идёт о простых структурах фиксированного размера или строках. Поэтому в TheTunnel предусмотрена работа с несколькими моделями де-сериализации:
- стандартные типы
- ProtoBuf типы
- собственные сериализаторы/десериализаторы
- деревья из всех вышеперечисленных типов объектов
Существует два вида передаваемых типов:
- типы с фиксированным размером
- типы с переменным размером
Каждая посылка является последовательностью объектов различных типов. Если тип элемента в последовательности имеет переменный размер, а размер последовательности больше 1, то перед ним находится int32 число, обозначающее длинну объекта.
Массив является частным случаем последовательности объектов с одинаковым типом.
Последовательность также является объектом , и может включать в себя другие последовательности. Если последовательность имеет строго фиксированный размер - она называется структурой. В противном случае последовательность считается объектом с переменным размером.
Дерево (граф) объектов является множеством вложенных друг в друга последовательностей.
type size
int8/16/32/64 8/16/32/64
uint8/16/32/64 8/16/32/64
float 4
double 8
FileTimeUTC* 8
FixedSizedStruct N
64-битное число, представляющее собой количество 100-наносекундных интервалов, которое прошло с 12.00 АМ 1601.01.01 в GMT+0.
« A file time is a 64-bit value that represents the number of 100-nanosecond intervals that have elapsed since 12:00 A.M. January 1, 1601 Coordinated Universal Time (UTC). «
Структура фиксированного размера - это объект такого типа, любые экземпляры которого имеют одинаковый размер, состав полей и отступы для этих полей. По своей сути является частным случаем последовательности с фиксированным размером. При использование C# - в описание типа этого объекта необходимо использовать аттрибуты StructLayout(Value= Explicit) для типа и FieldLayout для каждого из полей.
Пример C#:
[StructLayout(Value= Explicit, Size = 16)]
public class Toilet{
[FieldOffset(0)] public double Volume;
[FieldOffset(8)] public UInt32 UsedTimes;
[FieldOffset(12)] public float State;}Строка передаётся в Unicode(UTF-16) формате.
Тип, де/сериализуемый библиотекой ProtoBuff
Размер и формат сериализации/десериализации определяется пользователем
Несмотря на многообразие и кажущуюся сложность правил сериализации и десериализации я хочу оправдаться вот как:
- на самом деле всё не так уж и сложно, и хорошо ложится на языки программирования
- нет необходимости реализовывать все возможности. достаточно реализовать, к примеру, ProtoType или базовые типы и структуры, что делается очень быстро.
Для нормального общения клиента и сервера, а также выяснения какому из программистов дать по щщам, пишется контракт - документ, в котором указывается протокол общения, имена корд и типы передаваемых ими данных для серверной части.
# Пример контракта для простейшего чата:
1 VOID SendMessageToServer UTC:timestamp STRING:nick STRING:message
<-2 BYTE SendMessageToClient STRING:nick STRING:message #will not lie to yourself - bool is always byte