2017-10-18 74 views
4

我一直在嘗試使用Crystal和Kemal創建一個非阻塞服務器,它將(a)偵聽發送給它的UDP消息流,然後(b)然後轉發該消息發送給任何已啓動ws連接的瀏覽器的WebSocket。使用Crystal/Kemal來偵聽UDP數據包

到目前爲止,我可以管理最好的是:

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 

這一切似乎有點不雅,而事實上,沒有按預期工作,因爲:

  • 在所有發送的UDP數據包在啓動的Crystal服務器和連接到Crystal服務器的第一個Web瀏覽器之間緩存併發送一個巨大的積壓文件
  • 從WebSockets斷開連接的瀏覽器未正確處理,即沒有觸發socket.on_close,第二循環繼續,直到我終止水晶服務器

我希望的是server.on_message型處理,這將使我只有在收到UDP數據包,而不是持續輪詢哪些塊服務器來運行代碼。有沒有另外一種方法可以使用Crystal/Kemal來實現這一點?

謝謝!

回答

3

有幾個問題你的方法:

首先,socket.on_close不行,因爲從來沒有達到這條線。 while循環將運行的時間長度只有udp_working == true,並且只會在on_close鉤子中設置爲false

如果你不想要UDP數據報堆積起來,你需要從頭開始接收它們,並且如果沒有連接websocket,你可以做任何你想做的事情(也許處理?)。 UDPServer沒有on_message掛鉤,但receive已經是非阻塞的。所以你可以在一個循環中(在它自己的光纖中)運行它,並在方法返回時採取行動。詳情請參閱Crystal Concurrency;還有一個使用TCPSocket的例子,但UDP在這方面應該類似。

3

Crystal爲您處理非阻塞,您需要在單獨的光纖中編寫阻止代碼並使用通道進行通信。 Crystal將在幕後使用非阻塞代碼和select()調用。此外,您需要一個框架或一些自己的代碼來將收到的消息複製到每個偵聽的websocket。這通常稱爲發佈/訂閱或發佈/訂閱。


...

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 

即socket.on_close沒有被觸發,

您想運行socket.on_close第一,否則事件處理贏得不會被添加,因此直到循環之後纔會運行,但由於此循環,循環實際上是無限的。另外,如果在檢查udp_working var和調用#send

之間關閉套接字,則由於套接字關閉而導致 socket.send message錯誤的可能性很小