Как сделать свой сетевой протокол

Протокол своими руками. Создаем с нуля TCP-протокол и пишем сервер на C#

Ты в жизни не раз сталкивался с разными протоколами — одни использовал, другие, возможно, реверсил. Одни были легко читаемы, в других без hex-редактора не разобраться. В этой статье я покажу, как создать свой собственный протокол, который будет работать поверх TCP/IP. Мы разработаем свою структуру данных и реализуем сервер на C#.

Итак, протокол передачи данных — это соглашение между приложениями о том, как должны выглядеть передаваемые данные. Например, сервер и клиент могут использовать WebSocket в связке с JSON. Вот так приложение на Android могло бы запросить погоду с сервера:

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

Создаем базовую структуру протокола

Этот протокол будет базовым для простоты. Но мы будем вести его разработку с расчетом на то, что впоследствии его расширим и усложним.

Первое, что необходимо ввести, — это наш собственный заголовок, чтобы приложения могли отличать пакеты нашего протокола. У нас это будет набор байтов 0xAF , 0xAA , 0xAF . Именно они и будут стоять в начале каждого сообщения.

INFO

Почти каждый бинарный протокол имеет свое «магическое число» (также «заголовок» и «сигнатура») — набор байтов в начале пакета. Оно используется для идентификации пакетов своего протокола. Остальные пакеты будут игнорироваться.

Каждый пакет будет иметь тип и подтип и будет размером в байт. Так мы сможем создать 65 025 (255 * 255) разных типов пакетов. Пакет будет содержать в себе поля, каждое со своим уникальным номером, тоже размером в один байт. Это предоставит возможность иметь 255 полей в одном пакете. Чтобы удостовериться в том, что пакет дошел до приложения полностью (и для удобства парсинга), добавим байты, которые будут сигнализировать о конце пакета.

Читайте также:  Основные топологии сети шинная

Завершенная структура пакета:

XPROTOCOL PACKET STRUCTURE

(offset: 0) HEADER (3 bytes) [ 0xAF, 0xAA, 0xAF ]
(offset: 3) PACKET ID
(offset: 3) PACKET TYPE (1 byte)
(offset: 4) PACKET SUBTYPE (1 byte)
(offset: 5) FIELDS (FIELD[])
(offset: END) PACKET ENDING (2 bytes) [ 0xFF, 0x00 ]

(offset: 0) FIELD ID (1 byte)
(offset: 1) FIELD SIZE (1 byte)
(offset: 2) FIELD CONTENTS

Назовем наш протокол, как ты мог заметить, XProtocol. На третьем сдвиге начинается информация о типе пакета. На пятом начинается массив из полей. Завершающим звеном будут байты 0xFF и 0x00 , закрывающие пакет.

Пишем клиент и сервер

Для начала нужно ввести основные свойства, которые будет иметь пакет:

public class XPacket < public byte PacketType < get; private set; >public byte PacketSubtype < get; private set; >public List Fields < get; set; >= new List(); > 

Добавим класс для описания поля пакета, в котором будут его данные, ID и размер.

public class XPacketField < public byte FieldID < get; set; >public byte FieldSize < get; set; >public byte[] Contents < get; set; >> 

Сделаем обычный конструктор приватным и создадим статический метод для получения нового экземпляра объекта.

private XPacket() <> public static XPacket Create(byte type, byte subtype) < return new XPacket < PacketType = type, PacketSubtype = subtype >; > 

Теперь можно задать тип пакета и поля, которые будут внутри него. Создадим функцию для этого. Записывать будем в поток MemoryStream . Первым делом запишем байты заголовка, типа и подтипа пакета, а потом отсортируем поля по возрастанию FieldID .

public byte[] ToPacket() < var packet = new MemoryStream(); packet.Write( new byte[] , 0, 5); var fields = Fields.OrderBy(field => field.FieldID); foreach (var field in fields) < packet.Write(new[] , 0, 2); packet.Write(field.Contents, 0, field.Contents.Length); > packet.Write(new byte[] , 0, 2); return packet.ToArray(); > 

Теперь запишем все поля. Сначала пойдет ID поля, его размер и данные. И только потом конец пакета — 0xFF , 0x00 .

Читайте также:  Адресация в компьютерной сети ipv4

Теперь пора научиться парсить пакеты.

INFO

Минимальный размер пакета — 7 байт: HEADER (3) + TYPE (1) + SUBTYPE (1) + PACKET ENDING (2)

Проверяем размер входного пакета, его заголовок и два последних байта. После валидации пакета получим его тип и подтип.

public static XPacket Parse(byte[] packet) < if (packet.Length < 7) < return null; >if (packet[0] != 0xAF || packet[1] != 0xAA || packet[2] != 0xAF) < return null; >var mIndex = packet.Length - 1; if (packet[mIndex - 1] != 0xFF || packet[mIndex] != 0x00) < return null; >var type = packet[3]; var subtype = packet[4]; var xpacket = Create(type, subtype); /* */ 

Пора перейти к парсингу полей. Так как наш пакет заканчивается двумя байтами, мы можем узнать, когда закончились данные для парсинга. Получим ID поля и его размер, добавим к списку. Если пакет будет поврежден и будет существовать поле с ID , равным нулю, и SIZE , равным нулю, то необходимости его парсить нет.

 /* */ var fields = packet.Skip(5).ToArray(); while (true) < if (fields.Length == 2) < return xpacket; >var var size = fields[1]; var contents = size != 0 ? fields.Skip(2).Take(size).ToArray() : null; xpacket.Fields.Add(new XPacketField < FieldID = id, FieldSize = size, Contents = contents >); fields = fields.Skip(2 + size).ToArray(); > > 

У кода выше есть проблема: если подменить размер одного из полей, парсинг завершится с необработанным исключением или пропарсит пакет неверно. Необходимо обеспечить безопасность пакетов. Но об этом речь пойдет чуть позже.

Учимся записывать и считывать данные

Из-за строения класса XPacket необходимо хранить бинарные данные для полей. Чтобы установить значение поля, нам потребуется конвертировать имеющиеся данные в массив байтов. Язык C# не предоставляет идеальных способов сделать это, поэтому внутри пакетов будут передаваться только базовые типы: int , double , float и так далее. Так как они имеют фиксированный размер, можно считать его напрямую из памяти.

Читайте также:  Центральная машина компьютерной сети это

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

Источник

Оцените статью
Adblock
detector