2016-07-19 14 views
4

웹 기반 슬라이드 쇼를위한 응용 프로그램을 만들고 있는데, 여기서 '마스터'사용자는 슬라이드간에 이동하고 모든 사용자의 브라우저는 따라갈 수 있습니다. 이를 위해 저는 메시지를 보내기 위해 글로벌 채널에 웹 소켓과 Redis를 사용하고 있습니다. 연결하는 각 클라이언트에는 배열에 저장된 정보가 있습니다 (@clients). 그럼 Redis 채널을 구독하기위한 별도의 스레드가 있습니다. 여기서 'on.message'블록은 @clients 배열의 모든 사용자에게 메시지를 보내도록 정의되어 있지만 그 배열은이 블록 내부에서 비어 있습니다 (아무 곳에도 비어 있지 않음). 그렇지 않으면 모듈에 있음).레일스 스레드 내에서 변수에 액세스하기

꽤 많은 예제 다음

: https://devcenter.heroku.com/articles/ruby-websockets

사용자 정의 미들웨어 클래스에 관련 코드 : 인스턴스 변수가 때로 믿을 수 있기 때문에 새로운 스레드 내부에 액세스 할 때

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']) 
    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 @clients.count 
      ### prints '0,' no clients receive msg 
      @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 @clients.count 
     ### prints actual number of clients 
    end 

    ws.on :message do |event| 
     $redis.publish(CHANNEL, event.data) 
    end 

    ws.on :close do |event| 
     @clients.delete(ws) 
     ws = nil 
    end 

    ws.rack_response 
    else 
    @app.call(env) 
    end 
end 
end 

@clients 배열 비어 스레드간에 공유되지 않습니까? 그렇다면 어떻게 스레드간에 변수를 공유합니까?

저는 $ clients (전역 변수, 스레드간에 액세스 가능해야 함)를 사용해 보았지만 아무 소용이 없습니다.

편집 : 이 사이트는 너무 많은 포인트 - 그래버들로 가득합니다. 아무도 더 이상 대답을하지 않고 단지 사소한 편집을하여 대표를 얻습니다.

+1

@kfrz 이것은 파이썬이 아니라 루비이다. 왜 다른 스레드에서이 작업을 수행하고 있습니까? 그리고 또 다른 질문입니다, 왜 당신은 이것을 손으로하고 있습니까? Rails 5.0을 사용한다면'actioncable'을 사용하면 모든 문제를 해결할 수 있습니다. – siegy22

+0

도움이 필요하면 다음 페이지를 참조하십시오. http://stackoverflow.com/questions/16538732/share-variable-through-ruby-processes – DhruvPathak

+0

@RaVeN Redis 코드가 차단됩니다. 결코 통제권을 반환하지 않습니다. 따라서 스레드가 작동해야합니다. ActionCable은 괜찮은 솔루션이며이 경우 모든 클라이언트가 JavaScript 인 경우 작동 할 수 있습니다. 그러나이 솔루션은보다 일반적이고 WebSocket을 지원하는 모든 클라이언트를 처리 할 수 ​​있어야합니다. –

답변

0

업데이트] 편집 : 쇼 작업 코드 은 또한 당신은 @client 변수가이 방법으로 사용되는 뮤텍스를 사용을 시도 할 수 있습니다. 디버깅 코드를 제외하고는 메인 모듈이 수정되지 않았습니다. 참고 : 해지하기 전에 구독 취소 필요성에 관해 이미 언급 한 문제를 경험했습니다.

코드가 올바르게 표시됩니다. 어떻게 인스턴스화하는지보고 싶습니다.

var ws = new WebSocket(uri); 

당신이 인스턴스를 수행

require 'ws_communication' 
config.middleware.use WsCommunication 

그런 다음, 자바 스크립트 클라이언트에서,이 같은이 있어야합니다 설정/application.rb에서

, 당신은 아마 같은 최소한 뭔가를 WsCommunication의 또 다른 인스턴스? 그러면 @clients가 빈 배열로 설정되어 증상을 나타낼 수 있습니다. 이런 식으로 뭔가 잘못된 것 :

var ws = new WsCommunication; 

그것은, 클라이언트를 표시 할 경우 우리를 도와 것이다 아마, 설정/application.rb이 게시물이 도움이되지 않는 경우.

덧붙여 말하자면, @clients가 모든 업데이트의 뮤텍스에 의해 보호되어야한다는 주석에 동의합니다. 이것은 이벤트 중심 시스템에서 언제든지 바뀔 수있는 동적 구조입니다. redis-mutex은 좋은 선택입니다. (Github이 현재 모든 것에 500 개의 오류를 던지고있는 것처럼 보이기 때문에 링크가 맞기를 바랍니다.)

$ redis.publish는 메시지를받은 클라이언트의 정수 값을 반환합니다.

마지막으로 종료하기 전에 채널의 구독을 취소해야 할 수도 있습니다. 나는 정리되지 않은 동일한 채널에 대한 이전 구독 때문에 각 메시지를 여러 번, 심지어 여러 번 보내는 상황을 경험했습니다. 스레드 내에서 채널을 구독 중이므로 같은 스레드 내에서 구독을 취소해야합니다. 그렇지 않으면 프로세스가 마술처럼 보이는 오른쪽 스레드를 기다리는 중 "멈춰"있습니다. 나는 "unsubscribe"플래그를 설정하고 메시지를 보내는 것으로 그 상황을 처리한다. 그런 다음 on.message 블록 내에서 수신 거부 플래그를 테스트하고 수신 거부를 발행합니다.

만 약간의 디버깅 수정을 제공 모듈 : 내가 제공

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 

테스트 가입자 코드 : 나는 제공

# 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 

테스트 게시자 코드입니다. 이 단지 테스트이기 때문에 게시자와 구독자 쉽게 결합 될 수있다 :

# 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 

랙 미들웨어 계층에서이 모든 것을 실행하는 샘플 config.ru :이 홈페이지

require './controllers/main' 
require './middlewares/ws_communication' 
use WsCommunication 
run Main.new 

입니다. 나는 그것을 실행 버전에서 떼어 냈다. 그래서 당신이 그것을 사용한다면 그것은 비틀어 져야 할 필요가있다 :

%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 
+0

안녕하세요,이 모든 정보를 제공해 주셔서 감사합니다! 저는 이것을 답으로 표시 할 것입니다. 왜냐하면 프로젝트의 필요성이 바뀌어서 내가 필요한 것을 다른 방식으로 할 수 있었지만, 이것은 매우 유익했습니다. – volx757

0

@client가 모든 스레드에서 공유되어야합니다. 클라이언트가 배열에서 우연히 삭제되지 않았습니까? ws.on : close 블록에 "삭제 된 클라이언트"를 넣고 테스트 해보십시오. END AT http://ruby-doc.org/core-2.2.0/Mutex.html

관련 문제