# 一、WebSocket课程概述
# 1、WebSocket简介
首先需要知道websocket不是纯前端静态即可,需要搭配服务端一起实现。
平常我们常用的ajax是基于http的,但http的缺陷也很明显,就是必须由客户端(前端基本针对浏览器而言)发起,否则服务器无法主动向客户端发送任何消息与数据。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。
如果我们非要实现服务端主动发送消息到前端,就得被迫使用“轮询”,简单点说就是隔一段时间就向服务器做一个请求。但轮询的缺点也很明显:效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。
解决这个问题,最好的方式,就是 websocket。
本教程将借助 express 与 express-ws 来实现对话聊天功能。
# 2、线上预览地址(移动端)
http://47.93.114.103:3002/ (opens new window)
# 3、项目效果
# 二、项目创建与静态资源
# 1、创建项目并初始化
新建一个空白项目 ws-study
,然后执行 npm init -y
进行初始化。
# 2、安装依赖
websocket学习需要手动搭建服务端,这里使用express及其插件来完成。因此安装如下依赖:
yarn add express express-ws nodemon
然后配置 package.json
的 script:
{
"scripts": {
"start": "nodemon server/server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
}
2
3
4
5
6
执行 npm run start
即可打开服务器并热更新代码。
# 3、静态资源
项目根目录下新建一个 public
目录,在其中新建入口文件 index.html
与 index.css
文件。html任意写点内容,css稍后用来写样式。另外可以网上下载两张头像,建议不要太大,放到 public
下。
将这两张头像图片上传到 bitbug (opens new window) 生成favicon.ico,放到 public
下,后续有用。
# 4、express资源访问
项目根目录下创建 server
文件夹,在其中创建 server.js
,然后写入:
const express = require("express")
const expressWs = require("express-ws")
const app = express();
const port = 3030;
expressWs(app)
// 中间件
app.use(express.static('public')) // 主动访问public下的文件
app.get("*", (req,res)=>{})
app.listen(port, ()=>{
console.log(`Server is running at wx://localhost:${port}`)
})
2
3
4
5
6
7
8
9
10
11
12
这样我们可以直接访问 http://localhost:3030
,就可以看到public下的 index.html
。
# 5、角色新建
public
下新建两个角色(用html代表角色),这里我新建 lisi.html
和 wangwu.html
。我们书写最基本的聊天界面的结构与样式。大致效果如下:
# 6、配置角色访问地址
public
下有了 lisi.html
和 wanwu.html
后,我们可以在 server.js
中添加:
// 中间件
app.use('/master', express.static('public/master.html'))
app.use('/visitor', express.static('public/visitor.html'))
app.use(express.static('public'))
2
3
4
这样,访问 http://localhost:3030/master
即可访问 master.html
,访问 http://localhost:3030/visitor
即可访问 visitor.html
。
# 三、服务端websocket
在 server
文件夹下新建 websocket.js
,写入:
const express = require("express")
const expressWs = require("express-ws")
const router = express.Router()
expressWs(router)
// 李四访问的地址
router.ws("/lisi", ws=>{
ws.send("李四连接上了")
})
// 王五访问的地址
router.ws("/wangwu", ws=>{
ws.send("王五连接上了")
})
module.exports = router;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
上面的代码中,ws.send
是回调中必须存在的方法,用于向客户端返回数据。
然后引入 server.js
中:
const websocket = require('./websocket')
app.use('/ws', websocket) // 访问/ws路径时,调用websocket
2
# 四、客户端websocket
# 1、websocket方法
客户端websocket有这么几个方法我们需要先了解:
- new WebSocket(url) : 创建WebSocket 实例,客户端会与服务器进行连接。
- readyState : 返回实例对象的当前状态,共有四种:
CONNECTING:值为0,表示正在连接。
OPEN:值为1,表示连接成功,可以通信了。
CLOSING:值为2,表示连接正在关闭。
CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
2
3
4
- open : 用于指定连接成功后的回调函数。
- onclose : 用于指定连接关闭后的回调函数。
- onmessage : 用于指定收到服务器数据后的回调函数。
- send : 用于向服务器发送数据。
- onerror : 用于指定报错时的回调函数。
# 2、客户端连接
我们需要在 lisi.html
页面的js中写入:
<script>
// 实例化,建立连接
const lisiWs = new WebSocket("ws://localhost:3030/ws/lisi")
//连接成功回调
lisiWs.onopen = () => {
console.log(`连接成功,连接状态为: ${lisiWs.readyState}`)
}
// 接收服务器返回的数据
lisiWs.onmessage = msg => {
console.log(`返回的数据为:${msg}`)
}
// 连接断开时触发
lisiWs.onclose = () => {
console.log(`连接已经断开`)
}
// 连接发生错误时触发
lisiWs.onerror = () => {
console.log(`连接发生错误`)
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
这个时候访问 http://localhost:3030/lisi
,就会得到连接成功回调,如果没有意外,会打印出来 李四连接上了
。假若出了什么报错,也可在 onerror
中打印回调参数查看报错。
# 3、向服务端发送信息
假如你的发送信息方式为:在textarea(id为txt)中按回车发送,那么你的代码应该为:
txt.onkeyup = function(e){
if(e.keyCode===13){
// txt.value就是textarea的值,拿到后send(发送)给后端,此处代码省略对文本非空的判断等其他逻辑
lisiWs.send(txt.value);
}
}
2
3
4
5
6
# 4、服务端接收并返回信息
// 声明一个数组,用来存放李四发出来的信息
let lisiArr = [];
// 接收李四传来的信息,存储后,再返回给李四
router.ws('/lisi', ws=>{
ws.on('message', msg=>{
lisiArr.push(msg);
ws.send(msg);
})
})
2
3
4
5
6
7
8
9
10
# 五、另一个客户端的接收
我们要知道,每个客户端,除了要发送,还要接收。我们先把王五的接收功能做了。
# 1、服务端定时查看数组
王五的接收功能,其实就是定时查看李四的数组lisiArr,如果数组中有内容,就提取出来返回给王五客户端,并删除lisiArr中的这一项。
/*
本项目我将用 /wangwu 来代表王五发送的地址
用 /wangwu1 来代表王五监听lisiArr的地址
*/
router.ws('/wangwu1', ws=>{
let timer = null;
// 监听到连接断开时,停止定时器,避免性能损耗
ws.on('close', ()=>{
if(timer){ clearInterval(timer) }
})
timer = setInterval(()=>{
// 每一秒检查一次lisiArr
if(lisiArr.length>0){
let m = lisiArr[0]; // 获取第一项
lisiArr.splice(0, 1); // 删除第一项
ws.send(m); // send方法会阻断后续代码,因此必须写在最后
}
}, 1000)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 2、客户端监听
在 wangwu.html
中:
<script>
const wangwu1Ws = new WebSocket('ws://localhost:3030/ws/wangwu1')
wangwu1Ws.onopen = () => {}
wangwu1Ws.onmessage = res => {
// 得到监听回来的数据,渲染到页面中
console.log(res.data)
}
wangwu1Ws.onclose = function (e) {
console.log("服务器关闭");
}
wangwu1Ws.onerror = function (e) {
console.log("连接出错");
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 六、备用知识点
# 1、滚动到底部
当一个盒子内部的内容增加,并且超过该盒子的高度时,我们希望它自动滚动到底部:
var box = document.getElementById('box');
box.scrollTo({
top: box.scrollHeight,
behavior: "smooth"
})
2
3
4
5
6

