WebSocket作为一种在单个TCP连接上进行全双工通信的协议,解决了HTTP请求-响应模式无法高效推送数据的问题。在Ruby生态中,faye-websocket把WebSocket协议封装成一套简洁的回调接口,开发者只需要处理几个关键事件就可以构建实时服务。下面我们基于该库实现一个可运行的WebSocket服务器,并深入讨论消息广播、连接保活和生产部署。

faye-websocket的事件模型
faye-websocket并不是一个独立的网络服务器,而是一个协议层,它需要运行在支持异步I/O的Rack服务器上,常见的有Thin、Rainbows、Puma等。通过调用 Faye::WebSocket.load_adapter('thin') 可以告诉库使用对应服务器的异步特性。这样,当HTTP请求到达时,我们可以检测它是否包含Upgrade和Connection头部,如果是WebSocket握手请求,就创建一个WebSocket实例,接管该连接。
该库的核心是EventMachine风格的回调注册。典型的回调包括 on :open、on :message、on :close 和 on :error。其中message事件中的 event.data 既可能是文本,也可能是二进制数据,事件对象会根据帧类型自动处理。理解这些回调的触发时机是开发可靠实时服务的基础。
值得强调的是,faye-websocket并不会为每个连接单独开启一个线程。它复用事件循环,通过回调来响应网络活动。因此,在回调中执行阻塞操作会拖慢整个服务,应避免在回调里进行耗时任务,或将其委托给后台线程。
最小可运行的WebSocket服务端
先安装gem。在Gemfile中添加 gem 'faye-websocket' 和 gem 'thin',然后执行bundle install。接着创建config.ru文件,这是Rack应用的入口。
# config.ru
require 'faye/websocket'
require 'thin'
Faye::WebSocket.load_adapter('thin')
class ChatBackend
KEEPALIVE_TIME = 15
def initialize
@clients = []
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
ws.send('欢迎连接 WebSocket 服务')
end
ws.on :message do |event|
@clients.each { |client| client.send(event.data) }
end
ws.on :close do |event|
@clients.delete(ws)
ws = nil
end
ws.on :error do |event|
@clients.delete(ws)
end
# 返回异步响应
ws.rack_response
else
[200, { 'Content-Type' => 'text/plain' }, ['请使用 WebSocket 协议访问']]
end
end
end
run ChatBackend.new
上述代码中,Faye::WebSocket.websocket?(env) 用来判断当前请求是否要求升级为WebSocket。如果是,通过 Faye::WebSocket.new 创建连接对象,并注册各事件。最后返回 ws.rack_response,这个方法会返回一个特殊的Rack响应,通常类似于 [-1, {}, []],表示连接已由异步处理接管,Rack服务器不应再按普通HTTP响应处理。
运行 bundle exec thin start -p 3000 启动服务。客户端可以使用浏览器中的WebSocket对象连接 ws://localhost:3000,发送任意文本,服务端都会把它广播给所有已连接客户端。这里将 @clients 定义为数组,并在open和close回调中维护成员,实现了最简单的群发逻辑。
处理消息帧、心跳与连接状态
在真实场景中,客户端发送的消息不一定只是文本。浏览器端的 WebSocket.send 可以发送字符串、ArrayBuffer或Blob。faye-websocket在message事件中提供了 event.data,当收到二进制帧时,它的类型可能是二进制字符串或数组,取决于客户端发送方式和库的解析。为了统一处理,可以在服务端检查 event.data.encoding 或使用 event.data.bytes 进行转换。
长连接最怕网络中断、客户端异常退出。faye-websocket支持WebSocket协议层面的ping/pong保活。实例化时传入 ping: 15 表示服务端每15秒发送一个ping帧,如果客户端未响应pong,连接会被标记为不可用并触发close事件。这样可以及时清理僵尸连接,避免 @clients 数组无限增长。同时,客户端也应实现重连逻辑,当网络恢复时自动重新建立连接。
连接状态管理需要注意并发安全问题。虽然Thin默认使用单线程事件循环,但如果有多个worker进程或线程,必须把 @clients 放到共享存储中,比如Redis的Pub/Sub或专门的连接管理进程。否则只能在同一进程内广播,无法覆盖所有用户。
生产环境部署与性能考量
将WebSocket服务部署到生产环境时,反向代理的配置非常关键。Nginx需要正确设置 Upgrade 和 Connection 头,例如 proxy_set_header Upgrade $http_upgrade; 和 proxy_set_header Connection "upgrade";,否则握手会失败。另外,需要合理设置 proxy_read_timeout,避免长时间没有消息的连接被代理层误断开。
在容量规划方面,WebSocket连接会长期占用文件描述符和内存。每个连接在服务器中都是一个对象,如果同时在线用户数很大,单进程的事件循环可能成为瓶颈。可以考虑使用Rainbows!或Puma的多进程模式,并配合Redis完成跨进程消息分发。对于负载均衡,需要使用支持WebSocket的负载均衡器或让固定用户始终连接到同一后端节点。
除了连接数,消息广播效率也值得关注。上面的示例每次收到消息都遍历所有客户端,当客户端数量达到几万时,这个操作会线性增加CPU开销。优化的方向是使用发布订阅中间件,只把需要发送的消息推送到特定连接,或者在客户端做消息过滤。无论如何,faye-websocket提供的事件抽象让这些优化可以围绕业务逻辑逐步展开,而不需要重写协议层代码。
Ruby WebSocketfaye-websocket实时双向通信修改时间:2026-09-24 20:20:02