WebSocket 由来
WebSocket 之前,如果需要在客户端和服务之间双向通信,需要通过 HTTP 轮询来实现, HTTP 轮 询分为轮询与⻓轮询:
其中,轮询是指浏览器通过 JavaScript 启动⼀个定时器,然后以固定的间隔给服务器发请求,询问服 务器有没有新消息,缺点:
- 实时性不够
- 频繁的请求会给服务器带来极⼤的压⼒
⻓轮询是指浏览器发送⼀个请求时,服务器先拖⼀段时间,等到有消息了再回复。这个机制暂时地解 决了实时性问题,但是它带来了新的问题:
- 以多线程模式运⾏的服务器会让⼤部分线程⼤部分时间都处于挂起状态,极⼤地浪费服务器资源
- ⼀个HTTP连接在⻓时间没有数据传输的情况下,链路上的任何⼀个⽹关都可能关闭这个连接, ⽽⽹关是我们不可控的 因此,HTML5 新增了 WebSocket 协议,能够在浏览器和服务器之间建⽴⼀个不受限的双向通信的通道。
为什么WebSocket连接可以实现全双⼯通信⽽HTTP连接不⾏呢? 实际上HTTP协议是建⽴在 TCP协议之上的,TCP协议本身就实现了全双⼯通信,但是HTTP协议的请求-应答机制限制了 全双⼯通信。WebSocket连接建⽴以后,其实只是简单规定了⼀下:接下来,咱们通信就不使 ⽤HTTP协议了,直接互相发数据吧。
WebSocket 的优点:
- 较少的控制开销:在连接创建后,服务器和客户端之间交换数据时,⽤于协议控制的数据包头部 相对较⼩
- 更强的实时性:由于协议是全双⼯的,所以服务器可以随时主动给客户端下发数据
- 保持连接状态:与 HTTP 不同的是,WebSocket 需要先创建连接,这就使得其成为⼀种有状态 的协议,之后通信时可以省略部分状态信息
- 更好的⼆进制⽀持:WebSocket 定义了⼆进制帧,相对 HTTP,可以更轻松地处理⼆进制内容
- 可以⽀持扩展:WebSocket 定义了扩展,⽤户可以扩展协议、实现部分⾃定义的⼦协议
WebSocket 协议
WebSocket 使⽤ ws 或 wss 的统⼀资源标志符(URI),其中 wss 表示使⽤了 TLS 的 WebSocket
ws:// 数据不是加密的,对于任何中间⼈来说其数据都是可⻅的。
wss:// 是基于 TLS 的 WebSocket,类似于 HTTPS 是基于 TLS 的 HTTP),传输安全层在发 送⽅对数据进⾏了加密,在接收⽅进⾏解密
http 通过判断 header 中是否包含 Connection: Upgrade 与 Upgrade: websocket 来判断当前 是否需要升级到 websocket 协议,除此之外,还有其它 header
Sec-WebSocket-Key:浏览器随机⽣成的安全密钥Sec-WebSocket-Version:WebSocket 协议版本Sec-WebSocket-Extensions:⽤于协商本次连接要使⽤的 WebSocket 扩展Sec-WebSocket-Protocol:协议
当服务器同意进⾏ WebSocket 连接时,返回响应码 101
⼀旦 socket 被建⽴,我们就应该监听 socket 上的事件。⼀共有 4 个事件:
open:连接已建⽴message:接收到数据error:WebSocket 错误close:连接已关闭
如果我们想发送消息,可以使⽤ socket.send(data)
let socket = new WebSocket("wss://echo.websocket.org")
socket.onopen = function(e) { console.log("[open] Connection established") // 发送消息
socket.send("My name is an") }
socket.onmessage = function(event) { console.log(`[message] Data received from server: ${event.data}`) }
socket.onclose = function(event) { // ... }
socket.onerror = function(error) { console.log(`[error] ${error.message}`) }
WebSocket 使用 ws 或 wss 的统一资源标志符,通过判断 header 中是否包含 Connection: Upgrade 与 Upgrade: websocket 来判断当前是否需要升级到 websocket 协议,除此之外,它还包含 Sec-WebSocket-Key 、 Sec-WebSocket-Version 等header,当服务器同意 WebSocket 连接时,返回响应码 101 ,它的 API 很简单。
方法:
socket.send(data)socket.close([code], [reason])
事件:
openmessageerrorclose