WebSocket 技术已经逐渐成熟,在生产环境下也已经带给咱们很是多的便利。本文首先会努力阐明 WebSocket 的基本原理,而后会结合实际叙述如何使用它。javascript
首先须要明确的是 WebSocket 的定位。WebSocket 是创建在 HTTP 基础上,为客户端与服务端之间提供文本和二进制数据的全双工通讯的技术。这里有几个地方须要注意:html
WebSocket 是为了知足基于 Web 的日益增加的实时通讯需求而产生的。在传统的 Web 中,要实现实时通讯(好比网页版 QQ),经常使用的方式是经过轮询。在特定的时间间隔,由浏览器向服务器发送 HTTP 请求,而后将最新的数据返回给浏览器。这样的方法最明显的缺点就是须要不断的发送请求,不只占用了宽带,也占用服务器 CPU 资源(没有信息也要接受请求)。java
如此看来,WebSocket 的出现相比于纯 HTTP 实现实时通讯至少有两个优点:node
然而,WebSocket 在实现高效实时通讯的过程却也再也不享有在一些本由浏览器提供的服务和优化,如状态管理、压缩、缓存等。之后浏览器厂商是否是会针对 WebSocket 做出一些服务和优化不得而知,但至少如今 WebSocket 还彻底不足以撼动 HTTP 的地位。总的来讲,WebSocket 弥补了 HTTP 在某些通讯领域的短板,但毫不可能彻底取代 HTTP。react
WebSocket 本来是 HTML5 标准的一部分,随其发展壮大,如今已逐渐变成了独立的协议标准。在客户端,HTML5 提供了一套很是简洁的 API 供咱们使用。git
WebSocket(url: string, protocols?: string[] | string)
复制代码
ws
或者 wss
。一共就只有两个。github
close(code?: number, reason?: string): void
复制代码
关闭WebSocket链接或中止正在进行的链接请求。若是链接的状态已是closed,这个方法不会有任何效果。web
send(data: string | ArrayBuffer | Blob): void
复制代码
经过WebSocket链接向服务器发送数据。express
事件听从原生 Javascript 的两种写法:npm
onevent = handler
addEventListener(event, handler)
支持的事件有 open
,message
,close
和 error
。注意 message
事件在接收到全部数据时出发。
WebSocket 对象的属性用来描述通讯细节和状态。
binaryType: string
表示被传输二进制的内容的类型。取值应当是 'blob'
或者 'arraybuffer'
var ws = new WebSocket('wss://example.com/socket');
ws.binaryType = "arraybuffer"; // 强制将接收的二进制数据转为 ArrayBuffer 类型
ws.onmessage = function(msg) {
if(msg.data instanceof ArrayBuffer) {
processArrayBuffer(msg.data);
} else {
processText(msg.data);
}
}
复制代码
bufferedAmount: number
调用 send()
方法将多字节数据加入到队列中等待传输,可是还未发出。该值会在全部队列数据被发送后重置为 0。而当链接关闭时不会设为0。若是持续调用 send()
,这个值会持续增加。只读。
protocol: string
一个代表服务器选定的子协议名字的字符串。这个属性的取值为构造器传入的 protocols
参数或者之一。
readyState: number
链接的当前状态。取值是 Ready state constants之一。 只读。
url: string
传入构造器的URL。它必须是一个绝对地址的URL。只读。
// Create WebSocket connection.
const socket = new WebSocket('ws://localhost:8080');
// Connection opened
socket.addEventListener('open', function (event) {
socket.send('Hello Server!');
});
// Listen for messages
socket.addEventListener('message', function (event) {
console.log('Message from server', event.data);
});
复制代码
然而以上只是在客户端的 WebSocket 实现,若是没有服务端的配合,WebSocket 是不能进行通讯的。关于 WebSocket 的在服务端的实现有多种方案,下一节会在 Node.js 上进行实现。
以上部分是我的简单总结,若是想详细了解其 API 能够查阅 MDN 上的文档。
使用 WebSocket 其实并不复杂,本节将结合 socket.io 模块进行说明。接下来看看如何构建一个最简单的 WebSocket 应用。
第一步,安装依赖库:
npm install socket.io express
复制代码
第二步,构建客户端 /public/index.html:
<body>
<script src="/socket.io/socket.io.js"></script>
<script> const socket = io('http://localhost:3231'); socket.on('connect', () => { socket.emit('greet', 'Hello, websocket!'); socket.on('uppergreet', data => console.log(data)); }); </script>
</body>
复制代码
注意上面引入 socket.io 的方式,不用怀疑路径是否正确,由于 socket.io 会自动引入相应客户端部分代码,而且暴露一个 io
全局变量。
第三步,构建服务端 server.js:
const express = require('express');
const app = express();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const port = process.env.PORT || 3000;
app.use(express.static(__dirname + '/public'));
io.on('connection', socket => {
socket.on('greet', data => socket.emit('uppergreet', data.toUpperCase()));
});
http.listen(port, () => console.log('listening on port ' + port));
复制代码
最后,运行 node server.js
并用浏览器访问 http://localhost:3000
,打开控制台将会看到输出:
HELLO, WEBSOCKET!
复制代码
能够看到,使用 socket.io 构建 WebSocket 应用相比上一节提到的 WebSocket API 在写法上仍是有比较大的区别的。
除了本节提到的 socket.io 模块,在 Node.js 上还有多种实现,好比 websocket 和 nodejs-websocket,他们不只实现了服务端的 WebSocket 通讯协议,同时也对上一节提到的客户端的 WebSocket API 进行了或多或少的封装,功能完善了不少,在实际工做中已经获得了普遍应用,我的比较倾向于 socket.io 库。
WebSocket 适用于须要高效实时通讯的场景,好比网页聊天,对战游戏等。本节将使用 socket.io 模块进行说明,在 socket.io 的官网上有多个典型使用场景,这里咱们使用官网给出的较为简单的一个画板案例进行说明。 socket.io 官网还给出一个使用 jQuery 开发的聊天应用示例,我根据其最终效果实现了一个 react 版本。
画板案例的关键之处是多个客户端能够同时在一张画板上进行绘画,绘画结果全部客户端都能实时看到,(这个场景是否是和你画我猜的游戏相似?一人画图,多个用户能实时看到画图结果),这里使用 WebSocket 是很是合适的。
首先是服务端的代码 index.js,和上一节的代码很是相似:
const express = require('express');
const app = express();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const port = process.env.PORT || 3000;
app.use(express.static(__dirname + '/public'));
io.on('connection', socket => {
socket.on('drawing', data => socket.broadcast.emit('drawing', data));
});
http.listen(port, () => console.log('listening on port ' + port));
复制代码
接下来运行以下命令:
node index.js
复制代码
而后打开两个浏览器窗口都访问 http://localhost:3000
,而后在一个浏览器内进行绘画,另一个浏览器也能实时看到结果。
能够看到服务端代码很是简洁,在这里关键步骤就是 connection
事件的处理方法,在该方法内部 socket
会监听 drawing
事件,在监听到 drawing
事件以后的回调函数中,再次广播发送 drawing
事件,广播发送的意思就是向全部连上服务端的客户端发送。
而后是客户端的关键代码 /public/main.js:
var socket = io();
socket.on('drawing', onDrawingEvent);
var canvas = document.getElementsByClassName('whiteboard')[0];
canvas.addEventListener('mousedown', onMouseDown, false);
canvas.addEventListener('mouseup', onMouseUp, false);
canvas.addEventListener('mouseout', onMouseUp, false);
canvas.addEventListener('mousemove', throttle(onMouseMove, 10), false);
复制代码
上面代码中的 onDrawingEvent
、mouseUp
和 onMouseMove
方法内部都调用了一个关键的 drawLine
方法:
function drawLine(x0, y0, x1, y1, color, emit){
// ... 绘制 canvas ...
if (!emit) return;
socket.emit('drawing', {
// ... 绘制 canvas 的状态信息 ...
});
}
复制代码
drawLine
方法根据传入的 emit
位来判断是否要向服务器发送 drawing
事件,若是是客户端自己进行的绘制,则会向服务端发送 drawing
事件,不然不会。
以上就是实现一个多人同时绘画的实时应用的总体思路,能够看到当咱们使用 WebSocket 进行通讯,尤为是使用相似 socket.io 这种封装程度很高的工具库时,通讯过程是很是简洁的,咱们把大部分精力放在业务逻辑中便可。
WebSocket 技术的发展已经让开发者无须了解协议内部便可实现实时通讯逻辑,尤为是配合使用 Node.js 做为服务端时,相似 socket.io 的工具库为服务端和客户端都提供了很是方便的调用方法,上手很是快。若是在工做中须要构建高效实时应用,WebSocket 将会是不二选择。