Исходник Гайд SNET - Networking Interface

ImPasha

Software Developer & System Administrator
Автор темы
Друг
1,789
2,141

SNET.png


Что такое SNET и для чего его можно использовать
SNET (Simple Network) — модуль сетевого интерфейса, работающий на основе библиотеке LuaSocket и использующий в своей работе протокол UDP (User Datagram Protocol), который чаще всего используется при реализации сетевых игр. Помимо стандартного функционала UDP были добавлены некоторые функции, которые присущи TCP (Transmission Control Protocol), но отсутствуют в UDP. Так, например, SNET имеет функционал, позволяющий убедиться в том, что тот или иной пакет был доставлен до получателя, а также система приоритетов отправки пакетов. Чем выше приоритет, тем больше попыток сделает ваш сервер или клиент, чтобы отправить пакет в случае, если вторая сторона не отчиталась о его получении. Таким образом на модуле SNET можно писать как большие проекты такие как, например, SL:MP - прародитель SNET, именно там впервые была использована эта технология, так и маленькие проекты: чаты, мини-игры по вашему желанию (крестики-нолики, шашки, шахматы по сети) и прочее, что взбредет вам в голову, ведь функционал не ограничен.

Есть ли какие-нибудь ограничения по количеству клиентов
Нет, абсолютно никаких ограничений нет, ибо UDP как протокол не имеет функции подключения. Вы можете присоединить столько клиентов, сколько хотите - все эти операции происходят при помощи обмена пакетами. Простой пример: клиент отправляет пакет с запросом на подключение, сервер проверяет не зарегистрирован ли ещё этот клиент в системе и если нет, то происходит регистрации, а после клиенту отправляется пакет о том, что он зарегистрирован.

Подходит ли модуль для каких-либо площадок кроме MoonLoader'a
Да, SNET без каких-либо проблем запускается при помощи LuaJIT вне игры, а тот, как известно, поддерживается и OC Windows и различными сборками Linux, соответственно можно собрать сервер и поставить его на VPS/VDS - нет никаких ограничений в этом плане. При этом SNET совместим с MoonLoader`ом.

Примеры использования SNET в своих проектах
Ниже представлены две версии кода: один для сервера, второй для клиента - вместе они работают отлично и обмениваются пакетами. Обратите внимание на комментарии, в них содержится полезная информация, которая может вам пригодиться при написании своего клиента и/или сервера.

Client Example:
-- Пример использования SL:NET в качестве клиента
-- Примечание: SLNetLoop рекомендуется выполнять асинхронно, если возможно
-- Можно также выполнять в основном цикле, но возможны некоторые задержки

-- Нужно для корректной работы примера, можно убрать если есть папка с библиотеками
package.path = package.path .. ';../?.lua;../?.luac;../../?.lua;../../?.luac'
package.cpath = package.cpath .. ';../?.dll;../../?.dll'
-- Нужно для корректной работы примера, можно убрать если есть папка с библиотеками

require 'SLNet' -- подключаем модуль SL:NET для дальнейшего использования

netHandle = SLNetInit() -- инициализируем модуль SL:Net, нужно делать при каждой смене сервера
-- то есть, если вы хотите подключиться к другому серверу, сначала инициализируйте SL:NET
netHandle:connect('127.0.0.1', 6666) -- подключаемся к серверу по набору IP:PORT
-- можно также использовать функцию SLNetConnect(netHandle, IP, Port)
netHandle:setPrefix('EXMPL') -- устанавливает проверочный префик в начало пакета
-- сервер и все клиенты должны иметь один и тот же префикс, иначе пакет не обработается
-- можно также использовать функцию SLNetSetPrefix(netHandle, Prefix)


-- в данной callback-функции мы будем обрабатывать приходящие пакеты
function onReceiveData(packetID, bitStream)
  print('New Incoming Message => ' .. bitStream:export())

  if packetID == 1 then -- если нам пришел первый PacketID
    -- собираем те параметры, которые нужно получить от сервера
    local a, b = bitStream:read(UINT8), bitStream:read(STRING, bitStream:read(UINT8))
    print('Message Content: ' .. a .. ', ' .. b)
  end

  local testString = 'Hello SL:NET World!'

  local BSNew = BitStream:new() -- создадим BitStream и запишем в него данные
  BSNew:write(UINT8, 128):write(UINT8, #testString):write(STRING, testString)

  SLNetSend(netHandle, 1, BSNew, 5) -- отправляем сообщение с PacketID 1 и приоритетом 5
  -- приоритет помогает увеличить шанс доставки пакета, по сути он будет отправляться до
  -- тех пор, пока не придет подтверждение получения, а количество повторов зависит от
  -- значения четвертого аргумента, не может быть больше 255 повторений - это лимит
  -- пакеты не имеющие сильного значения лучше отправлять с приоритетом ноль
end

netHandle:setHook(onReceiveData) -- ставим хук на входящие пакеты
-- можно также использовать функцию SLNetSetHook(netHandle, Callback)

SLNetSend(netHandle, 1, nil, 5) -- отправить можно и пустой пакет, зачем - не знаю
while true do
  netHandle:loop() -- обязательно вызывать в цикле, желательно в начале
  -- этот метод отвечает за прогон основных потоков получения и отправки данных
  -- вызывать только после того, как произошел bind/connect, иначе вернет FALSE
  -- метод также имеет зеркальную функцию: SLNetLoop(netHandle)
end
Server Example:
-- Пример использования SL:NET в качестве сервера
-- Примечание: SLNetLoop рекомендуется выполнять асинхронно, если возможно
-- Можно также выполнять в основном цикле, но возможны некоторые задержки

-- Нужно для корректной работы примера, можно убрать если есть папка с библиотеками
package.path = package.path .. ';../?.lua;../?.luac;../../?.lua;../../?.luac'
package.cpath = package.cpath .. ';../?.dll;../../?.dll'
-- Нужно для корректной работы примера, можно убрать если есть папка с библиотеками

require 'SLNet'

netHandle = SLNetInit() -- инициализируем модуль SL:Net, вызывать при каждой смене IP:PORT
netHandle:bind('*', 6666) -- занимаем локальный IP и порт 6666, на него будут подключаться клиеты
-- можно также использовать функцию SLNetBind(netHandle, IP, Port)
netHandle:setPrefix('EXMPL') -- устанавливает проверочный префик в начало пакета
-- сервер и все клиенты должны иметь один и тот же префикс, иначе пакет не обработается
-- можно также использовать функцию SLNetSetPrefix(netHandle, Prefix)

-- в данной callback-функции мы будем обрабатывать приходящие пакеты
function onReceiveData(packetID, bitStream, addr, port)
  print('Message from ' .. addr .. ':' .. port)

  if packetID == 1 then -- если нам пришел первый PacketID
    -- собираем те параметры, которые нужно получить от сервера
    local a, b = bitStream:read(UINT8), bitStream:read(STRING, bitStream:read(UINT8))
    print('Message Content: ' .. a .. ', ' .. b)
  end

  local testString = 'Hello SL:NET World!'

  local BSNew = BitStream:new() -- создадим BitStream и запишем в него данные
  BSNew:write(UINT8, 128):write(UINT8, #testString):write(STRING, testString)

  SLNetSend(netHandle, 1, BSNew, addr, port, 5) -- отправляем сообщение с PacketID 1 и приоритетом 5
  -- приоритет помогает увеличить шанс доставки пакета, по сути он будет отправляться до
  -- тех пор, пока не придет подтверждение получения, а количество повторов зависит от
  -- значения четвертого аргумента, не может быть больше 255 повторений - это лимит
  -- пакеты не имеющие сильного значения лучше отправлять с приоритетом ноль
end

netHandle:setHook(onReceiveData) -- ставим хук на входящие пакеты
-- можно также использовать функцию SLNetSetHook(netHandle, Callback)

while true do
  netHandle:loop() -- обязательно вызывать в цикле, желательно в начале
  -- этот метод отвечает за прогон основных потоков получения и отправки данных
  -- вызывать только после того, как произошел bind/connect, иначе вернет FALSE
  -- метод также имеет зеркальную функцию: SLNetLoop(netHandle)
end

Где можно найти документацию по использованию функций модуля
В данный момент времени документация находится в разработке, позже она будет добавлена в репозиторий на GitHub, где хранится модуль. Весь основной функционал, который есть на данный момент, имеется в коде примерных файлов, все функции прокомментированы, разобраться не так уж и сложно.

Можно ли использовать модуль в первоначальном / измененном виде в коммерческих проектах
Да, можно, но при этом в вашем проекте обязательно должны иметься видимые упоминания об использовании модуля, а также о его разработчике. Запрещается менять название модуля при использовании, пытаться скрыть его использование копируя куски кода или повторяя алгоритмы работы. Использование в бесплатных / некоммерческих проектах этот вопрос также регулируется, должно быть упоминание об использовании SNET.

Где можно скачать последний релиз SNET и как поддержать разработку
SNET и его последний релиз можно
скачать в GitHub Releases, там же можно найти исходный код. Поддержать разработку SNET можно в теме мультиплеера SL:MP - там же находятся способы поддержки проекта, а также другие полезные ссылки, которые могут вам понравится.
 

vladmany

Известный
117
8
Допустим у меня есть 50 клиентов, запущенных на одном компьютере, но под разными прокси. Я могу запустить сервер на том же компьтере и обмениваться пакетами по localhost? И вообще, разумно ли для этого использовать сервер-клиент? В moonloader есть возможности обмениваться данными между процессами gta sa? Понимаю что вопрос не совсем об SL:NET, но думаю вы знаете ответ. Спасибо.
 

ImPasha

Software Developer & System Administrator
Автор темы
Друг
1,789
2,141
Допустим у меня есть 50 клиентов, запущенных на одном компьютере, но под разными прокси. Я могу запустить сервер на том же компьтере и обмениваться пакетами по localhost? И вообще, разумно ли для этого использовать сервер-клиент? В moonloader есть возможности обмениваться данными между процессами gta sa? Понимаю что вопрос не совсем об SL:NET, но думаю вы знаете ответ. Спасибо.
Если локальные клиенты видят сервер, а сервер клиентов, то да - работать будет. Насчет обмена данными между процессами сказать не могу, таких способов не знаю.
 

#Rin

Известный
Всефорумный модератор
1,214
1,043
В moonloader есть возможности обмениваться данными между процессами gta sa?
Нет, во всяком случае из коробки.

Допустим у меня есть 50 клиентов, запущенных на одном компьютере, но под разными прокси. Я могу запустить сервер на том же компьтере и обмениваться пакетами по localhost? И вообще, разумно ли для этого использовать сервер-клиент?
Да, можешь. Обмен данными между процессами называется IPC, но локальный сервер не единственный способ обмена данными, хотя он достаточно гибкий. Эта библиотека впринципе подойдёт, если нет вопроса с производительностью.
 
Последнее редактирование:

f0Re3t

Poh production
Друг
877
812
Что насчет пакетов с превышенным размером MTU UDP?
 

ImPasha

Software Developer & System Administrator
Автор темы
Друг
1,789
2,141
Что насчет пакетов с превышенным размером MTU UDP?
Требует проверки: LuaSocket, насколько мне известно, сам контролирует превышение лимита и последовательно отправляет несколько пакетов. Если же нет, то реализовать можно простой отправкой нескольких пакетов подряд, связывая их каким-нибудь значением, например уникальным идентификатором Acks заглавного сообщения. Но учитывая, что пакеты могут приходить в разном порядке, стоит дополнительно их пронумеровать и сделать проверку на получение всех пакетов, желательно, конечно, ещё добавить определенный timeout на получение всех частей пакета, иначе отказаться от остальных, неполный не нужен.
 
  • Нравится
Реакции: Vintik

ImPasha

Software Developer & System Administrator
Автор темы
Друг
1,789
2,141
Что насчет пакетов с превышенным размером MTU UDP?
Проверил, LuaSocket, а соответственно и SL:NET спокойно принимают и отправляют пакеты размером до 10.000 байт. Пакеты размером больше просто будут отклоняться автоматически как битые, то есть SLNetLoop не будет их возвращать вовсе. Можно будет сделать методом, описанным выше, если будет потребность, но пока я не встречал в практике пакеты такого размера, которые бы превышали 10.000 байт.
 
  • Нравится
Реакции: f0Re3t

ImPasha

Software Developer & System Administrator
Автор темы
Друг
1,789
2,141
Произвели оптимизацию модуля BitStream, обновленную версию всех измененных файлов загрузили на GitHub. Функционально ничем не отличается от того, что в релизной версии, но была изменена механика инициализации новых BitStream-пакетов, а также функции записи и чтения были значительно уменьшены.
 

Tak

Известный
177
70
Попытался сделать клиент / сервер / клиент на данном модуле. (работает xD)
Чтобы когда один клиент спавнил (createObject) объект, этот объект появлялся у других клиентов.

В консоль сервера пишется действие - spawn (в дальнейшем хочу сделать возможность передвижения, вращения, удаления), id объекта (спавним объект с таким же id у других клиентов), хендл объекта (по нему можно удалять объект у того кто спавнил), и позиция объекта по x; y; z (спавним объект по этим координатам у других клиентов)

 

ImPasha

Software Developer & System Administrator
Автор темы
Друг
1,789
2,141
Попытался сделать клиент / сервер / клиент на данном модуле. (работает xD)
Чтобы когда один клиент спавнил (createObject) объект, этот объект появлялся у других клиентов.

В консоль сервера пишется действие - spawn (в дальнейшем хочу сделать возможность передвижения, вращения, удаления), id объекта (спавним объект с таким же id у других клиентов), хендл объекта (по нему можно удалять объект у того кто спавнил), и позиция объекта по x; y; z (спавним объект по этим координатам у других клиентов)

Хотелось бы увидеть какой-нибудь полноценный продукт. Некоторые из идей обозначены в первом посте этой темы: разнообразные мини-игры - шашки, шахматы, карты, крестики-нолики, змейка по сети. Можно сделать какой-нибудь чатик, либо развить идею с редактором объектов.
 
  • Нравится
Реакции: Tak

Tak

Известный
177
70
А вот и чатик подъехал (на SL:NET), в нем нет и половины чего я запланировал (в разработке) + рандомные баги которые нужно будет фиксить. Но вот что уже есть:


 
Последнее редактирование: