Как стать автором
Обновить

Transcend Library 4: Введение

Время на прочтение 6 мин
Количество просмотров 4K
Работа с сетью на c++ как раз, два, три.
network n = OpenNetwork();
connection c = n.OpenConnection("habrahabr.ru", 80);
html_request h("GET", "/");
c << h;
c.WaitForMessage();
n.Response();
std::cout << c;



Добрый день дорогие хабравчане, cегодня я хочу рассказать вам о моей работе за последние 4 года. О моем творении, о моей жизни. Дело пойдет о многофункциональной библиотеке по работе с сетью, о ее истории и причинах создания, о решаемых задачах а так же о внутреннем устройстве.

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

История


Начало

Все началось в далеком 2008 году, летом. Мы только-только прошли winsock, пытались делать свой первый сервер. Учитель мне попался неважный, ибо ничего толком понятно не былоЯ был дураком, и ничего не понял.
Еще меня крайне расстраивал неудобный апи, и сразу захотелось все это дело инкапсулировать. Я тогда не знал с++, и первая версия была просто переименованием функций(сейчас утрачена).

Все больше и больше я отдалялся от родной winsock схемы, делая прозрачнее общение. (К примеру при вызове функции connect, сразу создавался сокет, делался запрос dns, и открывалось соединение). Постепенно это стало настолько удобным, что я решил попробовать сделать игру.

Ancient Legend

Как бы то ни было, до версии TL2 никаких существенных улучшений не происходило, и сама «библиотека» имела дай бог 300 строк кода.
С появлением реальной задачи возникла проблема — передача нескольких данных в одном соединении(чат, текстура, музыка). Именно с этого и начался сам прогресс. На протяжении полугода я решал эту проблему, подбирал оптимальное разбиение пакета, формат служебных сообщений, делал тесты. В итоге библиотека успешно справилась с задачей, с поддержкой одновременной передачи данных до 255 «сообщений» одновременно, с максимальным размером 4гига каждое.
К сожалению игру мы так и не написали в связи с утечкой кадров, альтруисты ведь. Да и конкуренты появились. Поэтому я смог уедениться в разработке следующих версий.

TL4

Следующей итерацией был переход на C++ и добавление новых стандартов, применение новых фишек, новых возможностей.
Теперь библиотека полноценно стала работать в двух режимах одновременно — сервер и клиент, позволяя решать задачи proxy или p2p. Максимальный размер сообщения увеличился до бесконечности, а максимальное количество одновременно передаваемых до 2^32(при этом активно-передаваемыми осталось 255). Добавились функции авто-реконнекта, появился приоритет для передачи сообщений.
Библиотека стала асинхронной, с неблокирующими сокетами, снизив нагрузку на основной поток в разы. (К примеру стандартно открытие соединения у меня занимало 443 ms в TL2, это число удалось уменьшить до 17 ms TL4).
Добавилось шифрование, сжатие и прочее прочее, список можно продолждать долго.

Основные решаемые задачи


Библиотека позволяет, не взирая на платформу сделать следующее:
Клиенту — открыть соединение, и передать любой набор данных из любых источников(фаил, массив, другое соединение) и быть уверенным, сколько бы раз соединение не прервалось, данные будут доставлены.
Серверу — слушать порт и всегда, интуитивно понятно иметь информацию о количестве соединений, траффике на каждом соединении, время в онлайне, среднюю скорость и прочее.

Пример «PROXY»


#include <tl4/def.h>

using namespace ax::tl4;

bool proxy( connection &c, message &m )
{
  std::string s;
  s << m;  // запрос начинается host:pass, не усложняем
  connection nc = c.GetNetwork().OpenConnection(s);
  nc << s;
  nc << c;
  nc >> c;
  return true;
}

void main()
{
  network n = OpenNetwork();
  port p = n.OpenPort(8080);

  p.NonTL4Compatible(true);
  p.OnOpenConnection(proxy);
  while (1)
    n.Response();
}

Конечно пример наигран, без обработки ошибок, да и вообще кривой(при попытке преобразования message в std::string программа заблокируется до прихода \0 или по таймауту), но суть ясна.

Возможности


  • Никаких ограничений на протокол
  • Поддержка шифрования, сжатия
  • Максимально точная синхронизация
  • Внесение ваших обработчиков(шифрования) на уровне сообщения, пакета. Полностью изменяемый packet construction pipe.
  • Обьединение соединений в «виртуальные сети»(группы, обрабатываемые за «один присест»). Все общения между сетями потоко-безопасны.
  • Передача до 4миллиардов обьектов одновременно через одно соединение.
  • Настраиваемые параметры соединения, программное ограничение скорости и подобное.
  • Настраиваемые обработчики всех событий для каждого соединения или группы.
  • Приоритеты передачи обьектов, полное управление тем «что именно будет сейчас переданно».
  • Возможность передачи не полностью имеющихся обьектов — к примеру торрент, поток интернет-радио. Без ограничения на размер.
  • Возможность обработки «нового обьекта», «нового куска обьекта», «завершение скачивание обьекта», «каждые N байт», «каждые N секунд».
  • Разработка протокола в одном месте.


Протокол-в-одном-месте

Это очень удобная вещь, позволяющая разрабатывать клиент-серверные приложения не дублируя код.
Небольшое введение. Для каждого обьекта и каждого пакета вызывается очередь обработчиков(на лету ее менять нельзя по понятным причинам). У обработчика есть два метода — Encode и Decode. Таким образом код шифровки/расшифровки, сжатия/разжатия всегда в одном месте. Это позволяет проверить корректность обработчика — достаточно вызвать метод Test, тот на случайных или предоставляемых данных проверит что после шифровки и расшифровки получился исходный вариант.
Тоже самое и в вашем протоколе — для протокола в целом существуют те же методы, позволяющие преобразовать обьект в сообщение и сообщение в обьект.

Как это работает


Stream

Все в мире программирования можно представить через последовательность. Массив, строка, фаил это все последовательность байт. Тоже про переменную и входящее-исходящее соединение.
Система классов stream была создана для того что бы абстрагироваться от физического источника потока и абстрагироваться от преобразований реально происходящих над информацией.
Чем то схоже с stl::stream но с некоторыми улучшениями.

Pipe

Представим простую задачу, мы хотим передать картинку с диска. Для этого ее нужно загрузить, переконвертировать bmp->jpg, зашифровать. Теперь это можно сделать просто поставив обработчики один за другим, и сказав что это атомарное преобразование(анология с водопроводом — трубы идут стык в стык).
pipe *ImageTransferPipe( std::string filename )
{
  pipe_constructor c;
  c.add(new BMP2JPG());
  c.add(new BlowFishCrypt());
  pipe p(c);
  return p.GetScheme().Source(new read_file_stream(filename));
}

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

Шифрование из-коробки

Как вы уже догадались, при создании соединения имеется единственно логичный функционал:
connection::SetHandler( pipe * )

Каждый байт при отправке пройдет через этот обработчик, и каждый байт при приеме также, пройдет через соответсвующий обработчик(pipe::GetInverseScheme).

Передача нескольких обьектов одновременно

Идея до боли проста, я надеюсь она пригодится тем кому нужна, и кто еще не сделал.
Все обьекты в очереди на отправку шинкуются по 255 байт, и посылаются именно этими блоками. Блоки снабжаются id обьекта. Обьекты передаются циклично — первый второй третий первый второй третий.
Замечу, что блоки всегда 255 байт, и не снабжаются размером. Если вдруг блок меньше, то через служебные сообщения это отрегулируется.
Если вдруг следующая порция обьекта «не готова»(нет данных), то он перемещается в очередь неактивных, взамен на один готовый. Все это регулируется через системные сообщения.

Системные сообщения

Они несут несколько функций. Передающая сторона сообщает о ротациях активный-неактивный, предупреждает о приближении конца обьекта, сообщает о добавлении обьектов, передача «низкоуровневых» исключений, передача формата pipe для обьекта и самое интересное — синхронизация.
Синхронизация машин

Для корректной работы должна быть включена на двух концах. Именно изза этой задачи пришлось реализовывать приоритет обьектов, необходимо что бы эти служебные сообщения добавлялись в очередь в момент создания.
Хосты обмениваются своими timestamp с миллисекундами, таким образом получая ping. После установки среднего значения, начинают обмениваться и им тоже. В результате обе машины имеют время отклика.
Самое интересное начинается в серверном решении. Меня поймут игроки с пингом больше пары сотен. Противник на несколько миллисекунд раньше узнает что то важное, и это решает все.
А теперь представим игры где события приходят на все машины одновременно, и делается это просто. При отправке сообщения «concurrent», соединения сортируются в порядке задержек, и фактическая отправка данных происходит так, что бы расчетное время прибытия было равным, с учетом стабильности соединения(погрешность меньшая 10ms для проводных решений).

Зачем этот пост?


Дело в том что библиотека не завершена. К примеру не все запланированные параметры соединения поддерживаются, хотелось бы алгоритмы шифрования из коробки, check sum и прочее. Вобщем работа все еще ведется.
Здесь я появился не для того что бы пропиариться, а узнать, существуют ли аналоги(хотелось бы спереть пару идей посмотреть как у людей), нужно ли это кому либо(имеет смысл выходить на рынок) и узнать, чего бы вам не хватало в подобной системе.

Публичные извинения

Прошу простить за мою манеру изложения, ошибки и неточности. Мне не удалось добавить тег «я пиарюсь» в связи с низкой кармой. Да и вообще зря я наверное это.

UPD: Продолжение
Теги:
Хабы:
+22
Комментарии 29
Комментарии Комментарии 29

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн