为什么需要WebSocket
客户端和服务器是通过http协议完成通信交换数据的,http具有一些特点。
- http是单向网络协议。在连接建立后,只许客户端向服务器端发送请求,建立连接后,服务器端才能发送数据,处于被动方。服务器端不能主动推送数据给客户端。
- http协议是无状态的。http经过三次握手后才建立一次连接,在三次握手时客户端向服务器发送连接请求中会包含 identity info(鉴别信息),每次当一个连接结束时,服务器就会将这些鉴别信息丢掉,客户端再次发送 HTTP 请求时,就需要重新发送这些信息。
假如我们的数据不是很频繁的实时更新的话,http协议可以很好满足我们的需求。但是当我们需要获取服务器的实时数据,比如股票的实时行情、聊天室的聊天内容等,这就需要客户端和服务器之间反复进行 HTTP 通信,客户端不断发送请求,去获取当前的实时数据。常采用以下两种方式:
- ajax 轮询。浏览器定时(隔几秒)向服务器发送一次请求,询问是否有新的数据,如果有就返回最新数据,浏览器接收到后将最新数据显示出来。
- Long Polling。Long Polling 的原理与 ajax 轮询的原理差不多,都是采用轮询的方式,它是 Polling 的一种改进。客户端发送请求到服务器后,服务器并不立即响应客户端,而是保持住这次连接,当有新的数据时,才返回给客户端,客户端接收到数据,进行展示,再立即发送一个新的请求给服务器,并重复这个过程。如果服务器的数据长期没有更新,一段时间后,这个请求就会超时,客户端收到超时消息后,再立即发送一个新的请求给服务器。
以上两种方式都是不断的建立HTTP连接,等待服务器处理。具有以下问题。
- ajax 轮询。如若一段时间内服务器没有更新的数据,客户端仍然需要定时发送请求,服务器返回之前的老数据。此时交换没更新的老数据,这样既浪费了带宽,又浪费了 CPU 的利用率。
- Long Polling。Long Polling 虽然解决了带宽和 CPU 利用率的问题,但是如果服务器的数据更新的过快,服务器在返回给客户端一次数据包之后,只能等待客户端再次发送一次请求来之后,才能发送下一个数据包给客户端。在服务器两次返回数据之间,需要等待客户端接收到数据之后处理数据的时间,以及客户端再次发送连接请求后,服务器验证客户端的鉴别信息,并成功建立连接的时间,在网络拥塞的情况下,这个应该是用户不能接受的。
同时, 由于HTTP 协议是无状态的,每次建立连接都需要重新传输 identity info(鉴别信息),这些信息不仅浪费处理时间,而且在网络传输中会耗费大量的流量,往往比实际需要传输的数据量还要大。这样的数据包在网络上周期性的传输,对网络带宽也是一直浪费。
WebSocket在此时应运而生。
WebSocket 协议
WebSocket 是 HTML5 新增的一种通信协议。WebSocket 协议是一种持久化的双向通信协议,它建立在TCP之上,同 HTTP 一样通过 TCP 来传输数据,但它和 HTTP有两点不同:
- WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和客户端都能主动的向对方发送或接收数据,就像 Socket 一样,不同的是 WebSocket 是一种建立在 Web 基础上的一种简单模拟 Socket 的协议。
- WebSocket 需要通过握手连接,类似于 TCP 它也需要客户端和服务器端进行握手连接,连接成功后才能相互通信。
WebSocket 工作流程
为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息 “Upgrade: WebSocket” 表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。
下面是一个典型的 WebSocket 发送请求和响应请求的例子:
浏览器向服务器发起 WebSocket 请求:
这个请求与普通的 HTTP 请求有一些区别
Upgrade: websocket
Connection: Upgrade
表示请求的目的就是要将客户端和服务器端的通讯协议从 HTTP 协议升级到 WebSocket 协议
Sec-WebSocket-Key:
Sec-WebSocket-Extensions:
Sec-WebSocket-Version:
客户端浏览器需要向服务器端提供的握手信息,服务器端解析这些头信息。Sec-WebSocket-Key 是一个 Base64 encode的值,这个是浏览器随机生成的,Sec-WebSocket-Version 是告诉服务器所使用的 Websocket Draft(协议版本)
服务器返回:
服务器端返回以下信息,以表明服务器端获取了客户端的请求,同意创建 WebSocket 连接。
告诉客户端即将升级的是Websocket协议
Upgrade: websocket
Connection: Upgrade
这个则是经过服务器确认,并且加密过后的 Sec-WebSocket-Key。
Sec-WebSocket-Accept
WebSocket demo
index.html
// 打开一个 web socket 这里端口号和上面监听的需一致
var ws = new WebSocket('ws://localhost:3000/');
ws.addEventListener('open', function() {
// Web Socket 已连接上,使用 send() 方法发送数据
setInterval(function() {
ws.send('客户端消息');
},2000);
});
// 这里接受服务器端发过来的消息
ws.addEventListener('message', function(e) {
console.log(e.data)
});
app.js
var ws = require('nodejs-websocket');
var server = ws.createServer(function(socket){
// 事件名称为text(读取字符串时,就叫做text),读取客户端传来的字符串
var count = 1;
socket.on('text', function(str) {
// 在控制台输出前端传来的消息
console.log(str);
//向前端回复消息
socket.sendText('服务器端收到客户端端发来的消息了!' + count++);
});
}).listen(3000);
WebSocket api
可以看
MDN
上关于WebSocket的api.
创建WebSocket
var Socket = new WebSocket(url, [protocol] );
假定我们使用了以上代码创建了 Socket 对象
WebSocket 属性
-
Socket.readyState 只读属性 readyState 表示连接状态,可以是以下值:
0 – 表示连接尚未建立。
1 – 表示连接已建立,可以进行通信。
2 – 表示连接正在进行关闭。
3 – 表示连接已经关闭或者连接不能打开。 - Socket.bufferedAmount.只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。
WebSocket 事件
- open 连接建立时触发
- message 客户端接收服务端数据时触发
- error 通信发生错误时触发
- close 连接关闭时触发
WebSocket 方法
- Socket.send() 使用连接发送数据
- Socket.close() 关闭连接