Websocket 介绍

Websocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许
服务器主动发送信息给客户端。
Websocket是—种持久协议,http是非持久协议

现在很多网站都有实时推送的需求,比如聊天,客服咨询等

早期没有websocket时,通过ajax轮询,由于http请求,服务器无法给浏览器主动发送数据,因此需要浏览器定时的给服务器发送请求(比
如1s一次),服务器把最新的数据响应给浏览器。这种模式的缺点就是浪费性能和资源。

websocket是一种网络协议,允许客户端和服务端全双工的进行网络通讯,服务器可以给客户端发消息,客户端也可以给服务器发消息。

Websocket基本使用

在HTML5中,浏览器已经实现了websocket的API,直接使用即可。

WebSocket-MDN

创建Websocket对象

1
2
3
4
// 参数1: url: 连接的websocket属性
// 参数2: protocol, 可选的,指定连接的协议
// var socket = new WebSocket('ws://echo.websocket.org')
var Socket = new Websocket(url, [protocol] );

WebSocket事件

事件事件处理程序描述
openSocket.onopen连接建立时触发
messageSocket.onmessage客户端接收服务端数据时触发
errorSocket.onerror通信发生错误时触发
closeSocket.onclose连接关闭时触发

Websocket方法

方法描述
Socket.send()使用连接发送数据
Socket.close()关闭连接

示例

一个简单的示例 index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket学习</title>
</head>
<body>
<input type="text">
<button>点我发送</button>
<div style="width: 100px;height: 100px;border:1px solid red"></div>

<script>
const input = document.querySelector("input")
const button = document.querySelector("button")
const div = document.querySelector("div")

// 1. 创建 WebSocket
// 参数1: WebSocket 服务器地址
const socket = new WebSocket("ws://echo.websocket.org")

// 2. open:与服务器连接成功时触发的
socket.addEventListener("open",()=>{
div.innerText = "连接成功"
})

// 3. 主动给WebSocket服务发送消息
button.addEventListener("click",()=>{
var value = input.value
socket.send(value)
})

// 4. 接收WebSocket服务的消息
socket.addEventListener("message",(e)=>{
console.log(e.data);
div.innerHTML = e.data
})

// 5. 服务断开连接 (无法演示)
socket.addEventListener("close",()=>{
div.innerHTML = "断开服务"
})
</script>
</body>
</html>

使用nodejs开发websocket服务

我们刚刚使用了官网提供的echo服务,I接下来我们自己通过nodejs实现一个简单的websocket服务。

使用nodejs开发websocket需要依赖一个第三方包。Nodejs Websocket

项目搭建

安装 nodejs-websocket

1
npm i nodejs-websocket

开发服务程序

使用 app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 导入模块
const ws = require("nodejs-websocket")
// 端口号
const port = 3000

// 创建服务
var server = ws.createServer((conn) => {
console.log("有用户连接了。。。");

// 每当用户传递过来数据、这个text事件就会被触发
conn.on("text", (data) => {
console.log("接收到数据 " + data);
// 给用户响应一个数据
conn.send(data)
})

// 连接断开就会触发
conn.on("close", () => {
console.log("用户断开连接");
})
// 发生错误就会触发
conn.on("error", () => {
console.log("发生错误");
})
})

// 监听端口
server.listen(port, () => {
console.log("连接端口" + port);
})

编辑 index.html

1
2
3
4
// 1. 创建 WebSocket 
// 参数1: WebSocket 服务器地址
// const socket = new WebSocket("ws://echo.websocket.org")
const socket = new WebSocket("ws://localhost:3000")

示例

一个简单的聊天室

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket学习</title>
</head>
<body>
<input type="text">
<button>点我发送</button>
<div></div>

<script>
const input = document.querySelector("input")
const button = document.querySelector("button")
const div = document.querySelector("div")
const TYPE_ENTER = 0
const TYPE_LEAVE = 1
const TYPE_MSG = 2

// 1. 创建 WebSocket
// 参数1: WebSocket 服务器地址
// const socket = new WebSocket("ws://echo.websocket.org")
const socket = new WebSocket("ws://localhost:3000")

// 2. open:与服务器连接成功时触发的
socket.addEventListener("open", () => {
div.innerText = "连接成功"
})

// 3. 主动给WebSocket服务发送消息
button.addEventListener("click", () => {
var value = input.value
socket.send(value)
input.value = ""
})

// 4. 接收WebSocket服务的消息
socket.addEventListener("message", (e) => {
// 将JSON数据转换成对象
var data = JSON.parse(e.data)
var dv = document.createElement("div")
dv.innerText = data.msg + "--------" + data.time
if (data.type == TYPE_ENTER) {
dv.style.color = "green"
} else if (data.type == TYPE_LEAVE) {
dv.style.color = "red"
} else {
dv.style.color = "blue"
}
div.appendChild(dv)
})

// 5. 服务断开连接 (无法演示)
socket.addEventListener("close", () => {
div.innerHTML = "断开服务"
})
</script>
</body>
</html>

app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 导入模块
const ws = require("nodejs-websocket");
// 进入、离开、消息
const TYPE_ENTER = 0
const TYPE_LEAVE = 1
const TYPE_MSG = 2
// 端口号
const port = 3000

// 记录当前连接的用户数量
let count = 0

// 创建服务
var server = ws.createServer((conn) => {
console.log("有用户连接了。。。");
count++
conn.userName = `用户${count}`
// 1. 告诉所有用户,有人加入了聊天室

bordercast({
type: TYPE_ENTER,
msg: `${conn.userName}进入了聊天室`,
time: new Date().toLocaleTimeString()
})

// 每当用户传递过来数据、这个text事件就会被触发
conn.on("text", (data) => {
// 2. 当我们接收到某个用户的信息的时候,告诉所有用户,发送的消息内容是什么
bordercast({
type: TYPE_MSG,
msg: `${conn.userName}: ` +data,
time: new Date().toLocaleTimeString()
})
})

// 连接断开就会触发
conn.on("close", () => {
console.log("用户断开连接");
count--
// 3. 告诉所有的用户,存人离开聊天室
bordercast({
type: TYPE_LEAVE,
msg: `${conn.userName}离开了聊天室`,
time: new Date().toLocaleTimeString()
})
})
// 发生错误就会触发
conn.on("error", () => {
console.log("发生错误");
})
})

// 用于发送广播消息
var bordercast = (msg)=>{
// server.connections 表示所有连接用户
server.connections.forEach(item=>{
// 需要转换成 JSON 数据
item.send(JSON.stringify(msg))
})
}

// 监听端口
server.listen(port, () => {
console.log("连接端口" + port);
})

如果使用原生的websocket进行开发,会比较麻烦,比如支持的事件太少,发送的数据只能是字符串格式的,提供的api也很少,类似于广播这种方法都没有,需要自己封装。

socket.io基本使用

socket.io

安装 socket.io

1
npm i socket.io

socket.emit: 表示触发某个事件 如果需要给浏览器发数据.需要触发浏览器注册某个事件
socket.on: 表示的注册某个事件 如果需要获取浏览器的数据,需要注册一个事件,等待浏览器触发

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const http = require('http')
const fs = require('fs')
const app = http.createServer()
app.on("request", (req, res) => {
fs.readFile(__dirname + '/index.html', function (err, data) {
if (err) {
res.writeHead(500)
return res.end('Error loading index.html')
}
res.writeHead(200)
res.end(data)
})

})

app.listen(3000, () => {
console.log("服务器启动成功");
});
const io = require("socket.io")(app)
// 监听用户连接
// socket 表示用户的连接
// socket.emit 表示触发某个事件 如果需要给浏览器发数据.需要触发浏览器注册某个事件
// socket.on 表示的注册某个事件 如果需要获取浏览器的数据,需要注册一个事件,等待浏览器触发
io.on("connection",socket=>{
console.log("新用户连接");
// 服务端发送消息
// socket.emit("send",{name:"zykj"})
socket.on("hehe",data=>{
console.log(data);
socket.emit("send",data)
})
})

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!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>
<script src="/socket.io/socket.io.js"></script>
<script>
// 连接socket服务
// 参数:服务器地址
const socket = io("http://localhost:3000");
// 发送事件
socket.emit("hehe",{name:"zykj",age:18})
// 接收事件
socket.on('send',data=>{
console.log(data);
})
</script>
</body>
</html>

基于socket.io开发完整的聊天室

部分内容讲解

FileReader MDN: https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader

在上面的项目中就是在本地上传图片时用到了 FileReader 将图片转换成 Base64 编码

1
2
//开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个data: URL格式的Base64字符串以表示所读取文件的内容。
FileReader.readAsDataURL()

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 通过 change 事件访问被选择的文件
// 发送图片
$('#file').on("change", function () {
var file = this.files[0]
//需要把这个文件发送到服务器,借助于H5新增的fileReader
var fr = new FileReader()
fr.readAsDataURL(file)
fr.onload = function () {
socket.emit("sendImage", {
username: username,
avatar: avatar,
// 读取完成后,数据保存在对象的result属性中
img: fr.result
})
}
})

参考:H5 FileReader对象

scrollIntoView

项目中、当我们像每次发完消息时、希望定位到最新的一条、也就是滚动到最底部、我们就需要使用 scrollIntoView

示例:

1
2
3
4
function scroll() {
// scrollIntoView(false) 代表滚动到最底部
$('.box-bd').childnet(':last').get(0).scrollIntoView(false)
}

参考 scrollIntoView()的用法