fifosock Module

Since Origin / Contributor Maintainer Source
2019-02-10 TerryE nwf fifosock.lua

This module provides a moderately convenient, efficient wrapper around the net.socket send method. It ensures in-order transmission while striving to minimize memory footprint and packet count by coalescing queued strings. It also serves as a detailed, worked example of the fifo module.

Use

ssend = (require "fifosock").wrap(sock)
ssend("hello, ") ssend("world\n")

-- when finished
ssend = nil
sock:on("sent", nil)

Once the socket has been wrapped, one should use only the resulting ssend function in lieu of sock:send, and one should not change the sock:on("sent") callback for the duration of the connection.

Use of this module creates a circular reference through the Lua registry: the socket points at the fifosock wrapper, which points back at the socket. As such, it is vitally important to break this cycle when the socket has outlived its use. The usual garbage collection will not be able to reclaim abandoned wrapped sockets. The user of fifosock must, when disposing of the socket, unwire the wrapper, by calling sock:on("sent", nil) and should drop all references to ssend; a convenient place to do this is in the sock:on("disconnect") callback.

Advanced Use

In addition to passing strings representing part of the stream to be sent, it is possible to pass the resulting ssend function functions. These functions will be given no parameters, but should return two values:

  • A string to be sent on the socket, or nil if no output is desired
  • A replacement function, or nil if the function is to be dequeued. Functions may, of course, offer themselves as their own replacement to stay at the front of the queue.

This facility is useful for providing a replacement for the sock:on("sent") callback channel. In the fragment below, "All sent" will be printed only when the entirety of "hello, world\n" has been successfully sent on the socket.

ssend("hello, ") ssend("world\n")
ssend(function() print("All sent") end) -- implicitly returns nil, nil

This facility is also useful for generators of the stream, roughly akin to sendfile-like primitives in larger systems. Here, for example, we can stream SPIFFS data across the network without ever holding a large amount in RAM.

local function sendfile(fn)
 local offset = 0
 local function send()
  local f = file.open(fn, "r")
  if f and f:seek("set", offset) then
    r = f:read()
    f:close()
    if r then
      offset = offset + #r
      return r, send
    end
  end
  -- implicitly returns nil, nil and falls out of the stream
 end
 return send, function() return offset end
end

local fn = "test"
ssend(("Sending file '%s'...\n"):format(fn))
dosf, getsent = sendfile(fn)
ssend(dosf)
ssend(("Sent %d bytes from '%s'\n"):format(getsent(), fn))