Reputation: 275
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
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
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