You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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>

运行与测试

  1. 启动服务器:ruby app.rb
  2. 打开浏览器访问http://localhost:4567
  3. 用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

工业级方案优势

  1. 分布式支持:不管Sinatra部署多少进程/服务器,所有WebSocket客户端都能收到消息
  2. 高性能:Redis Pub/Sub是内存级操作,延迟极低,适合实时场景
  3. 可扩展性:可以结合Redis Stream实现消息持久化,让离线客户端上线后拉取历史消息
  4. 可靠性:Sinatra进程重启不会影响消息通道,新连接会自动重新订阅

生产环境额外建议

  • 给NOTIFY路由添加鉴权(比如API密钥),避免非法请求
  • 监控Redis的Pub/Sub状态(订阅数、消息积压)
  • 优化WebSocket重连逻辑,比如添加指数退避策略

内容的提问来源于stack exchange,提问作者arjun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 09:10:43