Skip to content

SL:NET Basics (Russian)

Pavel Akulichev edited this page Sep 28, 2020 · 5 revisions

Основы SL:NET

В данном материале представлены основные аспекты работы с модулем SL:NET, а также некоторые пояснения, которые помогут разработчикам разобраться в сути работы данной библиотеки и без каких-либо трудностей использовать её в своих проектах, а также для разработки стронных продуктов.

Начать стоит с того, что SL:NET - это модуль, который основан на протоколе UDP (User Datagram Protocol). UDP как раз тот протокол, который чаще всего используется при создании продуктов, в которых требуется скорость обработки информации. UDP не имеет схем соединения клиентов с сервером и сервера с клиентами, происходит холостой выброс пакетов с данными в заданном направлении, а поймает его получатель или нет - неизвестно.

Основываясь на этих данных, можно сказать, что первое, что нужно разработать - систему подключения для клиентов и для сервера. Нам нужно понимать и чётко осознавать подключен тот или иной клиент к серверу или нет, но как же это сделать? На самом деле ничего сложного нет, каждый клиент имеет уникальную пару идентификационных данных, повториться которая не может - речь идет про IP и порт, которые клиент использует для получения ответов от сервера. Имея информацию о том, что эти данные уникальные, мы можем использовать их в качестве проверочных при получении и отправке пакетов. Давайте на примере разберемся как можно реализовать такую систему сохранения клиентов в памяти сервера для дальнейшего взаимодействия с тем или иным клиентом.

local Clients = {}

function onReceivePackets(packet_id, bitstream, address, port)
  if packet_id == 1 then
    -- мы будем считать, что PacketID 1 будет нужен для подключения
    for i, v in ipairs(Clients) do
      if v.address == address and v.port == port then
        return -- отклоняем пакет, потому что такой клиент уже есть 
      end
    end
    -- добавляем клиента
    table.insert(Clients, {
      address = address,
      port = port
    })
  end
end

Этим простым примером мы сделали возможным подключение клиентов, этот пример можно усовершенствовать, добавив отправку ответного пакета с информацией о том было подключение успешным или нет, вносить в таблицу с клиентами не только IP и порт, а ещё и другие данные, но это вы сделаете сами.

Но для того, чтобы наш сервер начал функционировать, нам нужно инициализировать SL:NET и задать ему параметры, присущие серверу. Для этого в SL:NET используется специальная функция SLNetBind, либо метод bind для объекта подключения, получаемого функцией SLNetInit. Инициализацию необязательно производить сразу при старте скрипта, это можно сделать абсолютно в любой момент. В нашем же случае мы сделаем это внутри глобальной функции.

function onReceivePackets(packet_id, bitstream, address, port)
 -- любые действия с входящими пакетами
end

function netInit()
  netHandle = SLNetInit()
  netHandle:bind('*', 4334)
  netHandle:setPrefix('TEST')
  netHandle:setHook(onReceivePackets)
end

Функция netInit теперь может быть вызвана в любом месте кода, но давайте же подробно рассмотрим что делает каждая из использованных мною функций и методов. SLNetInit создает объект подключения, который в дальнейшем будет заполнен либо под сервер, либо под клиент. Этот объект нам будет нужен при вызове почти любой функции SL:NET. Метод bind, как уже было сказано, используется для установки набора IP:PORT для сервера. Первым аргументом был передан специальный символ *, который обозначает, что IP адрес сервера будет выбран компьютером автоматически. Если у вас несколько сетевых адаптеров, то рекомендуется указывать маску подсети вручную, чтобы был использован корректный IP-адрес. Метод setPrefix устанавливает префикс для входящих и исходящих пакетов, это нужно для того, чтобы лишние пакеты, отправленные с других скриптов не обрабатывались вашим сервером или клиентом. Последний метод с названием setHook принимает функцию, которая будет вызываться при получении входящих пакетов, обработать которые можно будет в дальнейшем.

Чтобы сделать подключение к серверу со стороны клиента, нужно немного изменить код, а именно заменить метод bind на метод connect и вместо специального символа * передавать IP-адрес сервера, к которому вы хотите подключиться - внешний, либо локальный в случае, если сервер в вашей подсети. При получении неизвестных ошибок об отключенном SOCKET-объекте рекомендуется проверить введенные данные, возможно они заданы некорректно.

function netInit()
  netHandle = SLNetInit()
  netHandle:connect('127.0.0.1', 4334)
  netHandle:setPrefix('TEST')
  netHandle:setHook(onReceivePackets)
end

Чтобы оборвать соединение с сервером недостаточно удалить переменную netHandle это лишь приведет к вылету скрипта с ошибкой, ибо функция SLNetLoop, которая используется в бесконечном цикле, попытается выполнить неизвестную инструкцию из переменной, которая была удалена - не делайте так!

Для безопасного отключения необходимо просто пересоздать переменную повторно использовав SLNetInit, но уже не устанавливая новые параметры в виде набора IP-адреса и порта. Также вы можете удалить переменную в том случае, если в функции SLNetLoop и других стоит проверка на её существование.

Функцию SLNetLoop необходимо использовать в бесконечном цикле скрипта, эта функция отвечает за последовательный вызов функций отправки и получения пакетов. Без этой функции пакеты не будут отправляться и приниматься, соответственно модуль не сможет быть использован в полной мере.

while true do
  netHandle:loop()
end

Чтобы отправлять пакеты используется функция SLNetSend, всего у этой функции две перегрузки - для сервера и для клиента. Различие лишь в наличие двух предпоследних аргументов - IP-адрес и порт получателя. В клиенте же передаются только эти аргументы: PacketID, bitStream, Priority. Соответственно серверная версия этой функции будет получать дополнительные аргументы, в общем выглядит так: PacketID, bitStream, Address, Port, Priority.

Подробнее о том, как использовать SL:NET BitStream можно почитать на другой странице SL:NET Wiki.

Clone this wiki locally