Search code examples
udpcrystal-langkemal

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:

  • All UDP packets sent in between the Crystal server being started and the first web browser connecting to the Crystal server is cached and sent in one huge backlog
  • Browsers disconnecting from WebSockets are not handled properly, i.e. socket.on_close is not being triggered, and the loop continues until I terminate the Crystal server

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!


Solution

  • 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.