# 一、WebSocket课程概述

# 1、WebSocket简介

首先需要知道websocket不是纯前端静态即可,需要搭配服务端一起实现。

平常我们常用的ajax是基于http的,但http的缺陷也很明显,就是必须由客户端(前端基本针对浏览器而言)发起,否则服务器无法主动向客户端发送任何消息与数据。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。

如果我们非要实现服务端主动发送消息到前端,就得被迫使用“轮询”,简单点说就是隔一段时间就向服务器做一个请求。但轮询的缺点也很明显:效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。

解决这个问题,最好的方式,就是 websocket

本教程将借助 express 与 express-ws 来实现对话聊天功能。

# 2、线上预览地址(移动端)

http://47.93.114.103:3002/ (opens new window)

# 3、项目效果

websocket_demo

# 二、项目创建与静态资源

# 1、创建项目并初始化

新建一个空白项目 ws-study ,然后执行 npm init -y 进行初始化。

# 2、安装依赖

websocket学习需要手动搭建服务端,这里使用express及其插件来完成。因此安装如下依赖:

yarn add express express-ws nodemon
1

然后配置 package.json 的 script:

{
  "scripts": {
    "start": "nodemon server/server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
}
1
2
3
4
5
6

执行 npm run start 即可打开服务器并热更新代码。

# 3、静态资源

项目根目录下新建一个 public 目录,在其中新建入口文件 index.htmlindex.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}`)
})
1
2
3
4
5
6
7
8
9
10
11
12

这样我们可以直接访问 http://localhost:3030 ,就可以看到public下的 index.html

# 5、角色新建

public 下新建两个角色(用html代表角色),这里我新建 lisi.htmlwangwu.html 。我们书写最基本的聊天界面的结构与样式。大致效果如下:

image-20211110162940773

# 6、配置角色访问地址

public 下有了 lisi.htmlwanwu.html 后,我们可以在 server.js 中添加:

// 中间件
app.use('/master', express.static('public/master.html'))
app.use('/visitor', express.static('public/visitor.html'))
app.use(express.static('public'))
1
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;
1
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
1
2

# 四、客户端websocket

# 1、websocket方法

客户端websocket有这么几个方法我们需要先了解:

  • new WebSocket(url) : 创建WebSocket 实例,客户端会与服务器进行连接。
  • readyState : 返回实例对象的当前状态,共有四种:
CONNECTING:值为0,表示正在连接。
OPEN:值为1,表示连接成功,可以通信了。
CLOSING:值为2,表示连接正在关闭。
CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
1
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>
1
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);
  }
}
1
2
3
4
5
6

# 4、服务端接收并返回信息

// 声明一个数组,用来存放李四发出来的信息
let lisiArr = [];

// 接收李四传来的信息,存储后,再返回给李四
router.ws('/lisi', ws=>{
    ws.on('message', msg=>{
        lisiArr.push(msg);
        ws.send(msg);
    })
})
1
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)
})
1
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>
1
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"
})
1
2
3
4
5
6
支付宝打赏 微信打赏
Last Updated: 12/16/2021, 5:56:10 PM