在当前的 lua 套接字实现中,我看到我们必须安装一个定期回调的计时器,以便我们检查非阻塞 API 以查看我们是否收到任何内容。
这一切都很好,但是在 UDP 情况下,如果发送者有很多信息正在发送,我们是否有丢失数据的风险。假设另一台设备通过 UDP 发送 2MB 照片,我们每 100 毫秒检查一次套接字接收。在 2MBps 时,在我们的调用查询底层 TCP 堆栈之前,底层系统必须存储 200Kbits。
当我们在特定套接字上接收到数据而不是我们现在必须做的轮询时,有没有办法让事件触发?
有多种方法可以处理此问题;你会选择哪一个取决于你想做多少工作。*
但首先,您应该(对自己)澄清您是在处理 UDP 还是 TCP;UDP 套接字没有“底层 TCP 堆栈”。此外,UDP 是用于发送整个数据(如文本或照片)的错误协议;它是一个不可靠的协议,因此不能保证您接收到每个数据包,除非您使用托管套接字库(例如ENet)。
轮询是唯一的方法。
socket.select
不带时间参数的调用并等待套接字可读。socket.select
使用超时参数调用0
,并sock:settimeout(0)
在您正在读取的套接字上使用。然后简单地重复调用这些。我建议对非阻塞版本使用协程调度程序,以允许程序的其他部分继续执行而不会造成太多延迟。
与上述方法相同,但套接字存在于使用Lua Lanes制作的另一个通道(另一个线程中的轻量级 Lua 状态)中(最新来源)。这使您可以立即从套接字读取数据并进入缓冲区。然后,您使用linda将数据发送到主线程进行处理。
这可能是您问题的最佳解决方案。
我已经做了一个简单的例子,可以在这里找到。它依赖于 Lua Lanes 3.4.0 ( GitHub repo ) 和修补过的 LuaSocket 2.0.2 ( source , patch , blog post re' patch )
结果是有希望的,但如果你从中派生出我的示例代码,你肯定应该重构它。
如果你有点自虐,你可以尝试从头实现一个套接字库。LuaJIT的FFI 库使这从纯 Lua 成为可能。Lua Lanes 对此也很有用。
对于 Windows,我建议查看William Adam 的博客。他在 LuaJIT 和 Windows 开发方面有过一些非常有趣的冒险经历。至于 Linux 和其他,请查看 C 教程或 LuaSocket 的源代码,并将它们转换为 LuaJIT FFI 操作。
(如果 API 需要,LuaJIT 支持回调;但是,与从 Lua 轮询到 C 相比,存在显着的性能成本。)
ENet是一个很棒的库。它提供了 TCP 和 UDP 之间的完美组合:需要时可靠,否则不可靠。它还抽象了操作系统特定的细节,就像 LuaSocket 一样。你可以使用 Lua API 来绑定它,或者直接通过 LuaJIT 的 FFI 访问它(推荐)。
*双关语无意。
我将 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)