最近需要实现一个服务端推送的场景,对 WebSocket 的使用存在一些纠结。
HTTP vs WebSocket
websocket 也可以实现 HTTP 的通信功能,那么具体情况到底该如何选择呢?
- 可能通用的原则
- 默认情况下应该使用传统的 HTTP
- 根据具体情况,说服自己的 API 应该使用 websocket
- 相关对比
- 双工:HTTP 半双工,同时只能向一个方向传输。websocket 全双工,双方都能同时发送和接受数据
- 消息模式:Request-response 与 Bi-directional(双向)
- websocket 支持消息推送
- 开销:HTTP 每个请求都会一定开销,websocket 主要在建立和维持连接上,每个消息的开销较小
- 边缘缓存:HTTP 支持缓存,websocket 则没有
- 适用 HTTP 的场景
- 检索资源:查看比赛结果(HTTP)vs 查看比赛实时比分(websocket)
- 高度可缓存资源
- 幂等性和安全性:能更好的适应通信失败。因为 HTTP 具有广泛的安全性和幂等性期望。WebSocket 协议将这些问题留给了消息层设计(这意味着没有广泛的行业标准)。
- 错误场景:HTTP 允许响应去描述错误信息,比如请求、资源,或者提供状态信息来区分不同的成功场景。WebSocket 协议只对影响连接建立的错误场景提供支持,一旦请求建立和消息交换,任何额外的错误场景必须在消息传递层设计中解决。
- HTTP 支持同步事件
- 使用 websocket 场景
- 快速的反应时间:比如实时聊天
- 实时更新:实时更新资源的状态
- 点对点消息
- 高频消息传递:websocket 只在建立连接时有较大开销,后续消息传递没有额外开销。虽然 HTTP 1.1 允许多个请求复用一个连接,通常会有小的超时时间用于控制资源消耗。websocket 避免了建立了解和发送 HTTP 头部信息的开销,带来大的性能提升。
如果仅从功能角度考虑,websocket 提供了让人心动的服务端推送能力,对于实时性要求比较高的场景十分有用。但设计上不如 HTTP 简单、完善,HTTP 半双工通信机制足够简单,使用的编码过程中更好控制数据状态。与此同时 HTTP 还提供了缓存机制,幂等性和安全性能更好的适应通信失败,对于错误场景更加规范,而这些都是 websocket 所没有的,必须通过良好的设计去解决。
socket.io
本次实现并没有直接使用原生 websocket,而是使用了 socket.io 这个优秀的封装实现。
socket.io 特性
- 自动降级:HTTP 长轮询
- 自动重连
- 包缓冲区
- 确认机制,支持 timeout 设置
- emit 函数最后传递一个回调,服务端确认收到后,回调用它
- on 函数回调函数 payload 中最后是一个函数,可手动调用它表示确认收到,支持传参,默认会自动无参调用(maybe)
- 通过 ack 特性,可实现类似 request-response API
- 广播
- 多路复用:支持多 namespaces
关于断开连接
- 即使是在一个稳定的互联网连接上,断开连接也是常见的和预期的
- 由于网络原因,发送完 emit 后,连接断开,自动重连,如果服务端不做特殊处理,是收不到预期的消息的,能否巧妙利用 Room 群发特性,将 Room 设置为 userId 的方式解决?
- 重要提示:当中间件被执行时,Socket 实例实际上并没有被连接,这意味着如果连接最终失败,不会触发断开事件。
sessionId and socketId,连接建立具体步骤
- 握手成功后,服务端会返回 sessionId,这在后面并发的 HTTP 交流都会被用到
- 使用 sessionId 请求建立连接,这里可提交需要的 auth 信息
- 通过后,请求获取 socketId
- 建立 websocket 连接
- 第一个 HTTP 长轮询请求,一旦建立 WebSocket 连接就关闭
socket.io redis adapter
- 服务于多个 servers 场景,负责将事件广播给所有或一部分客户端
- 即使有了 adapter,当使用多节点服务以及 HTTP 长轮询机制时,sticky sessions 仍然需要