Luasocket - How to send/receive UDP broadcast messages?

3.1k views Asked by At

I've been trying to do this for a while. I tried following this http://lua.2524044.n2.nabble.com/UDP-Broadcast-td3995269.html, but to no avail. I've successfully sent unicast messages between the client and server. I've confirmed my broadcast address is correct. The only other thing I can think of is sniffing my interface to make sure there's no network problems. I'm gonna get around to that tomorrow, but wanted to post this here to see if anyone might notice a stupid mistake.

Note: I'm running the client and the server on the same laptop.

client

local socket = require("socket")
local s_address, s_port = "192.168.1.135", 33333
local d_address, d_port = "192.168.1.255", 22222

udp = socket.udp()
assert(udp)
assert(udp:settimeout(1))
assert(udp:setoption('broadcast', true))
assert(udp:setoption('dontroute',true))
assert(udp:setsockname(s_address, s_port))

while true do
  tosend = string.format("client %s:%s", s_address, s_port)
  print(string.format('sending to %s:%s...', d_address, d_port))
  udp:sendto(tosend, d_address, d_port)

  data, msg = udp:receive()
  toprint = string.format("data = \"%s\"", data)
  print(toprint)
  socket.sleep(.5)
end

server

local socket = require("socket")
local s_address, s_port = "192.168.1.135", 11111
local p_address, p_port = "192.168.1.255", 22222

udp = socket.udp()
assert(udp)
assert(udp:setoption('broadcast', true))
assert(udp:setoption('dontroute',true))
assert(udp:settimeout(1))
assert(udp:setsockname(s_address, s_port))
assert(udp:setpeername(p_address, p_port))

while true do
  data = udp:receive()
  print(string.format('listening on %s:%s...', p_address, p_port))
  toprint = string.format("data = \"%s\"", data)
  print(toprint)
  if data then
    msg_back = "server received your message"
    udp:send(msg_back)
  end
  socket.sleep(.5)
end

output
Both sides continually print data = "nil".

1

There are 1 answers

2
Baraqiel On

I had been looking to implement luasocket broadcast myself, and this was one of the few pages on the internet that even brought the topic up. In the end, between the luasocket API and some python code I was able to adapt, I found a solution that works for me:

local socket = require 'socket'
local HOME_ADDR = '192.168.0.1'
local SUBNET_PATTERN = '%d+%.%d+%.%d+%.'

local function get_own_address()
  local s = assert(socket.udp())
  assert(s:setpeername(HOME_ADDR, 80))
  local host = s:getsockname()
  s:close()
  return host
end

function broadcast(message, address, port)
  local broadcaster = assert(socket:udp())
  assert(broadcaster:setoption('broadcast', true))
  assert(broadcaster:setoption('dontroute', true))   -- do we need this?

  print(('Broadcasting %q to %s:%i'):format(message, address, port))
  assert(broadcaster:sendto(message, address, port))
  broadcaster:close()
end

function antiphony(message, timeout, call_port, response_port, response_mask)
  local message, timeout, mask = tostring(message), tonumber(timeout) or 1.5, response_mask or '*a'
  local send_port, recv_port = call_port or 10500, response_port or 10501
  local broadcast_address = get_own_address():match(SUBNET_PATTERN) .. '255'
  local listener = assert(socket.bind('*', recv_port, 64))
  local clients, responses, starttime = {}, {}

  listener:settimeout(0)
  broadcast(message, broadcast_address, send_port)
  starttime = socket.gettime()
  repeat clients[listener:accept() or false] = true
  until socket.gettime() - starttime > timeout

  for client in pairs(clients) do 
    if client then
      responses[client:getpeername()] = tostring(client:receive(mask))
      client:close()
    end
  end
  listener:close()

  return responses
end

function listen(response_callback, timeout, listen_port, reply_port)
  local recv_port, send_port = tonumber(listen_port) or 10500, tonumber(reply_port) or 10501
  local callback = response_callback or function()end
  local address = get_own_address():match(SUBNET_PATTERN) .. '255'
  local sleeper = socket.udp()

  sleeper:settimeout(timeout)
  sleeper:setsockname(address, recv_port)
  repeat
    local data, ip, port = sleeper:receivefrom(1024)
    if data then callback(ip, send_port, data) end
  until not data
end

function respond(message, data_handler) 
  return function(ip_addr, port, data)
    assert(ip_addr and port, ('Invalid address: %s:%s'):format(tostring(ip_addr), tostring(port)))
    local msg = tostring(type(data_handler) == 'function' and (data_handler(data, message) or messsage) or message)
    local sender = assert(socket.connect(ip_addr, port))
    assert(sender:send(msg))
    sender:close()
  end
end

--[[

-- Listener / Responder:
listen(respond('polo', print), 600) -- 10 minute timeout

-- Broadcaster / Receiever:
for address, response in pairs(antiphony('marco')) do 
  print(address, response) 
end

]]