WebSocket 学习 (一)

本贴最后更新于 1427 天前,其中的信息可能已经渤澥桑田

简介

下一个项目需要做一个对于 rabbitmq 里队列交换机等资源的监控项目,需要用到的技术有 rabbitmq 的 API 和 websocket 协议(其实最主要的是前端的大屏,领导喜欢看大屏哈哈哈哈),其实以前也用过 websocket,但是都只是使用,并没有对 websocket 协议本身有过深入的分析了解,趁这次知识储备的机会对这个协议做一个简单的了解

与 HTTP 协议的关系

同样作为应用层的协议,WebSocket 在现代的软件开发中被越来越多的实践,和 HTTP 有很多相似的地方,这里将它们简单的做一个纯个人、非权威的比较:

比较

相同点

  • 都是基于 TCP 的应用层协议。
  • 都使用 Request/Response 模型进行连接的建立。
  • 在连接的建立过程中对错误的处理方式相同,在这个阶段 WS 可能返回和 HTTP 相同的返回码。
  • 都可以在网络中传输数据。

不同点

  • WS 使用 HTTP 来建立连接,但是定义了一系列新的 header 域,这些域在 HTTP 中并不会使用。
  • WS 的连接不能通过中间人来转发,它必须是一个直接连接。
  • WS 连接建立之后,通信双方都可以在任何时刻向另一方发送数据。
  • WS 连接建立之后,数据的传输使用帧来传递,不再需要 Request 消息。
  • WS 的数据帧有序。

依赖关系

WebSocket 依赖于 HTTP 连接

每个 WebSocket 连接都始于一个 HTTP 请求。具体来说,WebSocket 协议在第一次握手连接时,通过 HTTP 协议在传送 WebSocket 支持的版本号,协议的字版本号,原始地址,主机地址等等一些列字段给服务器端:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Version: 13

注意,关键的地方是,这里面有个 Upgrade 首部,用来把当前的 HTTP 请求升级到 WebSocket 协议,这是 HTTP 协议本身的内容,是为了扩展支持其他的通讯协议。如果服务器支持新的协议,则必须返回 101:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

至此,HTTP 请求物尽其用,如果成功出发 onopen 事件,否则触发 onerror 事件,后面的传输则不再依赖 HTTP 协议。总结一下,这张图比较贴切:

1320353140479aaa27d34358a150c54d32fe8455.png

WebSocket 设计上就是天生为 HTTP 增强通信(全双工通信等),所以在 HTTP 协议连接的基础上是很自然的一件事,并因此而能获得 HTTP 的诸多便利。第二,这诸多便利中有一条很重要,基于 HTTP 连接将获得最大的一个兼容支持,比如即使服务器不支持 WebSocket 也能建立 HTTP 通信,只不过返回的是 onerror 而已,这显然比服务器无响应要好的多。

API 介绍

简单来说,WebSocket 是一种协议,与 HTTP 协议一样位于应用层,都是 TCP/IP 协议的子集。HTTP 协议是单向通信协议,只有客户端发起 HTTP 请求,服务端才会返回数据。而 WebSocket 协议是双向通信协议,在建立连接之后,客户端和服务器都可以主动向对方发送或接受数据。WebSocket 协议建立的前提需要借助 HTTP 协议,建立连接之后,持久连接的双向通信就与 HTTP 协议无关了。

WebSocket 协议的目标是在一个独立的持久连接上提供全双工双向通信。客户端和服务器可以向对方主动发送和接受数据。在 JS 中创建 WebSocket 后,会有一个 HTTP 请求发向浏览器以发起请求。在取得服务器响应后,建立的连接会使用 HTTP 升级将 HTTP 协议转换为 WebSocket 协议。也就是说,使用标准的 HTTP 协议无法实现 WebSocket,只有支持那些协议的专门浏览器才能正常工作。

由于 WebScoket 使用了自定义协议,所以 URL 与 HTTP 协议略有不同。未加密的连接为 ws://,而不是 http://。加密的连接为 wss://,而不是 https://。

使用 JavaScript 是实现 WebScoket 协议相对简单,以下是 WebSocket APIs

// 打开WebSocket, 传递的参数url没有同源策略的限制。
let websocket = new WebSocket(url)

// 监听open事件,在成功建立websocket时向url发送纯文本字符串数据(如果是对象则必须序列化处理)。
websocket.onopen = () => {
  if (websocket.readyState === WebSocket.OPEN) {
    websocket.send('hello world')
  }
}

// 监听message事件,在服务器响应时接受数据。返回的数据存储在事件对象中。
websocket.onmessage = e => {
  let data = e.data
  console.log(data)
}

// 监听error事件,在发生错误时触发,连接不能持续。
websocket.onerror = () => {
  console.log('websocket connecting error!!')
}

// 监听close事件,在连接关闭时触发。只有close事件的事件对象拥有额外的信息。可以通过这些信息来查看关闭状态
websocket.onclose = e => {
  let clean = e.wasClean // 是否已经关闭
  let code = e.code // 服务器返回的数值状态码。
  let reason = e.reason //服务器返回的消息。

注意,WebScoket 不支持 DOM2 语法为事件绑定事件处理程序,因此必须使用 DOM0 级语法来每个事件绑定事件处理程序。

// correct!
websocket.onerror = () => {}
// error!
websocket.addEventListener('error', () => {})

WebSocket 是应用层协议,是 TCP/IP 协议的子集,通过 HTTP/1.1 协议的 101 状态码进行握手。也就是说,WebSocket 协议的建立需要先借助 HTTP 协议,在服务器返回 101 状态码之后,就可以进行 websocket 全双工双向通信了,就没有 HTTP 协议什么事情了

参照 wiki 握手协议的例子:并对一些字段进行说明。

**Connection:**Connection 必须设置为 Upgrade,表示客户端希望连接升级

**Upgrade:**Upgrade 必须设置为 WebSocket,表示在取得服务器响应之后,使用 HTTP 升级将 HTTP 协议转换(升级)为 WebSocket 协议。

**Sec-WebSocket-key:**随机字符串,用于验证协议是否为 WebSocket 协议而非 HTTP 协议

**Sec-WebSocket-Version:**表示使用 WebSocket 的哪一个版本。

**Sec-WebSocket-Accept:**根据 Sec-WebSocket-Accept 和特殊字符串计算。验证协议是否为 WebSocket 协议。

**Sec-WebSocket-Location:**与 Host 字段对应,表示请求 WebSocket 协议的地址。

**HTTP/1.1 101 Switching Protocols:**101 状态码表示升级协议,在返回 101 状态码后,HTTP 协议完成工作,转换为 WebSocket 协议。此时就可以进行全双工双向通信了。

总结

你可以把 WebSocket 看成是 HTTP 协议为了支持长连接所打的一个大补丁,它和 HTTP 有一些共性,是为了解决 HTTP 本身无法解决的某些问题而做出的一个改良设计。在以前 HTTP 协议中所谓的 keep-alive connection 是指在一次 TCP 连接中完成多个 HTTP 请求,但是对每个请求仍然要单独发 header;所谓的 polling 是指从客户端(一般就是浏览器)不断主动的向服务器发 HTTP 请求查询是否有新数据。这两种模式有一个共同的缺点,就是除了真正的数据部分外,服务器和客户端还要大量交换 HTTP header,信息交换效率很低。它们建立的“长连接”都是伪.长连接,只不过好处是不需要对现有的 HTTP server 和浏览器架构做修改就能实现。

WebSocket 解决的第一个问题是,通过第一个 HTTP request 建立了 TCP 连接之后,之后的交换数据都不需要再发 HTTP request 了,使得这个长连接变成了一个真.长连接。但是不需要发送 HTTP header 就能交换数据显然和原有的 HTTP 协议是有区别的,所以它需要对服务器和客户端都进行升级才能实现。在此基础上 WebSocket 还是一个双通道的连接,在同一个 TCP 连接上既可以发也可以收信息。此外还有 multiplexing 功能,几个不同的 URI 可以复用同一个 WebSocket 连接。这些都是原来的 HTTP 不能做到的。

另外说一点技术细节,因为看到有人提问 WebSocket 可能进入某种半死不活的状态。这实际上也是原有网络世界的一些缺陷性设计。上面所说的 WebSocket 真.长连接虽然解决了服务器和客户端两边的问题,但坑爹的是网络应用除了服务器和客户端之外,另一个巨大的存在是中间的网络链路。一个 HTTP/WebSocket 连接往往要经过无数的路由,防火墙。你以为你的数据是在一个“连接”中发送的,实际上它要跨越千山万水,经过无数次转发,过滤,才能最终抵达终点。在这过程中,中间节点的处理方法很可能会让你意想不到。

比如说,这些坑爹的中间节点可能会认为一份连接在一段时间内没有数据发送就等于失效,它们会自作主张的切断这些连接。在这种情况下,不论服务器还是客户端都不会收到任何提示,它们只会一厢情愿的以为彼此间的红线还在,徒劳地一边又一边地发送抵达不了彼岸的信息。而计算机网络协议栈的实现中又会有一层套一层的缓存,除非填满这些缓存,你的程序根本不会发现任何错误。这样,本来一个美好的 WebSocket 长连接,就可能在毫不知情的情况下进入了半死不活状态。

而解决方案,WebSocket 的设计者们也早已想过。就是让服务器和客户端能够发送 Ping/Pong Frame(RFC 6455 - The WebSocket Protocol)。这种 Frame 是一种特殊的数据包,它只包含一些元数据而不需要真正的 Data Payload,可以在不影响 Application 的情况下维持住中间网络的连接状态。

总结参考 https://www.zhihu.com/question/20215561/answer/40250050

  • WebSocket

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

    48 引用 • 206 回帖 • 399 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...