这篇文档是基于webSocket的学习笔记,在之前想学习相关知识,都是散乱了,有的就只有服务端,有的只有客户端,所以自己写一篇记录下,我写的这个demo台使用nodejs前端简单使用html单页面。
最终效果
首先先明白为啥要学习websocket,它可以解决什么问题,他的运行原理是什么?
WebSocket解决了传统的HTTP协议在实时性 和双向通信方面的限制问题。
首先,传统的HTTP协议是一种无状态的协议,即服务器不能主动推送数据给客户端,而是需要客户端发送请求后服务器才能响应。这导致了在实时性要求较高的场景下,如聊天应用 、在线游戏 等,传统的HTTP协议无法满足 实时更新的需求。而WebSocket通过建立一条持久化的连接,允许服务器主动向客户端推送数据,从而实现了实时更新。
解决了传统HTTP协议在实时性和双向通信方面的限制问题,使得客户端和服务器之间可以实现实时更新和双向通信,适用于实时性要求较高的应用场景。
服务端
首先先创建一个node应用,创建一个webSocketdemo
文件,运行以下代码初始化项目,并安装ws库(node支持的一个WebSocket的库)
js
//初始化一个 node 项目
npm init -y
//安装依赖
npm i -save ws
//初始化项目安装依赖(虽然这时没有啥依赖,单要保持良好习惯)
npm i
运行结果如下
在index.js
文件中写下列代码,require引入ws
库并设置端口port: 8080
,这时创建名叫wss
的websocket连接,我们可以通过wss.on
添加监听事件
js
var webSocketsServer = require('ws').Server;
var wss = new webSocketsServer({ port: 8080 });
var clients = [];
// 为服务器添加 connection 事件监听,当有客户端连接到服务端时,立刻将客户端对象保存进数组中
wss.on('connection', function (ws) {
});
修改node项目的配置文件package.json
,并在scripts
添加一条指令node index.js
(注:其实也可以在控制台执行运行node index.js
并不会影响websocket运行),node项目为了统一管理所以写在这里,我们可以通过npm run dev
这条命令来启动项目。 启动效果npm run dev
:
启动效果node index.js
:
这时我们可以通过访问页面判断是否已经启动websocket, localhost:8080
运行原理
如上图,提示Upgrade Required
说明websocket启动成功,为啥会提示Upgrade Required
(需要升级),这里需要解释websocket运行原理:
websocket是h5带来的新通信协议,如上图,它和传统http一样进行连接需要进行三次握手、四次挥手,连接完成后又进行一个websocket握手
,当websocket握手
的具体意义是浏览器告诉服务器,我现在发起的这个请求不是传统的http请求而是websocket,需要双方将http协议升级为websocket
服务器接收到后马上升级协议,这时双发握手成功,实现协议转换。因此我们在测试的时候在浏览器中输入localhost:8080由于协议不对,所以进行提示Upgrade Required
以下是更为官方解释websocket,附带websocket原文:RFC 6455 - The WebSocket Protocol (ietf.org):
WebSocket 是一种在单个 TCP 连接上全双工通信的协议。它在客户端和服务器之间提供了实时的、持久的连接,使得数据能够以较低的延迟进行传输。 WebSocket 的原理如下:
- 客户端发起 WebSocket 握手请求,请求中包含了支持 WebSocket 的协议版本号等信息。
- 服务器收到 WebSocket 握手请求后,检查客户端的请求头信息,并进行协议升级处理。如果服务器也支持 WebSocket,则返回一个 101 切换协议的响应给客户端。
- 客户端收到服务器的响应后,也会进行协议升级处理。如果协议切换成功,客户端和服务器之间的连接就建立起来了。
- 建立连接后,客户端和服务器可以通过发送帧(frame)进行数据的传输。每个帧由一个固定格式的首部和可选的载荷组成。首部包含了帧的类型、长度等信息,载荷则是要传输的数据。
- 客户端和服务器都能够发送和接收数据。数据可以是文本或二进制形式。客户端和服务器都可以随时发送帧,而不需要等待对方的响应。
- 当客户端或服务器想要关闭连接时,可以发送一个特殊的帧来表示关闭请求。收到关闭请求的一方也会发送一个关闭帧作为响应,并关闭连接。
ws api 使用
找到一篇关于wsAPI介绍 Node Websocket/ws模块 API 参考 - 简书 (jianshu.com)
完整的index.js
代码,再进行解释:
js
//引入ws库创建一个websocket
var webSocketsServer = require('ws').Server;
//设置端口为8080
var wss = new webSocketsServer({ port: 8080 });
//使用数组保存所有的连接
var clients = [];
// 为服务器添加 connection 事件监听,当有客户端连接到服务端时,立刻将客户端对象保存进数组中
wss.on('connection', function (ws) {
console.log('Client 创建连接');
clients.push(ws);
console.log(`目前共有${clients.length}个客户端连接`);
// 为每个 client 对象绑定 message 事件,当某个客户端发来消息时,自动触发
ws.on('message', function (msg) {
// console.log(msg, typeof msg);
console.log('收到消息' + msg);
// 遍历 clients 数组中每个其他客户端对象,并发送消息给其他客户端
for (var c of clients) {
c.send(msg.toString());
}
});
// 当客户端断开连接时触发该事件
ws.onclose = function () {
var index = clients.indexOf(this);
clients.splice(index, 1);
console.log("有" + clients.length + "客户端在线")
}
});
从上述代码可以看出在wss.on
给服务器添加 connection 事件监听,当有客户端连接到服务端时,立刻将客户端对象ws
保存进数组中,其中每次连接新设备将新的连接通过 clients.push(ws);
添加到数组clients
中。对每个具体客户端对象ws
添加 ws.on('message', function (msg) {})
的监听事件。用来接收客户端ws
发送的消息,并通过for循环遍历 clients 数组中每个其他客户端对象,通过c.send(msg.toString());
将消息发送给其他客户端。当客户端断开连接时触发ws.onclose
事件并将对应的客户端对象ws
从数组clients
移除clients.splice(index, 1);
。
客户端
当我们写完服务端,我们可以撰写index.html
文件。
html
<!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>
<input />
<button>发送</button>
<button id="close">关闭</button>
<div>
<span>所有消息</span>
<div name="allMsg"></div>
</div>
</body>
<script>
//连接websocket
var ws = new WebSocket("ws://127.0.0.1:8080/");
//连接成功则进行以下函数
ws.onopen = function () {
console.log("连接成功");
}
//websocket 放在win对象中,方便调试
window.ws = ws;
</script>
</html>
运行代码可看见以下界面
我们也可以多开几个窗口来看下效果,如下图创建三个标签,所以后台数组clients
会记录三个ws
对象。
断开websocket
之前我们在代码最后一行写上 window.ws = ws;
目的是可以通过全局对象直观查看ws连接参数,方便我们理解调试
目前我们连接三个客户端,断开连接我们可以通过关闭浏览器标签来强制执行,当然我们也可以调用ws.close()
断开websocket连接 在优化下,将这个断开连接事件绑定在关闭按钮上
js
//给关闭按钮添加点击事件
document.getElementById("close").onclick = function () {
console.log("断开连接");
ws.close();
};
发送消息
我们可以调用websocket的send方法实现发消息,如下,在控制台通过ws.send("这是发送的第一条信息");
实现发送消息,服务端对应接收到消息 回顾服务端index.js
接收消息如何处理,通过ws.on('message',function (msg) {})
监听用户发送消息,通过for循环将消息转发给每个客户端 for (var c of clients) {c.send(msg.toString()); }
js
// 为每个 client 对象绑定 message 事件,当某个客户端发来消息时,自动触发
ws.on('message', function (msg) {
// console.log(msg, typeof msg);
console.log('收到消息' + msg);
// 遍历 clients 数组中每个其他客户端对象,并发送消息给其他客户端
for (var c of clients) {
c.send(msg.toString());
}
});
对应的index.html
页面稍微做优化下,用户将需要写的内容输入到input框,点击发送按钮时添加点击事件调用ws.send
方法发送消息。
js
//websocket发送消息
document.getElementsByTagName("button")[0].onclick = function () {
var msg = document.getElementsByTagName("input")[0].value;
ws.send(msg);
console.log("发送消息:" + msg);
}
此时页面还缺少一块内容,接收务端的消息,接收消息可以调用ws.onmessage
方法接收处理相关信息,例如将消息展示页面上:
js
//接受参数
ws.onmessage = function (e) {
console.log("接受消息",e.data);
var allMsg = document.getElementsByName("allMsg")[0];
//将消息添加到页面
var div = document.createElement("div");
div.innerHTML = e.data;
allMsg.appendChild(div);
}
完整的index.html
代码:
html
<!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>
<input />
<button>发送</button>
<button id="close">关闭</button>
<div>
<span>所有消息</span>
<div name="allMsg"></div>
</div>
</body>
<script>
//连接websocket
var ws = new WebSocket("ws://127.0.0.1:8080/");
ws.onopen = function () {
console.log("连接成功");
}
//websocket发送消息
document.getElementsByTagName("button")[0].onclick = function () {
var msg = document.getElementsByTagName("input")[0].value;
ws.send(msg);
console.log("发送消息:" + msg);
}
//给关闭按钮添加点击事件
document.getElementById("close").onclick = function () {
console.log("断开连接");
ws.close();
};
//接受参数
ws.onmessage = function (e) {
console.log("接受消息",e.data);
var allMsg = document.getElementsByName("allMsg")[0];
//将消息添加到页面
var div = document.createElement("div");
div.innerHTML = e.data;
allMsg.appendChild(div);
}
window.ws = ws;
</script>
</html>
最终效果