Lua sockets - Асинхронные события
В текущей реализации сокетов lua я вижу, что мы должны установить таймер, который периодически перезванивает, чтобы мы проверяли неблокирующий API, чтобы увидеть, получили ли мы что-нибудь.
Это все хорошо и хорошо, однако в случае UDP, если отправитель получает много информации, мы рискуем потерять данные. Скажем, другое устройство отправляет фотографию размером 2 МБ по протоколу UDP, и мы проверяем получение сокета каждые 100 мсек. При скорости 2 Мбит / с базовая система должна хранить 200 Кбит до того, как наш вызов запросит базовый стек TCP.
Есть ли способ вызвать событие, когда мы получаем данные в конкретный сокет, вместо того, чтобы опрос, который мы должны сделать сейчас?
4 ответа
Существуют различные способы решения этой проблемы; какой из них вы выберете, зависит от того, сколько работы вы хотите сделать.*
Но сначала вы должны уточнить (для себя), имеете ли вы дело с UDP или TCP; для UDP-сокетов не существует "основного TCP-стека". Кроме того, UDP является неправильным протоколом, который используется для отправки целых данных, таких как текст или фотография; это ненадежный протокол, поэтому вы не гарантированно получите каждый пакет, если только вы не используете библиотеку управляемых сокетов (например, ENet).
Lua51 / LuaJIT + LuaSocket
Опрос является единственным методом.
- Блокировка: вызов
socket.select
без аргумента времени и ждать, пока сокет не будет читабельным. - Неблокирующая: звонок
socket.select
с аргументом тайм-аута0
и использоватьsock:settimeout(0)
на сокете вы читаете из.
Тогда просто вызывайте их повторно. Я бы предложил использовать планировщик сопрограмм для неблокирующей версии, чтобы позволить другим частям программы продолжить выполнение, не вызывая слишком большой задержки.
Lua51 / LuaJIT + LuaSocket + Lua Lanes (рекомендуется)
То же, что и вышеописанный метод, но сокет существует в другой линии (облегченное состояние Lua в другом потоке), созданной с использованием Lua Lanes ( последний источник). Это позволяет мгновенно считывать данные из сокета и в буфер. Затем вы используете Линду для отправки данных в основной поток для обработки.
Это, вероятно, лучшее решение вашей проблемы.
Я сделал простой пример этого, доступный здесь. Он опирается на Lua Lanes 3.4.0 ( GitHub repo) и исправленный LuaSocket 2.0.2 ( источник, патч, пост в блоге 're' patch)
Результаты многообещающие, хотя вы должны определенно провести рефакторинг моего примера кода, если вы его извлекаете.
LuaJIT + OS-специфичные сокеты
Если вы немного мазохист, вы можете попробовать реализовать библиотеку сокетов с нуля. Библиотека FFI LuaJIT делает это возможным из чистого Lua. Луа Лейнс был бы полезен и для этого.
Для Windows я предлагаю взглянуть на блог Уильяма Адама. У него были очень интересные приключения с разработкой LuaJIT и Windows. Что касается Linux и всего остального, посмотрите учебники по C или исходному коду LuaSocket и переведите их в операции FFI LuaJIT.
(LuaJIT поддерживает обратные вызовы, если API требует этого; однако производительность по сравнению с опросом от Lua до C. значительно снижается)
LuaJIT + ENet
ENet - отличная библиотека. Он обеспечивает идеальное сочетание TCP и UDP: надежный при желании, ненадежный в противном случае. Он также абстрагирует специфические детали операционной системы, так же, как это делает LuaSocket. Вы можете использовать Lua API, чтобы связать его, или напрямую получить к нему доступ через FFI LuaJIT (рекомендуется).
* Кашлять непреднамеренно.
Я использую lua-ev https://github.com/brimworks/lua-ev для всего, что связано с IO-мультиплексированием. Это очень легко использовать вписывается в Lua (и его function
) Как колдовство. Он либо на основе select / poll / epoll, либо на kqueue и тоже очень хорошо работает.
local ev = require'ev'
local loop = ev.Loop.default
local udp_sock -- your udp socket instance
udp_sock:settimeout(0) -- make non blocking
local udp_receive_io = ev.IO.new(function(io,loop)
local chunk,err = udp_sock:receive(4096)
if chunk and not err then
-- process data
end
end,udp_sock:getfd(),ev.READ)
udp_receive_io:start(loop)
loop:loop() -- blocks forever
По моему мнению, Lua + luasocket + lua-ev - это просто команда мечты для создания эффективных и надежных сетевых приложений (для встроенных устройств / сред). Есть более мощные инструменты там! Но если ваши ресурсы ограничены, Lua - хороший выбор!
Lua по своей сути однопоточный; нет такого понятия, как "событие". Нет способа прервать выполнение кода Lua. Таким образом, хотя вы можете настроить что-то похожее на событие, вы получите его только в том случае, если вызовете функцию, которая опрашивает, какие события были доступны.
Как правило, если вы пытаетесь использовать Lua для такого рода низкоуровневой работы, вы используете не тот инструмент. Вы должны использовать C или что-то еще для доступа к данным такого рода, а затем передать их Lua, когда они будут готовы.
Вы, вероятно, используете неблокирующую select()
"опросить" сокеты для любых новых доступных данных. Luasocket не предоставляет никакого другого интерфейса, чтобы видеть, есть ли новые доступные данные (насколько я знаю), но если вы обеспокоены тем, что это занимает слишком много времени, когда вы делаете это 10 раз в секунду, подумайте над написанием упрощенной версии он проверяет только один сокет, который вам нужен, и избегает создания и отбрасывания таблиц Lua. Если это не вариант, рассмотрите возможность передачи nil
в select()
вместо {}
для этих списков вам не нужно читать и передавать статические таблицы вместо временных:
local rset = {socket}
... later
...select(rset, nil, 0)
вместо
...select({socket}, {}, 0)