CyberFerret
CyberFerret

Reputation: 275

Using Crystal/Kemal to listen for UDP packets

I have been trying to create a non-blocking server using Crystal and Kemal which will (a) listen for a stream of UDP messages being sent to it, and (b) then forwarding that message to a WebSocket to any browsers who have started a ws connection.

So far, the best I can manage is:

require "kemal"
require "socket"

server = UDPSocket.new
server.bind "localhost", 1234
puts "Started..."

ws "/" do |socket|

    udp_working = true

    while udp_working
        message, client_addr = server.receive
        socket.send message
    end

    socket.on_close do
        puts "Goodbye..."
        udp_working = false
    end
end

This all seems a little inelegant, and indeed, doesn't work as expected because:

I was hoping for a server.on_message type handling which would enable me to run code only when UDP packets were received, rather than continual polling which blocks the server. Is there another way to achieve this using Crystal/Kemal?

Thanks!

Upvotes: 4

Views: 312

Answers (2)

Shelvacu
Shelvacu

Reputation: 4360

Crystal handles non-blocking for you, you'll want to write blocking code in separate fibers and communicate using channels. Crystal will use non-blocking code and select() calls behind the scenes.

Further, you'll need either a framework or some of your own code to copy messages received to every listening websocket. This is often called publish/subscribe or pub/sub.


ws "/" do |socket|

   udp_working = true

   while udp_working
       message, client_addr = server.receive
       socket.send message
   end

   socket.on_close do
       puts "Goodbye..."
       udp_working = false
   end
end

i.e. socket.on_close is not being triggered,

You want to run socket.on_close first, otherwise the event handler won't get added and therefor won't run until after the loop, but the loop is effectively infinite because of that. Also, there is a small chance that socket.send message will error due to a closed socket, if the socket is closed between checking the udp_working var and the call to #send

Upvotes: 3

Johannes Müller
Johannes Müller

Reputation: 5661

There are several problems with your approach:

First, socket.on_close can't work because this line is never reached. The while loop will run as long as udp_working == true and it would only be set to false in the on_close hook.

If you don't want UDP datagrams to pile up, you need to receive them from the beginning and do whatever you want (perhaps dispose?) if there is no websocket connected. There is no on_message hook for UDPServer but receive is already non-blocking. So you can just run it in a loop (in it's own fiber) and act whenever the method returns. See Crystal Concurrency for details; there is also an example using a TCPSocket, but UDP should be similar in this regard.

Upvotes: 3

Related Questions