Я использовал руководство Heroku для реализации веб-сокетов.
Он корректно работает с Thin, но не работает с Unicorn и Puma.
Также реализовано эхо-сообщение, которое отвечает на сообщение клиента. Он корректно работает на каждом сервере, поэтому проблем с реализацией вебсокетов не возникает.
Настройка Redis также правильная (он перехватывает все сообщения и выполняет код внутри блока subscribe
).
Как это работает сейчас:
При запуске сервера инициализируется пустой массив @clients
. Затем запускается новый поток, который слушает Redis и предназначен для отправки этого сообщения соответствующему пользователю из массива @clients.
При загрузке страницы создается новое соединение через веб-сокет, оно сохраняется в массиве @clients.
Если мы получаем сообщение от браузера, мы отправляем его обратно всем клиентам, подключенным к одному и тому же пользователю (эта часть работает правильно как на Thin, так и на Puma).
Если мы получаем сообщение от Redis, мы также ищем все соединения пользователя, хранящиеся в массиве @clients. Здесь происходит странная вещь:
При работе с Thin он находит подключения в массиве @clients и отправляет им сообщение.
При работе с Puma/Unicorn массив @clients всегда пуст, даже если мы попробуем его в таком порядке (без перезагрузки страницы или чего-то еще):
- Send message from browser ->
@clients.length
is 1, message is delivered - Отправить сообщение через Redis ->
@clients.length
равно 0, сообщение потеряно - Отправить сообщение из браузера ->
@clients.length
по-прежнему равно 1, сообщение доставлено
- Send message from browser ->
Может кто-нибудь разъяснить мне, что мне не хватает?
Связанная конфигурация сервера Puma:
workers 1
threads_count = 1
threads threads_count, threads_count
Связанный промежуточный код:
require 'faye/websocket'
class NotificationsBackend
def initialize(app)
@app = app
@clients = []
Thread.new do
redis_sub = Redis.new
redis_sub.subscribe(CHANNEL) do |on|
on.message do |channel, msg|
# logging @clients.length from here will always return 0
# [..] retrieve user
send_message(user.id, { message: "ECHO: #{event.data}"} )
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|
# [..] retrieve current user
if user
# add ws connection to @clients array
else
# close ws
end
end
ws.on :message do |event|
# [..] retrieve current user
Redis.current.publish({user_id: user.id, { message: "ECHO: #{event.data}"}} )
end
ws.rack_response
else
@app.call(env)
end
end
def send_message user_id, message
# logging @clients.length here will always return correct result
# cs = all connections which belong to that client
cs.each { |c| c.send(message.to_json) }
end
end
@clients
. Кстати, все клиенты хранятся в одном процессе (все они принадлежат одному и тому же PID и массиву@clients
) - person Felix Borzik   schedule 11.06.2015