WebSocket 是一种计算机通信协议,提供了通过单个 TCP 连接的全双工通信通道。WebSocket 协议于2011年由IETF标准化为RFC 6455。允许Web应用程序使用该协议的当前API规范称为WebSockets。它是由WHATWG维护的一种持续更新的标准,是W3C的WebSocket API的继任者。
WebSocket 与 HTTP 不同。这两种协议都位于OSI模型的第7层,并依赖于第4层的TCP。尽管它们不同,RFC 6455规定WebSocket“设计用于在HTTP端口443和80上工作,同时支持HTTP代理和中介”,因此与HTTP兼容。为了实现兼容性,WebSocket握手使用HTTP Upgrade标头将协议从HTTP协议更改为WebSocket协议。
WebSocket协议通过提供一种标准化的方式,使服务器能够向客户端发送内容,而无需首先由客户端请求,以及允许消息在保持连接打开的同时来回传递,从而实现了Web浏览器(或其他客户端应用程序)与Web服务器之间的交互,开销较小,比半双工的HTTP轮询等替代方案更为高效,从而促进了与服务器之间的实时数据传输。通过这种方式,客户端和服务器之间可以进行双向持续对话。通常,通信是通过TCP端口号443(或未安全连接的情况下的端口80)进行的,这对于使用防火墙阻止非Web互联网连接的环境是有益的。类似的双向浏览器-服务器通信已经通过临时技术,如Comet或Adobe Flash Player,在非标准化的方式下实现。
大多数浏览器都支持该协议,包括Google Chrome、Firefox、Microsoft Edge、Internet Explorer、Safari和Opera。
与HTTP不同,WebSocket提供全双工通信。此外,WebSocket在TCP之上启用消息流。TCP仅处理字节流,没有消息的固有概念。在WebSocket之前,可以使用Comet通道实现端口80的全双工通信;然而,Comet的实现并不简单,并且由于TCP握手和HTTP标头开销,对于小消息来说效率低下。WebSocket协议旨在解决这些问题,而不会 compromise web 的安全性假设。
WebSocket协议规范定义了ws(WebSocket)和wss(WebSocket Secure)作为两个新的统一资源标识符(URI)方案,分别用于未加密和加密连接。除了方案名称和片段(即不支持#),URI的其余组件都定义为使用URI通用语法。
使用浏览器开发工具,开发人员可以检查WebSocket握手以及WebSocket帧。
历史
WebSocket最初在HTML5规范中被引用为TCPConnection,作为基于TCP的套接字API的占位符。在2008年6月,由Michael Carter领导的一系列讨论导致了WebSocket的第一个版本的诞生。
"WebSocket"这个名称是由Ian Hickson和Michael Carter在#whatwg IRC聊天室上的合作中不久之后创造的,随后由Ian Hickson编写并包括在HTML5规范中。2009年12月,Google Chrome 4成为第一个默认启用WebSocket标准支持的浏览器。WebSocket协议的开发随后于2010年2月从W3C和WHATWG小组转移到了IETF,并由Ian Hickson进行了两次修订。
在协议在多个浏览器中默认启用之后,RFC 6455于2011年12月由Ian Fette最终确定。
RFC 7692引入了WebSocket的压缩扩展,使用DEFLATE算法按消息基础进行压缩。
JavaScript客户端示例
// Creates new WebSocket object with a wss URI as the parameter
const socket = new WebSocket('wss://game.example.com/ws/updates');
// Fired when a connection with a WebSocket is opened
socket.onopen = function () {
setInterval(function() {
if (socket.bufferedAmount == 0)
socket.send(getUpdateData());
}, 50);
};
// Fired when data is received through a WebSocket
socket.onmessage = function(event) {
handleUpdateData(event.data);
};
// Fired when a connection with a WebSocket is closed
socket.onclose = function(event) {
onSocketClose(event);
};
// Fired when a connection with a WebSocket has been closed because of an error
socket.onerror = function(event) {
onSocketError(event);
};
Web服务器实现
自2013年以来,Nginx已支持WebSocket,在版本1.3.13中实现了WebSocket应用程序的反向代理和负载均衡器。
Apache HTTP服务器自2013年7月以来支持WebSocket,在版本2.4.5中实现。
Internet Information Services在发布Windows Server 2012的版本8中添加了对WebSocket的支持。
自2017年以来,lighttpd已支持WebSocket,在版本1.4.46中实现。lighttpd的mod_proxy可以充当WebSocket应用程序的反向代理和负载均衡器。lighttpd的mod_wstunnel可以构建WebSocket隧道,以传输任意数据,包括JSON格式,到后端应用程序。
协议
协议握手
为了建立WebSocket连接,客户端发送WebSocket握手请求,服务器返回WebSocket握手响应,如下所示。
客户端请求(就像在HTTP中一样,每一行以\r\n结束,最后必须有一个额外的空白行):
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
服务器响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
握手是从HTTP请求/响应开始的,允许服务器在同一端口上处理HTTP连接以及WebSocket连接。一旦连接建立,通信会切换到一个双向的二进制协议,不符合HTTP协议。
除了Upgrade标头外,客户端还会发送一个包含Base64编码的随机字节的Sec-WebSocket-Key标头,服务器会在Sec-WebSocket-Accept标头中回复该键的哈希值。这旨在防止缓存代理重新发送以前的WebSocket对话,不提供任何身份验证、隐私或完整性。哈希函数会将固定字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11(UUID)附加到Sec-WebSocket-Key标头中的值(未从Base64解码),然后应用SHA-1哈希函数,并使用Base64对结果进行编码。
RFC6455要求密钥必须是一个随机选择的16字节值,经过Base64编码,即Base64中的24字节(最后两个字节为==)。尽管一些宽松的HTTP服务器允许较短的密钥出现,但许多现代HTTP服务器将拒绝具有“无效Sec-WebSocket-Key标头”的错误请求。
基础帧协议
一旦连接建立,客户端和服务器可以在全双工模式下互相发送WebSocket数据或文本帧。数据被最小幅度地分帧,带有一个小的标头,后跟有效载荷。WebSocket传输被描述为“消息”,其中一个单独的消息可以选择性地分割为多个数据帧。这可以允许发送消息,其中初始数据是可用的,但消息的完整长度未知(它发送一个数据帧接一个数据帧,直到到达末尾并用FIN位进行确认)。
客户端到服务侧掩码
从客户端发送的有效载荷数据应由掩码键(masking key)进行掩码处理。掩码键是客户端选择的一个4字节的随机值,应该是不可预测的。掩码键的不可预测性对于防止恶意应用程序选择已经存在的字节至关重要。以下算法用于对有效载荷数据进行掩码处理。
j = i MOD 4
transformed-octet[i] = original-octet[i] XOR masking-key-octet[j]