1. 什么是websocket
WebSocket 是一种网络通信协议
2. 为什么需要websocket
Websocket 协议,是为了弥补HTTP协议【单向通信】的缺点。
详细来讲,HTTP只支持由客户端发起通信,一旦服务端的数据发生变化,是不可能主动向客户端推送消息的。
一般而言,此时都会使用【轮询 + HTTP】的解决方案,即每隔一段时间就建立一次HTTP连接,来帮助客户端追加更新。但效率非常低,非常浪费资源。
websocket 则支持服务器推送技术。
即,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,实现真正的双向平等对话。
客户端和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
3. websocket简介
websocket 特点
(1)实时通信:全双工协议,延迟更少,效率更高。
(2)与HTTP协议有良好的兼容性:默认端口也是80 和 443,并且握手阶段采用HTTP协议,可以通过各种HTTP代理服务器,不容易被屏蔽。
(3)控制开销:用于协议控制的数据包头部相对较小,数据格式比较简洁,性能开销比较小,通信高效。
(4)二进制传输:可以发送文本,也可以发送二进制数据。
(5)没有同源限制:可以实现客户端和服务器的任意通信。
(6)实现简单:建立在TCP协议之上,服务器端的实现比较简单。
webSocket 与 HTTP 的异同
相同点:
(1)都是基于TCP的应用层协议。
(2)都使用Request/Response模型进行连接的建立。
(3)在连接的建立过程中对错误的处理方式相同,在这个阶段WebSocket可能返回和HTTP相同的返回码。
不同点:
(1)HTTP协议基于Request/Response,只能做单向传输,是半双工通信,而WebSocket是全双工通信
- 半双工通信:单向流动, 服务器不主动推送数据给客户端
- 全双工通信:服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
(2)http是无状态的,所以请求得到响应以后就关闭了,无状态的好处是服务器不需要存储相关会话信息。缺点是每次http请求和响应都会发送关于请求的冗余信息;而WebSocket 只需要建立一次Request/Response消息对,之后都是TCP连接,避免了需要多次建立Request/Response消息对而产生的冗余头部信息。节省了大量流量和服务器资源。
(3)WebSocket在建立握手连接时,数据是通过HTTP协议传输的,但在建立连接之后,真正的数据传输阶段是不需要HTTP协议参与的。而http需要需要三次握手。
(4)WebSocket传输的数据是二进制流,是以帧为单位的,HTTP传输的是明文传输,是字符串传输。
4. 客户端的简单应用
事件:onopen、onmessages、onclose、onerror
const ws = new Websocket("wss://echo.websocket.org");
// onopen:用于执行连接成功后的回调
ws.onopen = function(evt){
consoel.log("===========连接开始==========");
ws.send("Hello WebSocket!");
}
// onmessage:用于指定收到服务器数据后的回调
ws.onmessage = function(evt){
console.log("收到数据:" + evt.data);
ws.close();
}
// onopen:用于执行连接断开后的回调
ws.onclose = function(evt){
consoloe.log("==========连接关闭==========");
}
ws.onerror = function(event) {
consoloe.error("==========连接有误==========");
};
以上 `onopen、onmessage、onclose`如果需要指定多个回调,就可以使用监听形式。
// 以onopen为例
ws.addEventListener("open",(event)=>{
const code = event.code;
const reason = event.reason;
const wasClean = event.wasClean;
})
方法:send() | close()
send() : 在连接成功后关闭前,发送消息(onopen后和onclose前才可发送消息)。
close() : 关闭连接
`webSocket` 实例对象的`send()`,用于向服务器发送数据。代码为:ws.send("")
webSocket.readyState
Websocket.CONNECTING: 值为0,表示正在连接
Websocket.OPEN:值为1,表示连接成功
Websocket.CLOSING:值为2,表示连接关闭
Websocket.CLOSED:值为3,表示连接已经关闭,或者打开连接失败
switch (ws.readyState) {
case WebSocket.CONNECTING:
// do something
break;
case WebSocket.OPEN:
// do something
break;
case WebSocket.CLOSING:
// do something
break;
case WebSocket.CLOSED:
// do something
break;
default:
// this never happens
break;
}
webSocket.bufferedAmount
bufferedAmount 是一个数值,表示还有多少字节的二进制数据没有发送,常用于判断发送数据是否结束
var data = new ArrayBuffer(10000000);
socket.send(data);
if (socket.bufferedAmount === 0) {
// 发送完毕
} else {
// 发送还没结束
}
// 限制客户端向服务器发送数据的频率
const ws = new WebSocket("ws://echo.websocket.org/updates");
ws.onopen = function(){
setInterval(()=>{
if(ws.bufferedAmount < 10240){
ws.send(getApplicationState());
}
},1000);
}
5. 服务端的应用
常用的 Node 实现有以下三种。
- WebSockets
- Socket.IO
- WebSocket-Node
6. 简单聊天室的实现——node
6.1 服务端
1. 创建一个空文件夹, `npm init -y` 初始化
2. `npm install websocket`
3. 创建`server.js`
// 因为websocket 是基于 http 协议的,因此 http 和 websocket 模块都需要被引入
const http = require("http");
const websocket = require("websocket").server;
const httpServer = http.createServer().listen(8080,()=>{
console.log("Hello localhost:8080!");
})
// websocket 是基于 http 协议的,这两个参数必填
const websocketServer = new websocket({
httpServer: httpServer,
autoAcceptConnections:false
})
// 创建通信线池:不用数据库的话,一般用数组来模拟一个虚拟线池。
const connecttionArr = [];
// 用npm包中的API:on()监听事件
websocketServer.on('request', function(request) {
// 这是一次客户端发送的消息,只要连接没断开,这个连接就需要被存储起来
const connection = request.accept()
// 每次接收一个链接,将它存放在数组里面
connecttionArr.push(connection)
// 监听客户端发送的消息
connection.on('message', function(message) {
console.log(message);
// 消息转换编码
// 发送消息给客户端(取出所有的链接,即可广播到各个客户端)
for(let i =0; i<connecttionArr.length ;i++){
connecttionArr[i].send(message.utf8Data);
}
})
})
配置好`server.js`后,`node server.js`启动服务器即可
6.2 客户端
客户端的websocket服务就是浏览器的API调用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 需要一进来如浏览器就要建立链接 -->
<!-- 点击按钮发送消息给服务器 -->
输入姓名:<input type="text" id="uName">
<br>
输入消息:<input type="text" id="context">
<button id="btn"> 点击发送消息 </button>
<div id="charRoom"></div>
</body>
<script>
// 用户名
const uName = document.getElementById('uName')
// 文本框内容
const context = document.getElementById('context')
// 点击按钮
const btn = document.getElementById('btn')
// 要显示聊天室的区域
const charRoom = document.getElementById('charRoom')
// 实例化 websocket
const websocket = new WebSocket('ws://localhost:8080')
// 建立链接
websocket.onopen = function() {
console.log(websocket.readyState);
}
btn.onclick = ()=>{
// 将用户名和要发送的内容放在一个对象中,一起传送给后端
const info = {
uName : uName.value,
context: context.value
}
// 清空文本框的内容
uName.value = "";
context.value = "";
// 通过 websockte 发送消息
websocket.send(JSON.stringify(info));
}
// 监听服务器传送来的消息
websocket.onmessage = function(event){
const chatInfo = JSON.parse(event.data);
charRoom.innerHTML += `
<strong>${chatInfo.uName}:</strong>
<span>${chatInfo.context}</span>
<br />
`
}
</script>
</html>
6.3 效果
7. 简单聊天室的实现——socket.io
7.1 服务端
1. 创建一个空文件夹, `npm init -y` 初始化
2. `npm install socket.io`
3. 创建`server.js`
// socket.io 框架
// 引入 http 模块的 createServer 方法
const { createServer } = require('http')
// 引入 socket.io 的 Server 模块
const { Server } = require('socket.io')
// 实例化 httpServer
const httpServer = createServer();
// 初始化 socket.io
const io = new Server(httpServer, {
cors: {
origin: '*',
methods: ['GET', 'POST']
}
})
// 使用 socket.io 来建立连接
io.on('connection', socket => {
socket.on('sendMsg', data => {
io.emit('pushMsg', data)
console.log(data);
})
})
// 创建服务器,进行监听
httpServer.listen(8080, function() {
console.log('http://localhost:8080');
})
7.2 客户端
客户端需要引入`socket.io`框架
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.socket.io/4.4.0/socket.io.min.js"
integrity="sha384-1fOn6VtTq3PWwfsOrk45LnYcGosJwzMHv+Xh/Jx5303FVOXzEnw0EpLv30mtjmlj" crossorigin="anonymous">
</script>
</head>
<body>
输入姓名:<input type="text" id="uName">
<br>
输入消息:<input type="text" id="context">
<button id="btn"> 点击发送消息 </button>
<div id="charRoom"></div>
</body>
<script>
// 用户名
const uName = document.getElementById('uName')
// 文本框内容
const context = document.getElementById('context')
// 点击按钮
const btn = document.getElementById('btn')
// 要显示聊天室的区域
const charRoom = document.getElementById('charRoom')
// 使用 socket.io 框架与服务器相连接
const socket = io.connect('http://localhost:8080')
// 点击发送消息的事件
btn.onclick = function() {
// 将用户名和要发送的内容放在一个对象中,一起传送给后端
const values = {
uName: uName.value,
context: context.value
}
// 清空文本框的内容
uName.value = ''
context.value = ''
// 通过 socket.io 框架向服务器发送数据
socket.emit('sendMsg', values)
}
// 使用 socket.on 方法监听服务端发送过来的数据
socket.on('pushMsg', data => {
// 添加到页面上
charRoom.innerHTML += `
<strong>${data.uName}:</strong>
<span>${data.context}</span>
<br />
`
})
</script>
</html>