[RGame Engine] Сетевой протокол движка
roxblnfkКопия моей статьи с ныне мёртвого develnet.ru
В начало
Здесь под сетевым протоколом понимается не TCP, а класс, входящий в состав сетевого кода движка.
Рассматриваемые классы rxnetcl
и rxnetsv
являются расширениями классов rxnetcllw
и rxnetsvlw
(классы, упрощающие и реализующие работу с сокетами на "низком" уровне) до сетевого протокола, которым пользуются клиент и сервер.
Главная особенность заключается в том, что всё это работает на неблокирующих сокетах. Это можно назвать достоинством, вызывающим множество проблем и сложностей :)
- Расшифровывает "сырые" (бинарные) данные.
- Шифрует данные для передачи (собирает пакеты данных[*]).
- Следит за наличием соединения между клиентом и сервером. Пока факт разъединения пользователя и сервера определяется двумя способами:
- автоматическое пингование при "простое" соединения. Если одна из сторон не присылает данные через определённое время (опция "TimeLimit"), то соединение считается разорванным. Значение опции "PingMaxTime" должно быть меньше в 2-3 раза значения опции "TimeLimit" для нормальной работы пинга (при почти равных значениях пингование будет производиться, но оно будет бессмысленным; при большем же значении функционал этот вообще работать не будет — соединение разорвётся до того, как настанет время начала пинга).
- ошибка при попытке отправить данные второй стороне как бы намекает, что соединение также разорвано (в этом месте кода пока есть "преведы": rxnetcllw/rxnetsvlw функция lw_send).
При обнаружении разъединения вызывается функция disconnect()
, передающая информацию об этом событии движку на функцию onDisconnect()
.
Недостатки:
- При пинговании (да и вообще) задержка между пакетами не считается. (исправимо)
- Нет шифрования данных. (исправимо)
- Нет сжатия не бинарных данных. (исправимо, функции
pack()
,unpack()
)
[*] Заметки подраздела:
- Пакеты данных
Каждый пакет состоит из двух основных частей: команды (заголовка) и контента.
Контент контент содержит информацию любого из следующих типов:array
/integer
/float(real)
/string
/boolean
/object
/null
(новые типы данных добавляйте в функцииpack()
,unpack()
).
Команда - целочисленное значение, позволяющее определить предназначение контента.
Каждый пакет начинается с одних и тех же двух байт, которые заданы в переменной FB
. Значения переменной FB
можно менять, но они должны совпадать на клиенте и сервере!
Нужно расписывать переменные класса и функции? Думаю по коду и так понятно :Р
Ответы на вопросы
Acid-UA
Интересует вопрос, почему команда должна быть численным значением? Ведь команда в виде слова гораздо понятнее, в числах можно и запутаться?
При проектировании протокола я ставил упор на минимизацию скорости обработки и минимизацию трафика
Чтобы понять, почему число, давайте посмотрим на структуру протокола по байтам.
Вот комментарий к функции pack()
рассматриваемого класса:
// | 2B head | 4B command | 4B type | 4B content length / value | [0-2GB content :)] | <= 14 байт минимум
- первые
2 байта
- берутся из переменной$FB
, знаменуют начало нового пакета. Если они не совпадают, то, значит, произошла рассинхронизация, либо используется чужеродный протокол — повод разорвать соединение. - далее идут фиксировано
4 байта
- команда - потом
4 байта
(можно было бы и 2 байта взять или даже 1) - способ упаковки контента 4 байта
, указывающие на длину контента, который идёт следом. Иногда эти 4 байта и есть контент (к примеру bool или int)-
0-2GiB
- контент.
У нас используются 32-х битные числа, поэтому они как-раз умещаются в 4 байта (1 байт = 8 бит, 4*8=32 бита) функцией php pack()
.
Если команда будет строкой, то эти 4 байта пункта 2. выглядели бы как:
2.1 сначала 1-4 байта, указывающие на длину строковой команды
2.2 не сжатая строка - команда
Как не крути, строка займёт больше места. На обработку числа уйдёт на порядок меньше времени, чем на строку, да и меньшим кодом. Разница, конечно, очень маленькая, но пакетов по сети будет идти очень много.
Что касается удобства восприятия - оставьте комментарий напротив числа, или можете использовать константы для этого: define('cc_USER_DEAD', 123);
Вы эту числовую команду должны использовать минимум раз: при обработке принятого пакета и при отправке. Если номер команды встречается где-то ещё, то, возможно, вы организовали код не правильно!
Также ознакомьтесь с системой отправки событий в теме Расширения движка, обработчики команд и событий — эвенты отправляются со строковым описанием…