Работа с сетью на c++ как раз, два, три.
network n = OpenNetwork();
connection c = n.OpenConnection("google.com", 80);
html_request h("GET", "/");
c << h;
c.WaitForMessage();
n.Response();
std::cout << c;
Добрый день дорогие читатели, cегодня я хочу рассказать вам о моей работе за последние 4 года. О моем творении, о моей жизни. Дело пойдет о многофункциональной библиотеке по работе с сетью, о ее истории и причинах создания, о решаемых задачах а так же о внутреннем устройстве.
Хочу сразу оговориться, возможно моя работа и является велосипедом, но ни на момент создания, ни на момент перехода на новые версии найти удовлетворяющие меня аналоги я так и не смог.
История
Начало
Все началось в далеком 2009 году, летом. Мы только-только прошли 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.Pass(5); // запрос начинается host:pass, не усложняем
connection nc = c.GetNetwork().OpenConnection(s);
nc << m;
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 программа заблокируется до прихода или по таймауту), но суть ясна.
Возможности
- Никаких ограничений на протокол
- Поддержка шифрования, сжатия
- Максимально точная синхронизация
- Внесение ваших обработчиков(шифрования) на уровне сообщения, пакета. Полностью изменяемый 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 и прочее. Вобщем работа все еще ведется.
Здесь я появился не для того что бы пропиариться, а узнать, существуют ли аналоги(хотелось бы спереть пару идей посмотреть как у людей), нужно ли это кому либо(имеет смысл выходить на рынок) и узнать, чего бы вам не хватало в подобной системе.
Публичные извинения
Прошу простить за мою манеру изложения, ошибки и неточности. Мне не удалось добавить тег «я пиарюсь» в связи с низкой кармой. Да и вообще зря я наверное это.
Автор: Offenso