简介
WebSocket 是一种和 HTTP 不同的协议。两者都位于 OSI 模型的应用层,并且都依赖于传输层的 TCP 协议,WebSocket 复用了 HTTP 的握手通道。
虽然和 HTTP 不同,但是 RFC 6455 中规定:it is designed to work over HTTP ports 80 and 443 as well as to support HTTP proxies and intermediaries(WebSocket 通过 HTTP 端口 80 和 443 进行工作,并支持 HTTP 代理和中介),从而使 WebSocket 与 HTTP 协议兼容。
WebSocket 协议的最大特点就是服务器可以主动向客户端发送信息,客户端也可以向服务器发送信息,实现了双向通信。
特点
连接状态
WebSocket 创建连接之后就成为一种有状态的协议,之后的通信可以省略部分状态信息。而 HTTP 请求可能需要在每个请求都携带状态信息(如身份认证等)。
实时性
由于 WebSocket 协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于 HTTP 请求需要等待客户端发起请求服务端才能响应,延迟明显更少。
数据开销
WebSocket 协议有着较少的控制开销。在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。在不包含扩展的情况下,对于服务器到客户端的内容,此头部大小只有 2 至 10 字节(和数据包长度有关);对于客户端到服务器的内容,此头部还需要加上额外的 4 字节的掩码。相对于HTTP 请求每次都要携带完整的头部,此项开销显著减少了。
二进制支持
WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容。
扩展
WebSocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议。如部分浏览器支持压缩等。
压缩
相对于 HTTP 压缩,WebSocket 在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率。
建立连接
WebSocket 请求通过 HTTP/1.1 协议的 101 状态码进行握手,同样包含请求报文和响应报文。不同浏览器的 WebSocket 最大连接数量不同:
- IE:6
- Chrome:256
- Firefox:200
- Safari:1273(Mac版本)
客户端:申请协议升级
1 | GET /chat |
Connection
:该字段必须设置为Upgrade
,表示客户端要升级协议。Upgrade
:必须设置为websocket
,表示客户端要求升级到WebSocket协议。Sec-WebSocket-Key
:浏览器随机生成基于 Base64 的密钥。服务器基于这个密钥生成响应首部的Sec-WebSocket-Accept
。Origin
:该字段是必须的,如果缺少Origin
字段,服务器需要回复 HTTP 403 状态码(禁止访问)。Sec-WebSocket-Version
:表示支持的 WebSocket 版本(RFC6455 要求使用的版本是 13)。如果服务端不支持该版本,会返回一个 Sec-WebSocket-Version 头,里面包含服务端支持的版本号。Sec-WebSocket-Protocol
:表示不仅要传输任何数据,还要传输 SOAP 或 WAMP(“The WebSocket Application Messaging Protocol”)协议中的数据。Sec-WebSocket-Extensions
:由浏览器自动发送,其中包含其支持的所有扩展的列表。
服务器:响应协议升级
1 | 101 Switching Protocols |
Sec-WebSocket-Accept
:服务器通过请求报文中的Sec-WebSocket-Key
加密过后生成的值,浏览器通过这个值确保请求和响应对应,同样可以防止恶意连接或避免普通 HTTP 请求被误认为WebSocket协议。Sec-WebSocket-Protocol
:表示服务器仅支持所请求的子协议中的 SOAP。Sec-WebSocket-Extensions
:表示服务器支持客户端请求中的扩展。
Sec-WebSocket-Accept 的生成过程
- 将请求报文中的
Sec-WebSocket-Key
加上一个特殊的字符串。 - 计算 SHA-1 摘要,然后进行 Base64 编码,结果就为
Sec-WebSocket-Accept
头的值。1
toBase64(sha1(Sec-WebSocket-Key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11))
1
2
3
4
5
6
7
8
9
10const crypto = require('crypto');
const magic = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
const secWebSocketKey = 'w4v7O6xFTi36lq3RNcgctw==';
let secWebSocketAccept = crypto.createHash('sha1')
.update(secWebSocketKey + magic)
.digest('base64');
console.log(secWebSocketAccept);
// Oy4NRAQ13jhfONC7bP8dTKb4PTU=