theme: channing-cyan
前言
学习一下 WebSocket
协议,在这之前我使用 nodejs-websocket
这个包解决,使用比较简单,但是我发现那样不利于自己对该协议的理解,于是我打算用比较官方的依赖 ws
包来进行 WebSocket
协议实践。
创建项目下载依赖
$ mkdir project && cd project
$ npm init -y
$ npm i ws @types/ws
服务端
首先我们先编服务端代码 webSocket.js。
引入我们的 ws 和协议包 http,并创建 WebSocket 服务。
import {WebSocketServer} from 'ws'
import {createServer} from 'http'
const httpServer = createServer()
// 创建 WebSocket 服务
const wss = new WebSocketServer({
noServer: true
})
然后我们编写握手事件 upgrade
,我们这里选择当接收到的请求头属性 sec-websocket-protocol
为 conn
时进行连接。
import {WebSocketServer} from 'ws'
import {createServer} from 'http'
const httpServer = createServer()
// 创建 WebSocket 服务
const wss = new WebSocketServer({
noServer: true
})
// 握手事件
httpServer.on('upgrade', (req, socket, head) => {
if (req.headers['sec-websocket-protocol'] === 'conn') {
wss.handleUpgrade(req, socket, head, (ws) => {
wss.emit('connection', ws, req)
})
}
})
然后我们编写连接事件,当连接成功时,我们可以往客户端发送一个连接的标志。
import {WebSocketServer} from 'ws'
import {createServer} from 'http'
const httpServer = createServer()
// 创建 WebSocket 服务
const wss = new WebSocketServer({
noServer: true
})
// 握手事件
httpServer.on('upgrade', (req, socket, head) => {
if (req.headers['sec-websocket-protocol'] === 'conn') {
wss.handleUpgrade(req, socket, head, (ws) => {
wss.emit('connection', ws, req)
})
}
})
// 连接事件
wss.on('connection', (ws) => {
// 往客户端发送 connected 事件,我们使用 type 来进行事件标识,这样方便客户端处理
ws.send(JSON.stringify({type: 'connected'}))
})
当然,连接完成我们同时可以在连接事件中添加对客户端传来数据的监听,就实现了客户端往服务端发送事件。
wss.on('connection', (ws) => {
ws.send(JSON.stringify({type: 'connected'}))
// message 监听事件事件,客户端传来的消息都走这里。
ws.on('message', (data, isBinary) => {
// 由于我们无法确定传过来的数据类型,因此要用 isBinary 区分 buffer 转化为字符串
const receiveData = isBinary ? data : data.toString()
console.log(receiveData)
})
})
在加个对异常错误 error 的监听。
wss.on('error', (e) => {
const {code} = e
if (code !== 'EADDRINUSE') {
console.error(
`WebSocket server error:\n${e.stack || e.message}`,
);
}
});
这样我们就完成了 httpServer 的 WebSocket 协议服务端内容,但是假如我们有多个 httpServer 呢?我们不如把 WebSocket 协议内容封装成一个函数,方便复用。
import {WebSocketServer} from 'ws'
import {createServer} from 'http'
const createWebSocketServer = (httpServer) => {
const wss = new WebSocketServer({
noServer: true
})
// 握手事件
httpServer.on('upgrade', (req, socket, head) => {
if (req.headers['sec-websocket-protocol'] === 'conn') {
wss.handleUpgrade(req, socket, head, (ws) => {
wss.emit('connection', ws, req)
})
}
})
wss.on('connection', (ws) => {
ws.send(JSON.stringify({type: 'connected'}))
ws.on('message', (data, isBinary) => {
const receiveData = isBinary ? data : data.toString()
console.log(receiveData)
})
})
wss.on('error', (e) => {
const {code} = e
if (code !== 'EADDRINUSE') {
console.error(
`WebSocket server error:\n${e.stack || e.message}`,
);
}
});
// 返回 发送信息的方法 、 关闭的方法 还有 对应的服务 wss
return {
send(message) {
wss.clients.forEach((ws) => {
if (ws.readyState === 1) {
ws.send(message)
}
})
},
close() {
wss.close()
},
wss
}
}
然后我们就可以很容易的复用了。
const httpServer1 = createServer()
const httpServer2 = createServer()
const ws1 = createWebSocketServer(httpServer1)
const ws2 = createWebSocketServer(httpServer2)
我们暂时只需要启动一个服务,我们将它启动在 3000 端口,然后顺便写一个定时器,不停向服务端发送消息来进行测试。
最终服务端完整代码:
import {WebSocketServer} from 'ws'
import {createServer} from 'http'
const createWebSocketServer = (httpServer) => {
const wss = new WebSocketServer({
noServer: true
})
// 握手事件
httpServer.on('upgrade', (req, socket, head) => {
if (req.headers['sec-websocket-protocol'] === 'conn') {
wss.handleUpgrade(req, socket, head, (ws) => {
wss.emit('connection', ws, req)
})
}
})
// 连接事件
wss.on('connection', (ws) => {
// 往客户端发送 connected 事件,我们使用 type 来进行事件标识,这样方便客户端处理
ws.send(JSON.stringify({type: 'connected'}))
// message 监听事件事件,客户端传来的消息都走这里。
ws.on('message', (data, isBinary) => {
// 由于我们无法确定传过来的数据类型,因此要用 isBinary 区分 buffer 转化为字符串
const receiveData = isBinary ? data : data.toString()
console.log(receiveData)
})
})
wss.on('error', (e) => {
const {code} = e
if (code !== 'EADDRINUSE') {
console.error(
`WebSocket server error:\n${e.stack || e.message}`,
);
}
});
// 返回 发送信息的方法 、 关闭的方法 还有 对应的服务 wss
return {
send(message) {
wss.clients.forEach((ws) => {
if (ws.readyState === 1) {
ws.send(message)
}
})
},
wss,
close() {
wss.close()
}
}
}
const httpServer = createServer()
const ws = createWebSocketServer(httpServer)
const sendMessage = (type, data) => {
ws.send(JSON.stringify({type, data}))
}
setInterval(() => {
sendMessage('console', `服务端定时向客户端发送消息 时间:${new Date().toLocaleString()}`)
}, 3000)
httpServer.listen(3000, () => {
console.log('服务器开启')
})
$ node webSocket.js
服务器开启
客户端
首先我们需要检测当前客户端浏览器是否支持 WebSocket
,如果支持我们尝试连接。
http
协议下我们使用 ws:
连接,https
可能需要使用 wss:
。
还记得我们在服务端设置了请求头属性 sec-websocket-protocol
为 conn
时进行连接,所以我们要传递一个字符串 conn
<body>
<script>
if ('WebSocket' in window) {
const ws = new WebSocket('ws://localhost:3000', 'conn');
}
</script>
</body>
完成连接的步骤之后就是加入对服务端事件的监听,该监听事件为 message
,还可以顺便加上对连接关闭 close
的监听。
<body>
<script>
if ('WebSocket' in window) {
const ws = new WebSocket('ws://localhost:3000', 'conn');
let pingTimer = null
ws.addEventListener('message', async ({data}) => {
const json = JSON.parse(data)
// 我们通过和服务端商量的使用 type 的方式处理不同的事件。
if (json.type === 'connected') {
console.log('[webSocket] connected.')
pingTimer = setInterval(() => ws.send('心跳'), 30000)
}
// 定时对服务端发送消息告诉服务端客户端还 “活着”
if (json.type === 'console') {
console.log(json.data)
}
})
ws.addEventListener('close', async () => {
if (pingTimer) clearInterval(pingTimer);
console.info('[webSocket] disconnected.');
});
}
</script>
</body>
我们还可以加一个主动向服务端发送消息的测试按钮。
最后完整的客户端代码:
<body>
<button onclick="send()">
向服务器发送消息
</button>
<script>
if ('WebSocket' in window) {
const ws = new WebSocket('ws://localhost:3000', 'conn');
let pingTimer = null
ws.addEventListener('message', async ({data}) => {
const json = JSON.parse(data)
// 我们通过和服务端商量的使用 type 的方式处理不同的事件。
if (json.type === 'connected') {
console.log('[webSocket] connected.')
pingTimer = setInterval(() => ws.send('心跳'), 30000)
}
// 定时对服务端发送消息告诉服务端客户端还 “活着”
if (json.type === 'console') {
console.log(json.data)
}
})
ws.addEventListener('close', async () => {
if (pingTimer) clearInterval(pingTimer);
console.info('[webSocket] disconnected.');
});
function send() {
ws.send(`客户端向服务端发送消息 时间:${new Date().toLocaleString()}`)
}
}
</script>
</body>
进行测试
尾言
如果觉得文章对你有帮助的话,欢迎点赞收藏哦,有什么错误或者意见建议也可以留言,感谢~