WebSocket 是一种网络通信协议,很多高级功能都需要它。
初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?
答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起。举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。HTTP 协议的这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用“轮询”:每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。
轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。
简介
WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。WebSocket 允许服务器端与客户端进行全双工(full-duplex)的通信。举例来说,HTTP 协议有点像发电子邮件,发出后必须等待对方回信;WebSocket 则是像打电话,服务器端和客户端可以同时向对方发送数据,它们之间存着一条持续打开的数据通道。
其他特点包括:
(1)建立在 TCP 协议之上,服务器端的实现比较容易。
(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。
(4)可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信,完全可以取代 Ajax。
(6)协议标识符是ws
(如果加密,则为wss
,对应 HTTPS 协议),服务器网址就是 URL。
WebSocket 握手
浏览器发出的 WebSocket 握手请求类似于下面的样子:
上面的头信息之中,有一个 HTTP 头是Upgrade
。HTTP1.1 协议规定,Upgrade
字段表示将通信协议从HTTP/1.1
转向该字段指定的协议。Connection
字段表示浏览器通知服务器,如果可以的话,就升级到 WebSocket 协议。Origin
字段用于提供请求发出的域名,供服务器验证是否许可的范围内(服务器也可以不验证)。Sec-WebSocket-Key
则是用于握手协议的密钥,是 Base64 编码的16字节随机字符串。
服务器的 WebSocket 回应如下。
上面代码中,服务器同样用Connection
字段通知浏览器,需要改变协议。Sec-WebSocket-Accept
字段是服务器在浏览器提供的Sec-WebSocket-Key
字符串后面,添加 RFC6456 标准规定的“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”字符串,然后再取 SHA-1 的哈希值。浏览器将对这个值进行验证,以证明确实是目标服务器回应了 WebSocket 请求。Sec-WebSocket-Location
字段表示进行通信的 WebSocket 网址。
完成握手以后,WebSocket 协议就在 TCP 协议之上,开始传送数据。
客户端的简单示例
WebSocket 的用法相当简单。
下面是一个网页脚本的例子,基本上一眼就能明白。
客户端 API
浏览器对 WebSocket 协议的处理,无非就是三件事。
- 建立连接和断开连接
- 发送数据和接收数据
- 处理错误
构造函数 WebSocket
WebSocket
对象作为一个构造函数,用于新建WebSocket
实例。
执行上面语句之后,客户端就会与服务器进行连接。
webSocket.readyState
readyState
属性返回实例对象的当前状态,共有四种。
- CONNECTING:值为0,表示正在连接。
- OPEN:值为1,表示连接成功,可以通信了。
- CLOSING:值为2,表示连接正在关闭。
- CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
下面是一个示例。
webSocket.onopen
实例对象的onopen
属性,用于指定连接成功后的回调函数。
如果要指定多个回调函数,可以使用addEventListener
方法。
webSocket.onclose
实例对象的onclose
属性,用于指定连接关闭后的回调函数。
webSocket.onmessage
实例对象的onmessage
属性,用于指定收到服务器数据后的回调函数。
注意,服务器数据可能是文本,也可能是二进制数据(blob
对象或Arraybuffer
对象)。
除了动态判断收到的数据类型,也可以使用binaryType
属性,显式指定收到的二进制数据类型。
webSocket.send()
实例对象的send()
方法用于向服务器发送数据。
发送文本的例子。
发送 Blob 对象的例子。
发送 ArrayBuffer 对象的例子。
webSocket.bufferedAmount
实例对象的bufferedAmount
属性,表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。
webSocket.onerror
实例对象的onerror
属性,用于指定报错时的回调函数。
WebSocket 服务器
WebSocket 协议需要服务器支持。各种服务器的实现,可以查看维基百科的列表。
常用的 Node 实现有以下三种。
具体的用法请查看它们的文档,本教程不详细介绍了。
参考链接
- Ryan Stewart, Real-time data exchange in HTML5 with WebSockets
- Malte Ubl & Eiji Kitamura,Introducing WebSockets: Bringing Sockets to the Web
- Jack Lawson, WebSockets: A Guide
- Michael W., Starting with Node and Web Sockets
- Jesse Cravens, Introduction to WebSockets
- Matt West, An Introduction to WebSockets
- Maciej Sopyło, Node.js: Better Performance With Socket.IO and doT
- Jos Dirksen, Capture Canvas and WebGL output as video using websockets
- Fionn Kellehe, Understanding Socket.IO
- How to Use WebSockets
- WebSockets - Send & Receive Messages