Client/Server UDP within a thread.

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
FlyingDojo
Prole
Posts: 7
Joined: Tue Feb 09, 2021 9:41 pm

Client/Server UDP within a thread.

Post by FlyingDojo »

Hi, lurked but haven't posted nor created an account until now, I've been just playing around with love for a while getting to grips with it, I've tried to setup a BASIC server with UDP that listens only. I have a client that on a keypress it sends a single word, "fish". I've tested the thread push and popping standalone with the server sections in and it worked fine when tested so I'm assuming that I'm doing it correctly. It's got to be something wrong with the UDP code, I'm literally just wanting to send a single word. Can you help me, even if it's just a sanity check? I'm on mac OSX, MacBook pro Air with Big Sur, not sure if that changes anything in regards to issues.

I haven't got a .love, so will post the code.

Client: main.lua

Code: Select all

local socket = require("socket")
local ipaddr  = "192.168.1.71"
local udp = socket.udp()
local sendit

function love.load()

end

function sendMessage(message)
    udp:sendto(message, ipaddr, 65000)
end

function love.keypressed(key)
  if key == 'g' then
    sendit = not sendit
  end
end

function love.update(dt)
if sendit then
  sendMessage('fish')
end

end

function love.draw()
  local delta = love.timer.getAverageDelta()
  -- Display the frame time in milliseconds for convenience.
  -- A lower frame time means more frames per second.
  love.graphics.print(string.format("Average frame time: %.3f ms", 1000 * delta), 0, 0)
  fps = love.timer.getFPS( )
  love.graphics.print("Current FPS: "..tostring(love.timer.getFPS( )), 0, 15)

  love.graphics.print("Client: "..ipaddr, 0, 35)

  -- local count = love.thread.getChannel('status'):getCount()
  -- if count then
  --   love.graphics.print(count, 0, 100)
  -- end

-- local stat = love.thread.getChannel('status'):peek()
--   if stat then
--       love.graphics.print("test: "..tostring(stat), 0, 80)
--   end

  if sendit then
    love.graphics.print("sending",50,50)
  end
end
Server: main.lua

Code: Select all

local socket = require("socket")

function love.load()
  serverThread = love.thread.newThread("listen.lua")
  serverThread:start()
  local err = serverThread:getError()

  address = "Server Address:  " .. socket.dns.toip(socket.dns.gethostname())

end

function love.keypressed(key)
  if key == 's' then

  elseif key == 'q' then
    love.event.quit(exitstatus)
  elseif key == 'r' then
    love.event.quit( "restart" )
  end
end

function love.update(dt)
  local message = love.thread.getChannel('messages'):peek() --set to peek not pop so the first sent it shown
  local ip = love.thread.getChannel('ips'):peek()
  local port = love.thread.getChannel('ports'):peek()

  if message then

  elseif ip ~= 'timeout' then
    error("Unknown network error: "..tostring(msg))
  end

  assert( not err, error )
end

function love.draw()
  local delta = love.timer.getAverageDelta()
  -- Display the frame time in milliseconds for convenience.
  -- A lower frame time means more frames per second.
  love.graphics.print(string.format("Average frame time: %.3f ms", 1000 * delta), 0, 0)
  fps = love.timer.getFPS( )
  love.graphics.print("Current FPS: "..tostring(love.timer.getFPS( )), 0, 15)

  love.graphics.print(address, 0, 35)

  --see if were getting any messages on the thread channel
  local count = love.thread.getChannel('messages'):getCount()
  if count then
    love.graphics.print(count, 0, 100)
  end

  if message then
    love.graphics.print("Message: "..tostring(message), 0, 60)
  elseif ip then
    love.graphics.print("IP: "..tostring(ip), 0, 75)
  elseif port then
    love.graphics.print("Port: "..tonumber(port), 0, 99)
  end
end
Server: listen.lua

Code: Select all

local socket = require("socket")


function listen()
    local udp = socket.udp()
    udp:setsockname ('*', 65000)
    udp:settimeout(0)

    while true do
        local message, ip, port = udp:receivefrom()
        love.thread.getChannel('messages'):push(message)
        love.thread.getChannel('ips'):push(ip)
        love.thread.getChannel('ports'):push(port)
        socket.sleep(0.01)
    end
end

listen()
Many Thanks, FD!
User avatar
Xii
Party member
Posts: 137
Joined: Thu Aug 13, 2020 9:09 pm
Contact:

Re: Client/Server UDP within a thread.

Post by Xii »

address = "Server Address: " .. socket.dns.toip(socket.dns.gethostname())
What does the server address look like?
FlyingDojo
Prole
Posts: 7
Joined: Tue Feb 09, 2021 9:41 pm

Re: Client/Server UDP within a thread.

Post by FlyingDojo »

It’s gives me 192.168.1.71 same as client up and I’ve tried port forwarding on router, also tried local host and doesn’t make a difference. Firewall off too Cheers fd
User avatar
Xii
Party member
Posts: 137
Joined: Thu Aug 13, 2020 9:09 pm
Contact:

Re: Client/Server UDP within a thread.

Post by Xii »

This post suggests that you should not pass 0 to udp:settimeout().

You haven't actually said what's (not) happening, by the way. What are you expecting your code to do? What does it (not) do instead?
FlyingDojo
Prole
Posts: 7
Joined: Tue Feb 09, 2021 9:41 pm

Re: Client/Server UDP within a thread.

Post by FlyingDojo »

I originally had that no number in the timeout but it didn’t work like now and I added it because of something I read on the net, it still didn’t work and I’ve just left it there. I’m expecting the client to send a single word “fish” to the server and then the server to receive it, push it via thread to the main file and then it gets popped/ peeked for now and then would be displayed onto the server love window via love print. It’s quite literally basic one way communication I’m after to get something working and build from there, except it doesn’t work ha!

Thanks, FD
FlyingDojo
Prole
Posts: 7
Joined: Tue Feb 09, 2021 9:41 pm

Re: Client/Server UDP within a thread.

Post by FlyingDojo »

Xii wrote: Wed Feb 10, 2021 11:29 am This post suggests that you should not pass 0 to udp:settimeout().

You haven't actually said what's (not) happening, by the way. What are you expecting your code to do? What does it (not) do instead?
I’ve actually read that post, I originally didn’t have anything within the timeout, it didn’t work, I added nil, still didn’t work and then move to putting a 0 which doesn’t work. I haven’t tried a positive value though so I will test that and respond with the results. I’m at work right now so won’t post back for a few hours, bare with me. Many thanks, FD!

Edit:
Sorry for double post, just realised.
User avatar
Xii
Party member
Posts: 137
Joined: Thu Aug 13, 2020 9:09 pm
Contact:

Re: Client/Server UDP within a thread.

Post by Xii »

I'm checking the client send for errors:

Code: Select all

    local result, err = udp:sendto(message, ipaddr, 65000)
	print(result, err)
Which should print like "1 nil", but actually prints: "nil refused". So the client is "refused" from sending anything. I don't know why. The docs say:
the only way it can fail is if the underlying transport layer refuses to send a message to the specified address (i.e. no interface accepts the address).
grump
Party member
Posts: 947
Joined: Sat Jul 22, 2017 7:43 pm

Re: Client/Server UDP within a thread.

Post by grump »

FlyingDojo wrote: Tue Feb 09, 2021 9:54 pm I have a client that on a keypress it sends a single word, "fish".
You're not sending only one "fish" when a key is pressed; you're sending one "fish" per frame after the key was pressed. Not sure if that's what you actually want.

Code: Select all

function love.update(dt)
  local message = love.thread.getChannel('messages'):peek() --set to peek not pop so the first sent it shown
  local ip = love.thread.getChannel('ips'):peek()
  local port = love.thread.getChannel('ports'):peek()

  if message then

  elseif ip ~= 'timeout' then
    error("Unknown network error: "..tostring(msg))
  end

  assert( not err, error )
end
Use Channel:pop, not :peek. You keep reading the same value over and over, and the channel is filling up because you never remove messages.

There's a race condition looming: update() could be called before the thread had a chance to push anything, which will trigger the "Unknown network" error.
FlyingDojo
Prole
Posts: 7
Joined: Tue Feb 09, 2021 9:41 pm

Re: Client/Server UDP within a thread.

Post by FlyingDojo »

grump wrote: Wed Feb 10, 2021 1:52 pm
FlyingDojo wrote: Tue Feb 09, 2021 9:54 pm I have a client that on a keypress it sends a single word, "fish".
You're not sending only one "fish" when a key is pressed; you're sending one "fish" per frame after the key was pressed. Not sure if that's what you actually want.

Code: Select all

function love.update(dt)
  local message = love.thread.getChannel('messages'):peek() --set to peek not pop so the first sent it shown
  local ip = love.thread.getChannel('ips'):peek()
  local port = love.thread.getChannel('ports'):peek()

  if message then

  elseif ip ~= 'timeout' then
    error("Unknown network error: "..tostring(msg))
  end

  assert( not err, error )
end
Use Channel:pop, not :peek. You keep reading the same value over and over, and the channel is filling up because you never remove messages.

There's a race condition looming: update() could be called before the thread had a chance to push anything, which will trigger the "Unknown network" error.
Yea I knew it repeated, I left it like that for now as I didn’t think it would make a difference because the server would peek it so the message would remain in the thread queue so when I print it to screen it stays on screen instead of when popped it would disappear very fast. This is moved from thread testing code I did, in reality thinking about it if I’m sending the message repeatedly then it would pop the same amount and print on screen the same and wouldn’t just be a flash. Theoretically...

I don’t understand your comment on the race condition, could you elaborate for me?? Thanks.

Fd
User avatar
pgimeno
Party member
Posts: 3548
Joined: Sun Oct 18, 2015 2:58 pm

Re: Client/Server UDP within a thread.

Post by pgimeno »

I added this to the client and then sendto() succeeded:

Code: Select all

udp:setsockname('*', 0)
As for the server, there are multiple issues. I'll leave the main.lua issues up to grump, since he's on that already. I'll just note that a prerequisite for it to work is to not give an error when peeking the ips channel returns nil.

As for listen.lua, to do it cleanly there are two possible approaches, one simpler than the other. For both cases, remove socket.sleep() completely.

- If you're only going to receive from the same UDP socket, you can just not set the timeout. This is the simpler way.
- If you plan on receiving from multiple sockets (UDP or otherwise), use socket.select to wait until one of them receives something.
Post Reply

Who is online

Users browsing this forum: Bing [Bot], Google [Bot] and 142 guests