Skip to content

[RUS] TheTunnel Protocol Implementation

tmteam edited this page Oct 3, 2014 · 3 revisions

Протокол TheTunnel.

Протокол TheTunnel (TT) состоит из трёх слоёв

  • транспортный слой Light(T)
  • логистический слой Cord(C)
  • презентационный слой (D)

TT предназначен для работы поверх стека Tcp/Ip.

Транспортный слой Light.

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 секунд), то принимающая сторона имеет право отменить приём этого сообщения (отчистить буфер).

Логистический слой - Cord

Корд-слой начинается когда 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 число, обозначающее длинну объекта.

Массив является частным случаем последовательности объектов с одинаковым типом.

Последовательность также является объектом , и может включать в себя другие последовательности. Если последовательность имеет строго фиксированный размер - она называется структурой. В противном случае последовательность считается объектом с переменным размером.

Дерево (граф) объектов является множеством вложенных друг в друга последовательностей.

Стандартные типы фиксированного размера (little-endian)


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

FIleTimeUTC

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). «

FixedSizedStruct

Структура фиксированного размера - это объект такого типа, любые экземпляры которого имеют одинаковый размер, состав полей и отступы для этих полей. По своей сути является частным случаем последовательности с фиксированным размером. При использование 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 -строка(тип переменного размера)

Строка передаётся в Unicode(UTF-16) формате.

ProtoType(тип переменного размера)

Тип, де/сериализуемый библиотекой 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

Пример FTP - подобного протокола