WebSocket игнорирует таймаут

ca4tuk

Известный
Автор темы
166
44
Версия MoonLoader
.026-beta
использую либу websocket

есть такой код:
Lua:
local websocket = require 'websocket'
local client = websocket.client.copas({{timeout = 1}})
function main()
    if not isSampLoaded() or not isSampfuncsLoaded() then return end
    while not isSampAvailable() do wait(100) end
    sampAddChatMessage("loaded", -1)
    connected, err = client:connect(ip, 'echo')
    if not connected then
        sampAddChatMessage("bad, err: "..err, -1)
        wait(-1)
    else
        sampAddChatMessage("good", -1)
        local ok = client:send("test")
        local message, opcode = client:receive()
        sampAddChatMessage("answer: "..message, -1)
        client:close()
    end
    while true do
        wait(0)
    end
end

если сервер включен и все окей - все работает как часы, но если сервер выключен - скрипт почему-то не пишет о том что произошла какая-то ошибка.
проведя пару минут понял, что значение таймаута почему-то не работает, скрипт до конца своих лет ожидает ответ от сервера не смотря ни на что.
сам до решения домыслить не могу, поэтому прошу вашей помощи, в идеале таймаут должен выскакивать если сервер не отвечает дольше трёх секунд, цифра 1 там ради теста
 
Решение
Странный выбор на самом деле, проще было использовать более популярную библиотеку LuaSocket, тем более с ней куда больше примеров использования как в качестве сервера, так и в качестве клиента. Судя по всему ты используешь протокол TCP, но предупреждаю, что в случае, если ты собрался использовать его для быстрого обновления данных, то это не самый лучший вариант. Несмотря на то, что это очень надежный протокол, он не является быстрым. Если нужно быстро обновлять информацию и нет необходимости в сохранности 100% пакетов, то лучше использовать UDP, пример использования есть в SNET. Тем более, в случае с UDP отсутствует рукопожатие с сервером как таковое, так что в нём просто нет никакой необходимости.

Lua:
local socket =...

ImPasha

Software Developer & System Administrator
Друг
1,788
2,141
Странный выбор на самом деле, проще было использовать более популярную библиотеку LuaSocket, тем более с ней куда больше примеров использования как в качестве сервера, так и в качестве клиента. Судя по всему ты используешь протокол TCP, но предупреждаю, что в случае, если ты собрался использовать его для быстрого обновления данных, то это не самый лучший вариант. Несмотря на то, что это очень надежный протокол, он не является быстрым. Если нужно быстро обновлять информацию и нет необходимости в сохранности 100% пакетов, то лучше использовать UDP, пример использования есть в SNET. Тем более, в случае с UDP отсутствует рукопожатие с сервером как таковое, так что в нём просто нет никакой необходимости.

Lua:
local socket = require("socket") -- LuaSocket
local host, port = '127.0.0.1', 1306
local UDP = assert(socket.udp())

UDP:settimeout(0) -- Рекомендуется устанавливать алгоритмически, поэтому ноль
UDP:setpeername(host, port) -- Указываем адрес и порт сервера

local last_update = 0 -- Время последнего чтения данных с сервера
-- Это необходимо только в том случае, если мы получаем данные

function get_ms()
  return socket.gettime() * 1000
end

function main()
  while not isSampAvailable() do wait(0) end
  wait(1000)
  UDP:send('Hello world!') -- отправляем серверу сообщение
  while true do
    wait(0)
    -- если прошло более 100 мс с последнего получения данных
    -- то мы запрашиваем пакеты, которые нам отправил сервер
    -- в случае с UDP, этой проверкой вовсе можно пренебречь
    if get_ms() - last_update >= 100 then
      last_update = get_ms() -- записываем новое время
      local data = UDP:receive() -- получаем первый пакет из очереди
      if data ~= nil then
        -- если он есть, то просто выводим его в консоль
        -- это может быть текст, либо, скажем, JSON
        print(data)
      end
    end
  end
end

Для того, чтобы всё это дело протестировать, можно написать небольшой скрипт на Node JS, который будет эмулировать сервер, получающий данные и отправляющий точно такие же данные в ответ. Опять же реализация для UDP, которая совместима с кодом клиента, который выше:

JavaScript:
var UDP = require('dgram')
var server = UDP.createSocket('udp4')

server.on('message', async (msg, info) => {
  try {
    console.log('Data received from server : ' + msg.toString())
    console.log('Received from %s:%d\n', info.address, info.port)
    server.send(msg, info.port, info.address, async (err) => {
      if (err) console.log(err)
    })
  } catch(err) {
    console.log(err)
  }
});

server.on('listening', async () => {
  try {
    var address = server.address();
    var port = address.port;
    console.log('Server is listening at port ' + port);
  } catch(err) {
    console.log(err)
  }
})

server.bind(1306);
 

ca4tuk

Известный
Автор темы
166
44
Странный выбор на самом деле, проще было использовать более популярную библиотеку LuaSocket, тем более с ней куда больше примеров использования как в качестве сервера, так и в качестве клиента. Судя по всему ты используешь протокол TCP, но предупреждаю, что в случае, если ты собрался использовать его для быстрого обновления данных, то это не самый лучший вариант. Несмотря на то, что это очень надежный протокол, он не является быстрым. Если нужно быстро обновлять информацию и нет необходимости в сохранности 100% пакетов, то лучше использовать UDP, пример использования есть в SNET. Тем более, в случае с UDP отсутствует рукопожатие с сервером как таковое, так что в нём просто нет никакой необходимости.

Lua:
local socket = require("socket") -- LuaSocket
local host, port = '127.0.0.1', 1306
local UDP = assert(socket.udp())

UDP:settimeout(0) -- Рекомендуется устанавливать алгоритмически, поэтому ноль
UDP:setpeername(host, port) -- Указываем адрес и порт сервера

local last_update = 0 -- Время последнего чтения данных с сервера
-- Это необходимо только в том случае, если мы получаем данные

function get_ms()
  return socket.gettime() * 1000
end

function main()
  while not isSampAvailable() do wait(0) end
  wait(1000)
  UDP:send('Hello world!') -- отправляем серверу сообщение
  while true do
    wait(0)
    -- если прошло более 100 мс с последнего получения данных
    -- то мы запрашиваем пакеты, которые нам отправил сервер
    -- в случае с UDP, этой проверкой вовсе можно пренебречь
    if get_ms() - last_update >= 100 then
      last_update = get_ms() -- записываем новое время
      local data = UDP:receive() -- получаем первый пакет из очереди
      if data ~= nil then
        -- если он есть, то просто выводим его в консоль
        -- это может быть текст, либо, скажем, JSON
        print(data)
      end
    end
  end
end

Для того, чтобы всё это дело протестировать, можно написать небольшой скрипт на Node JS, который будет эмулировать сервер, получающий данные и отправляющий точно такие же данные в ответ. Опять же реализация для UDP, которая совместима с кодом клиента, который выше:

JavaScript:
var UDP = require('dgram')
var server = UDP.createSocket('udp4')

server.on('message', async (msg, info) => {
  try {
    console.log('Data received from server : ' + msg.toString())
    console.log('Received from %s:%d\n', info.address, info.port)
    server.send(msg, info.port, info.address, async (err) => {
      if (err) console.log(err)
    })
  } catch(err) {
    console.log(err)
  }
});

server.on('listening', async () => {
  try {
    var address = server.address();
    var port = address.port;
    console.log('Server is listening at port ' + port);
  } catch(err) {
    console.log(err)
  }
})

server.bind(1306);
вообще, ты наверное догадался что это нужно для мапы которую мы делаем, поэтому вариант с потерей данных не подходит.
нужно что-то быстрое и при этом сохранное, есть идеи?
 

ImPasha

Software Developer & System Administrator
Друг
1,788
2,141
вообще, ты наверное догадался что это нужно для мапы которую мы делаем, поэтому вариант с потерей данных не подходит.
нужно что-то быстрое и при этом сохранное, есть идеи?
Да, догадался, поэтому и предложил UDP, так как он как раз очень хорошо подходит. Если вы хотите плавную карту, то вам нужен очень быстрый обмен данными, а в этом случае потеря некоторых пакетов не страшна, ведь уже буквально через миллисекунду прилетит новый пакет, который восполнит потерянный, в этом случае главное не делать критических пакетов, потеря которых невосполнима, но в случае с обновлением карты такие вряд ли будут.

На примере с моим мультиплеером на Lua для GTA:SA могу сказать, что UDP очень хорошо себя показывают в этом деле. Наш UDP сервер выдерживал до 100 человек, которые должны были синхронизироваться друг с другом буквально за несколько миллисекунд, и это не считая то, что мы ещё синхронизировали объекты и транспорт. А вообще надежность доставки пакетов можно гарантировать и через UDP построив над UDP свой уровень с системой валидации, которую использует TCP, по сути это просто обратная связь, где клиент подтверждает серверу, что получил его пакет, а если подтверждение не приходит, то сервер повторно отправляет тот же самый пакет, и наоборот соответственно. Но, опять же, для вашей карты такое вряд ли понадобится, ибо там по идее не должно быть никаких критических пакетов, потеря которая невосполнима.
 
  • Нравится
Реакции: whyega52

ca4tuk

Известный
Автор темы
166
44
Да, догадался, поэтому и предложил UDP, так как он как раз очень хорошо подходит. Если вы хотите плавную карту, то вам нужен очень быстрый обмен данными, а в этом случае потеря некоторых пакетов не страшна, ведь уже буквально через миллисекунду прилетит новый пакет, который восполнит потерянный, в этом случае главное не делать критических пакетов, потеря которых невосполнима, но в случае с обновлением карты такие вряд ли будут.

На примере с моим мультиплеером на Lua для GTA:SA могу сказать, что UDP очень хорошо себя показывают в этом деле. Наш UDP сервер выдерживал до 100 человек, которые должны были синхронизироваться друг с другом буквально за несколько миллисекунд, и это не считая то, что мы ещё синхронизировали объекты и транспорт. А вообще надежность доставки пакетов можно гарантировать и через UDP построив над UDP свой уровень с системой валидации, которую использует TCP, по сути это просто обратная связь, где клиент подтверждает серверу, что получил его пакет, а если подтверждение не приходит, то сервер повторно отправляет тот же самый пакет, и наоборот соответственно. Но, опять же, для вашей карты такое вряд ли понадобится, ибо там по идее не должно быть никаких критических пакетов, потеря которая невосполнима.
да, ты наверное прав, я не подумал о том что потери важны только при отправке критически важной информации.
под потерями ты имеешь ввиду статистическое или единоразовое раз в 10 лет неполучение пакета?

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

ImPasha

Software Developer & System Administrator
Друг
1,788
2,141
да, ты наверное прав, я не подумал о том что потери важны только при отправке критически важной информации.
под потерями ты имеешь ввиду статистическое или единоразовое раз в 10 лет неполучение пакета?

мы думаем сделать систему комнат дабы не загромождать карту ненужными людьми, при этом все ещё поддерживая идею "общей" карты, поэтому скорее всего у нас будет сделана мини-авторизация как раз таки требующая для передачи критически важную информацию
Для критической информации, которая передаётся не так часто, вы по прежнему можете использовать либо HTTP протокол, как и раньше, либо TCP протокол, ведь вряд ли отправка критической информации осуществляется очень часто. Я бы скорее предпочел HTTP, ведь это в разы удобнее, чем TCP.

А конкретно для обновления данных карты, которые вы раньше отправляли через HTTP протокол, уже можно будет использовать UDP. Например, сначала скрипт может проходить авторизацию через HTTP, получать какой-нибудь секретный токен и передавать его уже через UDP, чтобы сервер понял, что отправляются валидные пакеты, а не просто мусор. Этого будет достаточно, чтобы обеспечить безопасность комнаты и ограничить возможность других пользователей в отправке данных в чужие комнаты и помехе работы со скриптом другим пользователям.

А насчёт потери пакетов в UDP, это скорее статистическое, но в целом всё зависит от загруженности канала. Как показывала практика на примере того же мультиплеера, речь идёт о потере около 3% пакетов за всё время работы, что в общей сумме не такое уж и большое количество.
 
  • Нравится
Реакции: whyega52 и kru_tin

ca4tuk

Известный
Автор темы
166
44
Для критической информации, которая передаётся не так часто, вы по прежнему можете использовать либо HTTP протокол, как и раньше, либо TCP протокол, ведь вряд ли отправка критической информации осуществляется очень часто. Я бы скорее предпочел HTTP, ведь это в разы удобнее, чем TCP.

А конкретно для обновления данных карты, которые вы раньше отправляли через HTTP протокол, уже можно будет использовать UDP. Например, сначала скрипт может проходить авторизацию через HTTP, получать какой-нибудь секретный токен и передавать его уже через UDP, чтобы сервер понял, что отправляются валидные пакеты, а не просто мусор. Этого будет достаточно, чтобы обеспечить безопасность комнаты и ограничить возможность других пользователей в отправке данных в чужие комнаты и помехе работы со скриптом другим пользователям.

А насчёт потери пакетов в UDP, это скорее статистическое, но в целом всё зависит от загруженности канала. Как показывала практика на примере того же мультиплеера, речь идёт о потере около 3% пакетов за всё время работы, что в общей сумме не такое уж и большое количество.
понял, спасибо, идея с использованием HTTP в критических ситуациях хорошая.
но, не хотелось-бы в открытую давать юзеру инфу о том ,куда летит запрос (50% сидящих здесь знают про дебаггеры и скорее всего сразу полезут смотреть шо куда летит, лишь бы сломать).
а шифровать поток данных тоже неверно, это может нагнать эти 50% юзеров в недоверие к скрипту (ууу стиллеры бебебе).
скорее всего остановлюсь на TCP.

ты ебейше шаришь, поэтому у меня соответствующий вопрос - трафик идущий через сокеты можно полностью скрыть, или я хуйню несу какую-то?
 

ImPasha

Software Developer & System Administrator
Друг
1,788
2,141
понял, спасибо, идея с использованием HTTP в критических ситуациях хорошая.
но, не хотелось-бы в открытую давать юзеру инфу о том ,куда летит запрос (50% сидящих здесь знают про дебаггеры и скорее всего сразу полезут смотреть шо куда летит, лишь бы сломать).
а шифровать поток данных тоже неверно, это может нагнать эти 50% юзеров в недоверие к скрипту (ууу стиллеры бебебе).
скорее всего остановлюсь на TCP.

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

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

- Клиент активирует скрипт, скрипт обращается к HTTP серверу и получает ID своей комнаты и секретный токен
- Сервер запоминает набор ID комнаты и секретный токен, при помощи ID можно посмотреть карту
- Секретный токен же в свою очередь необходим только для отправки данных
- Когда скрипт отправляет данные о карте, он включает в себя секретный токен, сервер, получивший данные, сверяет секретный токен с ID карты, и если всё сходится, обновляет данные в определенной комнате
- Когда пользователь через веб-интерфейс запрашивает карту, он вводит уникальный ID своей карты, только так он сможет её посмотреть, плюс, если он захочет, он сможет отправить ID карты другим людям, чтобы они могли смотреть карту, но благодаря секретному токену они не смогут менять карту

Как вариант, можно использовать защищенный веб-сокет, то есть WSS, чтобы зашифровать трафик именно между сервером и веб-клиентом. Но по сути не имея доступа к серверу и не будучи интернет-провайдеров будет практически невозможно перехватить трафик, только если это не будет публичный Wi-Fi, либо если на компьютере или сервере не будут сидеть какие-нибудь вирусы, которые будут перехватывать трафик. Но это опять же можно сделать только со стороны клиента, либо сервера, третьи лица ничего сделать не смогут, так что клиент сможет "взломать" только по сути свою карту
 

ImPasha

Software Developer & System Administrator
Друг
1,788
2,141
Вот мои наработки по переводу вашей карты на сокеты, возможно поможет в вашей разработке. Связь между скриптом и сервером осуществляется по UDP через порт 1306, а между сервером и веб-клиентом через WebSocket через порт 1307. Карта работает очень плавно, так что это отличный вариант.

Понятное дело, что нет никаких комнат, аутентификаций через HTTP и прочего. Просто то, что было у вас, но на сокетах.
 

Вложения

  • socket.zip
    2.6 MB · Просмотры: 18
  • Нравится
Реакции: whyega52 и ca4tuk

ca4tuk

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

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

- Клиент активирует скрипт, скрипт обращается к HTTP серверу и получает ID своей комнаты и секретный токен
- Сервер запоминает набор ID комнаты и секретный токен, при помощи ID можно посмотреть карту
- Секретный токен же в свою очередь необходим только для отправки данных
- Когда скрипт отправляет данные о карте, он включает в себя секретный токен, сервер, получивший данные, сверяет секретный токен с ID карты, и если всё сходится, обновляет данные в определенной комнате
- Когда пользователь через веб-интерфейс запрашивает карту, он вводит уникальный ID своей карты, только так он сможет её посмотреть, плюс, если он захочет, он сможет отправить ID карты другим людям, чтобы они могли смотреть карту, но благодаря секретному токену они не смогут менять карту

Как вариант, можно использовать защищенный веб-сокет, то есть WSS, чтобы зашифровать трафик именно между сервером и веб-клиентом. Но по сути не имея доступа к серверу и не будучи интернет-провайдеров будет практически невозможно перехватить трафик, только если это не будет публичный Wi-Fi, либо если на компьютере или сервере не будут сидеть какие-нибудь вирусы, которые будут перехватывать трафик. Но это опять же можно сделать только со стороны клиента, либо сервера, третьи лица ничего сделать не смогут, так что клиент сможет "взломать" только по сути свою карту
тут скорее не проблема в том что могут "посмотреть куда идёт", а в том что увидят что конкретно передается и попробуют что-то сломать.
да, если сломают - вина полностью будет наша, но и пихать "защиту на дурака" на каждый метод дело такое себе, поэтому хотелось-бы скрыть не поток информации, а саму информацию как таковую любым способом и в любой вид.

в остальном наша логика такая-же, как ты и описал пошагово, а насчет wss и т.п - знал-бы как и что лучше юзать - сделал-бы.
заметку про использование LuaSocket возьму на глаз, возможно попробую использовать эту штуку, мне её чапо и кидал вроде-бы когда я у него за это спросил, спасибо

в целом, все шо хотел спросить и узнать - спросил и узнал, ещё раз спасибо, больше вопросов нет 😘
 

ImPasha

Software Developer & System Administrator
Друг
1,788
2,141
тут скорее не проблема в том что могут "посмотреть куда идёт", а в том что увидят что конкретно передается и попробуют что-то сломать.
да, если сломают - вина полностью будет наша, но и пихать "защиту на дурака" на каждый метод дело такое себе, поэтому хотелось-бы скрыть не поток информации, а саму информацию как таковую любым способом и в любой вид.
Это просто скорее бесполезно, чем невозможно. Если не сделать защиту со стороны сервера, то трафик в любом случае вскроют и начнут перебирать варианты обхода защиты сервера. Поэтому проще бросить усилия на защиту со стороны сервера, нежели на попытки зашифровать трафик. В любом случае, любой вид шифрования, который будет зашит в вашем скрипте, вскроют и в обратном направлении вернут всё в исходный вид. Могу на личном примере сказать, что как сильно бы тот же MVDHelper не пытался шифровать свой трафик внутри клиента, пока они не сделали защиту на сервере, я каждое обновление кошмарил их просто декомпилируя скрипт и разбирая метод шифрования. Вообще у меня не было желания что-то сломать просто так, но я был крайне не согласен с их политикой платить за всё в бесплатном скрипте, поэтому я просто заставлял их отрабатывать те деньги, которые они получают.

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