简介

什么是 WebSocket

WebSocket 协议是 HTML5 标准中定义的一种新协议(RFC 6455),它实现了浏览器与服务器之间的全双工通信(full-duplex)。

为什么需要 WebSocket

传统的 HTTP 方案只适用于客户端主动发起请求的场景,无法满足服务器主动推送消息的需求。基于 HTTP 的 Ajax 和长轮询(Long Poll)等技术通过轮询方式工作,消耗大量网络带宽和计算资源。

WebSocket 相对于普通的 TCP Socket 通信,在应用层定义了基本的交互流程,使得 Tornado 等服务器框架和 JavaScript 客户端可以构建标准的 WebSocket 模块。

WebSocket 的特点

WebSocket 的最大特点是服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,实现真正的双向平等对话。

其他特点包括:

  • 建立在 TCP 协议之上,服务器端实现较为容易。
  • 与 HTTP 协议兼容良好,默认端口为 80 和 443,握手阶段使用 HTTP 协议,因此不易被屏蔽,能通过各种 HTTP 代理服务器。
  • 数据格式轻量,性能开销小,通信高效。
  • 可以发送文本或二进制数据。
  • 无同源限制,客户端可与任意服务器通信。
  • 协议标识符为 ws(加密时为 wss),服务器网址即为 URL。

WebSocket 握手原理

WebSocket 的通信原理是在客户端与服务器之间建立 TCP 持久连接,从而实现服务器即时推送消息。

虽然 WebSocket 不是 HTTP 协议,但在握手阶段仍使用 HTTP 进行传输。

客户端握手请求

客户端通过发送包含特殊字段的 HTTP 请求告知服务器建立 WebSocket 连接,特殊字段如下:

GET ws://echo.websocket.org/?encoding=text HTTP/1.1

Origin: http://websocket.org
Connection: Upgrade
Sec-WebSocket-Key: uRovscZjNol/umbTt5uKmw==
Upgrade: websocket
Sec-WebSocket-Version: 13

含义如下:

  • 客户端希望建立 WebSocket 连接,ws 对应 httpwss 对应 https
  • 客户端使用的 WebSocket 版本为 13,密钥为 uRovscZjNol/umbTt5uKmw==(用于标识连接,不用于加密)。
  • Origin 由浏览器添加,WebSocket 协议本身不强制同源策略,但服务器可根据 Origin 拒绝请求。

可能还存在 Sec-WebSocket-Protocol: chat 字段,由用户定义,用于区分同 URL 下不同服务的协议。

服务端握手响应

服务器若同意建立 WebSocket 连接,则返回类似响应,特殊字段如下:

HTTP/1.1 101 Switching Protocols

Connection: Upgrade
Upgrade: WebSocket
Access-Control-Allow-Origin: http://websocket.org
Access-Control-Allow-Credentials: true
Sec-WebSocket-Accept: rLHCkw/SKsO9GAH/ZSFhBATDKrU=
Access-Control-Allow-Headers: content-type
  • 服务器返回 HTTP 101,表示已将连接转换为 WebSocket。
  • Sec-WebSocket-Accept 是服务器确认并处理后的 Sec-WebSocket-Key,用于客户端验证服务器身份。
  • 同源策略取决于服务器实现。

至此,客户端与服务器间建立 TCP 持久连接,双方可随时发送消息。

HTML5 客户端实现

客户端围绕 WebSocket 对象展开。

在 JavaScript 中,通过如下代码初始化 WebSocket 对象,构造函数需传入服务器的 wswss 开头的 URL:

var Socket = new WebSocket(url);

回调函数属性

可为该对象的以下事件指定处理函数:

  • WebSocket.onopen:连接建立时触发。
  • WebSocket.onmessage:收到服务器消息时触发。
  • WebSocket.onerror:通信出错时触发。
  • WebSocket.onclose:连接关闭时触发。

普通属性

  • WebSocket.readyState:返回当前状态,共四种:
    • WebSocket.CONNECTING(0):正在连接。
    • WebSocket.OPEN(1):连接成功,可通信。
    • WebSocket.CLOSING(2):正在关闭。
    • WebSocket.CLOSED(3):已关闭或连接失败。
  • WebSocket.bufferedAmount:表示未发送的二进制数据字节数,可用于判断发送是否完成。

主动操作方法

此外,可通过以下方法主动操作:

  • WebSocket.send(data):向服务器发送消息。
  • WebSocket.close():主动关闭连接。

Tornado 服务端实现

Tornado 定义了 tornado.websocket.WebSocketHandler 类用于处理 WebSocket 请求。

消息处理方法

  • open():新连接建立时调用,可在此获取参数或操作 Cookie。
  • on_message(message):收到客户端消息时调用。
  • on_close():连接关闭时调用。

主动操作方法

  • write_message(message, binary=False):发送消息。
  • close(code=None, reason=None):主动关闭连接。

其他方法

  • check_origin(origin):自定义同源检查策略。origin 参数来自请求头,若无 Origin 则不调用。返回 True 接受,False 拒绝。Tornado 4.0+ 默认仅允许 OriginHost 域名一致的请求。

Nginx 配置

主要涉及 UpgradeConnection 头的设置。

location ~ /api/kernels/ {
    proxy_pass          http://localhost:8888;
    proxy_set_header    Host       $host;
    proxy_http_version  1.1;
    proxy_set_header    Upgrade    "websocket";
    proxy_set_header    Connection "Upgrade";
    proxy_read_timeout  86400;
}

保持连接

可通过以下方式保持连接:

  1. 修改 Nginx 的 proxy_read_timeout
  2. 客户端定时发送心跳包。
  3. 客户端异常断开时自动重连。

参考链接