如何在Sinatra中实现NOTIFY请求数据实时推送至WebSocket客户端?
我来帮你解决这个问题!你现在的核心痛点是Sinatra的NOTIFY请求处理和WebSocket服务器之间没法互通数据对吧?其实只要让这两个模块能共享消息通道就行,下面分基础单进程实现和工业级分布式方案来给你详细说明。
一、基础单进程实现(适合开发/小规模场景)
我们可以用sinatra-websocket gem让Sinatra直接处理WebSocket请求,同时维护一个线程安全的连接集合,当NOTIFY请求进来时,遍历集合推送数据。
1. 依赖准备
先在Gemfile里添加需要的依赖:
gem 'sinatra' gem 'sinatra-websocket' gem 'thin' # Thin服务器原生支持WebSocket
执行bundle install安装依赖。
2. Sinatra核心代码(app.rb)
require 'sinatra' require 'sinatra/websocket' # 线程安全的WebSocket连接集合,用Mutex保证多线程下的操作安全 connections = [] conn_mutex = Mutex.new set :server, 'thin' # 主页:返回包含WebSocket客户端的页面 get '/' do erb :index end # WebSocket连接路由 get '/ws' do halt 400, "Not a WebSocket request" unless request.websocket? request.websocket do |ws| ws.onopen do # 线程安全地添加新连接 conn_mutex.synchronize { connections << ws } ws.send("✅ 已连接到通知服务器") end ws.onclose do # 移除断开的连接 conn_mutex.synchronize { connections.delete(ws) } end ws.onerror do |e| puts "❌ WebSocket错误: #{e.message}" conn_mutex.synchronize { connections.delete(ws) } end end end # 监听自定义NOTIFY方法的路由 post '/notify', method: :NOTIFY do # 读取请求体数据(支持JSON/RAW等格式) notify_data = request.body.read puts "📥 收到NOTIFY数据: #{notify_data}" # 推送数据给所有在线WebSocket客户端 conn_mutex.synchronize do connections.each do |conn| begin conn.send(notify_data) rescue => e puts "❌ 推送失败: #{e.message}" connections.delete(conn) end end end status 200 "📤 数据已推送给客户端" end
3. 前端页面(views/index.erb)
<!DOCTYPE html> <html> <head> <title>实时通知演示</title> <style> #messages { margin-top: 20px; padding: 10px; border: 1px solid #eee; border-radius: 4px; } .message-item { margin: 8px 0; padding: 6px; background: #f5f5f5; border-radius: 3px; } </style> </head> <body> <h1>实时通知接收端</h1> <div id="messages"></div> <script> // 建立WebSocket连接 const ws = new WebSocket(`ws://${window.location.host}/ws`); ws.onopen = () => console.log("✅ WebSocket已连接"); ws.onclose = () => { console.log("❌ WebSocket断开,3秒后尝试重连"); setTimeout(() => window.location.reload(), 3000); }; // 接收并展示服务器推送的消息 ws.onmessage = (event) => { const msgDiv = document.createElement('div'); msgDiv.className = 'message-item'; msgDiv.textContent = `📢 ${event.data}`; document.getElementById('messages').appendChild(msgDiv); }; </script> </body> </html>
运行与测试
- 启动服务器:
ruby app.rb - 打开浏览器访问
http://localhost:4567 - 用curl发送NOTIFY请求测试:
curl -X NOTIFY http://localhost:4567/notify -d '{"type":"alert","content":"服务器状态更新"}'
此时浏览器页面会实时收到这条消息。
二、工业级分布式解决方案(适合生产环境)
上面的单进程方案在多进程/多服务器部署时会失效(每个进程维护独立的连接集合),生产环境推荐用Redis Pub/Sub做消息中转,实现跨进程/跨服务器的消息同步。
1. 依赖调整
Gemfile添加Redis依赖:
gem 'redis' gem 'connection_pool' # 用于Redis连接池,提升性能
2. 修改后的核心代码(app.rb)
require 'sinatra' require 'sinatra/websocket' require 'redis' require 'connection_pool' # 初始化Redis连接池,避免频繁创建销毁连接 redis_pool = ConnectionPool.new(size: 5, timeout: 5) { Redis.new(host: 'localhost', port: 6379) } NOTIFY_CHANNEL = 'app_notifications' set :server, 'thin' get '/' do erb :index end get '/ws' do halt 400, "Not a WebSocket request" unless request.websocket? request.websocket do |ws| # 单独创建Redis订阅客户端(订阅不能用连接池,因为是长连接) redis_sub = Redis.new(host: 'localhost', port: 6379) # 订阅通知频道 redis_sub.subscribe(NOTIFY_CHANNEL) do |on| on.message do |_, msg| ws.send(msg) rescue puts "❌ 推送消息失败" end end ws.onopen do ws.send("✅ 已连接到通知服务器,订阅频道成功") end ws.onclose do redis_sub.unsubscribe(NOTIFY_CHANNEL) redis_sub.close end end end post '/notify', method: :NOTIFY do notify_data = request.body.read puts "📥 收到NOTIFY数据: #{notify_data}" # 用连接池发布消息到Redis频道 redis_pool.with { |redis| redis.publish(NOTIFY_CHANNEL, notify_data) } status 200 "📤 数据已发布到Redis频道" end
工业级方案优势
- 分布式支持:不管Sinatra部署多少进程/服务器,所有WebSocket客户端都能收到消息
- 高性能:Redis Pub/Sub是内存级操作,延迟极低,适合实时场景
- 可扩展性:可以结合Redis Stream实现消息持久化,让离线客户端上线后拉取历史消息
- 可靠性:Sinatra进程重启不会影响消息通道,新连接会自动重新订阅
生产环境额外建议
- 给NOTIFY路由添加鉴权(比如API密钥),避免非法请求
- 监控Redis的Pub/Sub状态(订阅数、消息积压)
- 优化WebSocket重连逻辑,比如添加指数退避策略
内容的提问来源于stack exchange,提问作者arjun
相关产品推荐
相关产品推荐

