BIJGEWERKT BEWERKEN AAN EINDE:Toont werkende code. Hoofdmodule ongewijzigd, behalve voor foutopsporingscode. Opmerking:ik heb het probleem ervaren dat ik al heb opgemerkt met betrekking tot de noodzaak om je af te melden voorafgaand aan de beëindiging.
De code ziet er correct uit. Ik zou graag willen zien hoe je het concretiseert.
In config/application.rb heb je waarschijnlijk iets als:
require 'ws_communication'
config.middleware.use WsCommunication
Dan zou je in je JavaScript-client zoiets als dit moeten hebben:
var ws = new WebSocket(uri);
Instantieer je een ander exemplaar van WsCommunication? Dat zou @clients op een lege array plaatsen en uw symptomen kunnen vertonen. Iets als dit zou onjuist zijn:
var ws = new WsCommunication;
Het zou ons helpen als je de client en misschien config/application.rb zou laten zien als dit bericht niet helpt.
Overigens ben ik het eens met de opmerking dat @clients bij elke update door een mutex moeten worden beschermd, zo niet ook. Het is een dynamische structuur die op elk moment kan veranderen in een gebeurtenisgestuurd systeem. redis-mutex is een goede optie. (Ik hoop dat die link correct is, want Github lijkt op dit moment 500 fouten op alles te gooien.)
U kunt ook opmerken dat $redis.publish een geheel getal retourneert van het aantal clients dat het bericht heeft ontvangen.
Ten slotte kan het zijn dat je ervoor moet zorgen dat je kanaal vóór beëindiging is uitgeschreven. Ik heb situaties gehad waarin ik elk bericht meerdere, zelfs vele keren heb verzonden vanwege eerdere abonnementen op hetzelfde kanaal die niet waren opgeschoond. Aangezien je je abonneert op het kanaal binnen een thread, moet je je afmelden binnen diezelfde thread, anders blijft het proces gewoon "hangen" totdat de juiste thread op magische wijze verschijnt. Ik handel die situatie af door een "unsubscribe"-vlag in te stellen en vervolgens een bericht te verzenden. Vervolgens test ik binnen het on.message-blok op de afmeldvlag en geef ik daar de afmelding uit.
De module die u heeft geleverd, met slechts kleine debugging-aanpassingen:
require 'faye/websocket'
require 'redis'
class WsCommunication
KEEPALIVE_TIME = 15 #seconds
CHANNEL = 'vip-deck'
def initialize(app)
@app = app
@clients = []
uri = URI.parse(ENV['REDISCLOUD_URL'])
$redis = Redis.new(host: uri.host, port: uri.port, password: uri.password)
Thread.new do
redis_sub = Redis.new(host: uri.host, port: uri.port, password: uri.password)
redis_sub.subscribe(CHANNEL) do |on|
on.message do |channel, msg|
puts "Message event. Clients receiving:#{@clients.count};"
@clients.each { |ws| ws.send(msg) }
end
end
end
end
def call(env)
if Faye::WebSocket.websocket?(env)
ws = Faye::WebSocket.new(env, nil, {ping: KEEPALIVE_TIME})
ws.on :open do |event|
@clients << ws
puts "Open event. Clients open:#{@clients.count};"
end
ws.on :message do |event|
receivers = $redis.publish(CHANNEL, event.data)
puts "Message published:#{event.data}; Receivers:#{receivers};"
end
ws.on :close do |event|
@clients.delete(ws)
puts "Close event. Clients open:#{@clients.count};"
ws = nil
end
ws.rack_response
else
@app.call(env)
end
end
end
De testabonneecode die ik heb verstrekt:
# encoding: UTF-8
puts "Starting client-subscriber.rb"
$:.unshift File.expand_path '../lib', File.dirname(__FILE__)
require 'rubygems'
require 'eventmachine'
require 'websocket-client-simple'
puts "websocket-client-simple v#{WebSocket::Client::Simple::VERSION}"
url = ARGV.shift || 'ws://localhost:3000'
EM.run do
ws = WebSocket::Client::Simple.connect url
ws.on :message do |msg|
puts msg
end
ws.on :open do
puts "-- Subscriber open (#{ws.url})"
end
ws.on :close do |e|
puts "-- Subscriber close (#{e.inspect})"
exit 1
end
ws.on :error do |e|
puts "-- Subscriber error (#{e.inspect})"
end
end
De testuitgeverscode die ik heb verstrekt. Uitgever en Abonnee kunnen gemakkelijk worden gecombineerd, aangezien dit slechts tests zijn:
# encoding: UTF-8
puts "Starting client-publisher.rb"
$:.unshift File.expand_path '../lib', File.dirname(__FILE__)
require 'rubygems'
require 'eventmachine'
require 'json'
require 'websocket-client-simple'
puts "websocket-client-simple v#{WebSocket::Client::Simple::VERSION}"
url = ARGV.shift || 'ws://localhost:3000'
EM.run do
count ||= 0
timer = EventMachine.add_periodic_timer(5+rand(5)) do
count += 1
send({"MESSAGE": "COUNT:#{count};"})
end
@ws = WebSocket::Client::Simple.connect url
@ws.on :message do |msg|
puts msg
end
@ws.on :open do
puts "-- Publisher open"
end
@ws.on :close do |e|
puts "-- Publisher close (#{e.inspect})"
exit 1
end
@ws.on :error do |e|
puts "-- Publisher error (#{e.inspect})"
@ws.close
end
def self.send message
payload = message.is_a?(Hash) ? message : {payload: message}
@ws.send(payload.to_json)
end
end
Een voorbeeld van config.ru die dit allemaal uitvoert op de rack-middleware-laag:
require './controllers/main'
require './middlewares/ws_communication'
use WsCommunication
run Main.new
Dit is Main. Ik heb het uit mijn actieve versie verwijderd, dus het moet misschien worden aangepast als je het gebruikt:
%w(rubygems bundler sinatra/base json erb).each { |m| require m }
ENV['RACK_ENV'] ||= 'development'
Bundler.require
$: << File.expand_path('../', __FILE__)
$: << File.expand_path('../lib', __FILE__)
Dir["./lib/*.rb", "./lib/**/*.rb"].each { |file| require file }
env = ENV['OS'] == 'Windows_NT' ? 'development' : ENV['RACK_ENV']
class Main < Sinatra::Base
env = ENV['OS'] == 'Windows_NT' ? 'development' : ENV['RACK_ENV']
get "/" do
erb :"index.html"
end
get "/assets/js/application.js" do
content_type :js
@scheme = env == "production" ? "wss://" : "ws://"
erb :"application.js"
end
end