Search code examples
rubywebsocketeventmachineem-websocket

How do I talk to a client from within a loop using EventMachine?


I want to show my program's progress (exemplified here as a loop) on a website in real-time.

If I have a proper Ruby server running, a client should be able to connect to the server via websockets using a browser, and, once connected, receive 'updates' about a newly spawned process in real time. No polling, or Ajax required.

My idea to test this is to establish duplex communication between EventMachine::WebSocket and a client browser using clientside JavaScript. Build a loop, and send updates to a browser client.

ruby_socket_server.rb:

require 'eventmachine'
require 'em-websocket'
@sockets = []
EventMachine.run do
    EventMachine::WebSocket.start(:host =>'localhost',:port =>8887) do |socket|
    socket.onopen do
        @sockets << socket
        (0..10).each do |i|
            puts "i am on loop #{i}" #to terminal window
            @sockets.each do |s|
                s.send "Just received notification of loop #{i}" 
                sleep(1) #pause for a second
            end
        end
    end
end

main.js:

socket = new WebSocket('ws://localhost:8887');
socket.onopen = function(){
    console.log('OPEN');
}
socket.onclose = function(){
    console.log('CLOSED');
}
socket.onmessage = function(msg){
    console.log(msg);
}

I expect it to look like:

  • Server: i am on loop 1.
  • Client: Just received notification of loop 1.
  • Server: i am on loop 2.
  • Client: Just received notification of loop 2.

What is actually happening:

  • Server: i am on loop 1.
  • Server: i am on loop 2.
  • Client: Just received notification of loop 1.
  • Client: Just received notification of loop 2.

I am unable to send data to the client from WITHIN a Ruby loop, i.e. it eventually sends all packets, but not DURING every iteration as expected. Instead, it waits until the server loop is complete, and sends multiple calls back to back.

What am I doing wrong here? Is what I am looking for even possible? Any thoughts on how to crack this nut, would be helpful.


Solution

  • EventMachine is not multithreaded. So if you do not let it run, i.e. if you stay inside any of its callbacks, then nothing will happen.

    In your example you call sleep inside a callback. This will prevent the callback from exiting and hence EM can't run, and if it can't run it can't send your message.

    So any EM server has to be constructed fully around an evented callback setup.

    That's why it's called Event Machine. It requires a slightly different way of thinking, but once you get the hang of it you should be fine.

    Here's an example on how you could make it work:

    EM.run do
      EM::WebSocket.start(:host =>'localhost',:port =>8887) do |ws|
        ws.onopen { |handshake|
          puts "New connection from #{handshake.origin}"
          loop = 1
          timer = EM.add_periodic_timer(1) {
            ws.send "Timer loop #{loop}"
    
            if (loop += 1) == 5
              EM.cancel_timer(timer)
              ws.send "Bye"
              ws.close_websocket
            end
          }
        }
      end
    end