简介
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=