1. 前言
之前,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
HTML5 定义的 WebSocket 协议,WebSocket 能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。它是一种在单个 TCP 连接上进行全双工通讯的协议。
单工、半双工和全双工是电信计算机网络中的三种通信信道。
- 单工,表示只支持数据在一个方向上传输,在同一时间只有一方能接受或发送信息,例如:电视、广播、收音机。
- 半双工,数据传输允许数据在两个方向上传输,但是在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信,例如:对讲机。
- 全双工,数据通信允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力,在同一时间可以同时接受和发送信息,例如:电话。
2. 简介
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,然后,浏览器和服务器之间就形成了一条快速通道,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。
特点:
- 服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话。
- 建立在 TCP 协议之上,服务器端的实现比较容易。
- 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
- 数据格式比较轻量,性能开销小,通信高效。
- 可以发送文本,也可以发送二进制数据。
- 没有同源限制,客户端可以与任意服务器通信。
- 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
3. 客户端使用
3.1 webSocket构造函数
WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。
var ws = new WebSocket('ws://localhost:8088');
参数为指定连接的 URL。执行上面语句之后,客户端就会与服务器进行连接。
3.2 webSocket 属性
属性 | 描述 |
---|---|
Socket.readyState | 只读属性 readyState 表示连接状态 |
Socket.bufferedAmount | 只读属性 bufferedAmount 表示已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。 |
3.2.1 Socket.readyState
readyState属性返回实例对象的当前状态,可以是以下值:
- 0 - 状态 - CONNECTING,表示连接尚未建立。
- 1 - 状态 - OPEN,表示连接已建立,可以进行通信。
- 2 - 状态 - CLOSING,表示连接正在进行关闭。
- 3 - 状态 - CLOSED,表示连接已经关闭或者连接不能打开。
if (ws.readyState === 2 || ws.readyState === 3) {
console.log("连接已断开");
}
3.2.2 Socket.bufferedAmount
只读属性 bufferedAmount 表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。
var data = new ArrayBuffer(10000000);
socket.send(data);
if (socket.bufferedAmount === 0) {
// 发送完毕
} else {
// 发送还没结束
}
3.3 webSocket 事件属性
实例对象的事件属性,属性值为一个回调函数,如果要指定多个回调函数,可以使用addEventListener方法。
事件 | 事件处理程序 | 描述 |
---|---|---|
open | Socket.onopen | 连接建立时触发 |
message | Socket.onmessage | 客户端接收服务端数据时触发 |
error | Socket.onerror | 通信发生错误时触发 |
close | Socket.onclose | 连接关闭时触发 |
//连接建立时触发
ws.onopen = () => {
console.log("open...")
}
ws.addEventListener('open', function (event) {
ws.send('Hello Server!');
});
//客户端接收服务端数据时触发
ws.onmessage = (msg) => {
console.log(msg.data);
}
ws.addEventListener("message", function(msg) {
console.log(msg.data);
});
//通信发生错误时触发
ws.onerror = () => {
console.log('error')
}
//连接关闭时触发
ws.onclose = () => {
console.log('close')
}
3.4 webSocket 方法
方法 | 描述 |
---|---|
Socket.send() | 向服务器发送数据 |
send()方法用于向服务器发送数据。数据格式可以是String、Blob、ArrayBuffer。
ws.send('your message');
4. 服务端使用
这里我们使用node.js 来实现 websocket的服务端。
4.1 安装ws
模块
npm install ws
//或者使用yarn安装
yarn add ws
4.2 创建websocket实例
const WebSocket = require("ws");
const wServer = new WebSocket.Server({
host:[string], //主机名
port:[number], //端口号
backlog:[number], //未完成的连接队列的最大长度
server:[http.server], //一个预先创建的Node.js HTTP/S服务器
handleProtocols:[Function], //用来处理WebSocket子协议的函数
path:[string], //路径
noServer:[boolean], //不启用任何服务器模式
clientTracking:[boolean], //指定是否跟踪客户端
maxPayload:[number] //允许的最大消息大小(以字节为单位)
});
4.3 方法
- connection,通讯连接完成后触发。
- message,收到客户端的消息后触发。
- close,客户端中断连接时触发。
- send(),给客户端发送消息。
wServer.on("connection",(client)=>{
console.log("On the connected");
//给客户端发送消息
client.send("welcom to back");
client.on("message",(msg)=>{
//收到客户端的消息
})
client.on("close",()=>{
//与客户端中断连接
})
})
5. 案例
完成即时通讯。
客户端代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="msglist"></div>
<textarea id="msg" rows="10" cols="40"></textarea>
<button id="btn">留言</button>
<script>
var $btn = document.querySelector("#btn");
var $msg = document.querySelector("#msg");
var $list = document.querySelector('#msglist');
//实例化websocket
var ws = new WebSocket("ws://localhost:8088");
wsEvent();
//执行事件
function wsEvent() {
//收到服务端发来的数据
ws.onmessage = (msg) => {
$list.innerHTML += msg.data + "</br>";
}
ws.onclose = () => {
//如果连接中断,重新连接
reconnect()
}
ws.onerror = () => {
//如果连接中断,重新连接
reconnect()
}
}
$btn.addEventListener("click", () => {
//发送数据给服务端
ws.send($msg.value);
})
function reconnect() {
//重新连接
if (ws.readyState === 2 || ws.readyState === 3) {
ws = new WebSocket('ws://localhost:8099');
wsEvent();
}
}
</script>
</body>
</html>
服务端代码:
const ws = require("ws");
//创建socket服务
var websocketServer=new ws.Server("http://localhost:8088");
//用来存放客户端的数组
var clientList=[];
//客户端的标识
var id=0;
websocketServer.on("connection",(client)=>{
//通讯连接后,给客户端添加标识,将客户端添加到数组中
client.id="name"+ id++;
clientList[id]=client;
//给客户端发送数据
client.send("welcom to back");
//收到客户端发来的数据
client.on("message",(msg)=>{
//将数据发给所有连接上的客户端
boardcast(client.id+":"+msg);
})
client.on("close",()=>{
//如果客户端断开连接
boardcast(client.id+"下线了");
delete clientList[client.id];
})
})
function boardcast(msg){
//将数据发给所有连接上的客户端
for(let prop in clientList){
clientList[prop].send(msg);
}
}