wss-parser v1.5.1
1 wssio
随着各大浏览的发展,现在各大浏览器都已支持
WebSocket
,而人们也逐渐开始使用新版本的浏览器,而老版本浏览器实现长连接通讯一般都基于轮询
、长轮询
、长连接
等技术栈,而这些技术栈都有各自的优点与缺点,而目前也有相关库对这些对这些技术做了相关兼容性处理,例如Socket.io-Client
一个JavaScript
语言库,该库在进行长连接通讯时会根据当前浏览器底层支持情况进行选择通讯方式,从而到达长连接,那么与之相应的就有服务端库Socket.io
库,该库是运行在Node.js
端,虽然Socket.io
已做了兼容性处理,但还是在进行长连接通讯存在一些不足之处,比如单机服务维护连接数不够高、连接认证不够灵活等不足之处,而目前老版本浏览器的市场占有率也在逐渐淡出,各大主流浏览器新版也基本上支持了WebSocket
通讯,而WebSocket
实现了真正的TCP
的连接,从而在长连接通讯上有更好的优势,而单纯WebSocket
又不具备Socket.io
中Namespaces
、Room
等特性,从而又在具体项目应用中不是那么的方便,那么本项目Wssio
、Wscio
就基于ws
库对Socket.io
、Socket.io-Client
相关库二次开发,使其单机服务维护更多的连接数、连接认证更加的灵活等有点。
2 文档
2.1 概览
2.1.1 什么是 Wssio
Wssio
是一个基于ws
库对Socket.io
、Socket.io-Client
相关库二次开发的类库,可以实现浏览器与服务器之间实现实时、双向、基于事件的通信,它包括:
- 一个Node.js 服务: 源码
- 一个在浏览器端运行的
JavaScript
库(也可以从Node.js运行):源码 | Client-API主要特点是:
支持自动重连
除非服务器端断开连接或者客户端自动断开连接,否则客户端断开会无限重连,直到重新连接上。服务器异常停止后客户端以后自动进行重连,直到服务器重新启动客户端连接成功、并且消费之前未处理的事件。
支持二进制
任何可序列化的数据结构都可以被发送, 包括:浏览器中的 ArrayBuffer(数组缓存) 和 Blob(二进制大文件) Node.js中的 ArrayBuffer和Buffer缓存
支持多路复用
为了在实际应用中支持各种分组(比如基于项目组、权限组等),
Wssio
允许你创建多个Namespaces(命名空间),这些命名空间将充当单独的通信通道,但是底层还是只维持着一条连接。支持房间
在每个
Namespace
中,你可以定义sockets
,可以加入或者离开任意房间(Rooms
),可以广播指定的房间,也可以发送到已加入的每个房间成员,最简单的例子入聊天群组发送消息。部分代码示例:
const namespace = wssio.of('/test') // 切换命名空间 namespace.on('connection', (socket) => { // 监听该命名空间下的所有连接 socket.emit('request', '数据', /*回执函数*/) //单一发送事件 socket.join('room' || ['room1', 'room2'], /*回调*/) // 加入房间 socket.to('room').emit('event', '数据') //向指定房间发送消息 socket.on('event', function(){}) //监听某个消息事件 })
2.1.2 安装
Server
(服务端) 源码npm i --save wssio
Javascript
(客户端) 源码
CDN
方式<script src="https://unpkg.com/wscio@1.2.5/dist/wscio.min.js"></script>
NPM
方式在Node.js中,或者在打包工具比如 webpack
npm i --save wscio
其他实现方式:
待完善
2.1.3 在Node HTTP 服务中使用 Wssio
Server
服务端const http = require('http') const wssio = require('wssio') const server = http.createServer() const wss = new wssio({ server }).of('/test'); wss.on('connection', (socket) => { socket.emit('hello', { hello:'world' })//发送个客户端消息 socket.on('event', (data)=>{ console.log(data) //收到的消息 }) }); server.listen(3000, '0.0.0.0');
Client
客户端<script> const socket = wscio('ws://127.0.0.1:3000', { nsp: '/test' }); socket.on('connect', function() { console.log('第一次连接上'); }); socket.on('hello', function(data) { console.log(data) //收到服务器的消息 socket.emit('event', '数据') //完成了一次消息互换 }) </script>
2.1.4 在Express中使用 Wssio
Server
服务端const app = require('express')() const server = require('http').Server(app) const wssio = require('wssio') const wss = new wssio({ server }).of('/test'); wss.on('connection', (socket) => { socket.emit('hello', { hello:'world' })//发送个客户端消息 socket.on('event', (data)=>{ console.log(data) //收到的消息 }) }); server.listen(3000, '0.0.0.0');
Client
客户端与前面一直的使用方法
2.1.5 发送和接收事件
Wssio
、Wscio
允许发送和接收自定义事件,除了error
、connect
、disconnect
、disconnecting
、newListener
、removeListener
几个事件为默认事件不能使用外,其余都可以发送自定义事件,发送事件为emit
方法,参数为事件名、数据、回执函数,其中事件名为必须参数,接收事件为on
方法,参数为事件名、回调函数,其中两个参数都为必须。
2.1.6 限制自己使用命名空间
多路复用单个连接的优点,而不是使用两个
WebSocket
连接的Wssio
,它将使用一个。
Server
服务端const wss = new require('wssio')() const test1 = wss.of('/test1').on('connection', (socket) => { socket.emit('test1', { message: 'test1' }) }) const test2 = wss.of('/test2').on('connection', (socket) => { socket.emit('test2', { message: 'test2' }) })
Client
客户端<script> const test1 = wscio('ws://127.0.0.1:3000', { nsp: '/test1' }); const test2 = wscio('ws://127.0.0.1:3000#/test2'); test1.on('connect', function() {}) test2.on('test2', function() {}) </script>
2.1.7 发送和获取数据(确认)
有时候我们希望在客户端确认消息接收时收到回执消息。为此,我们只需将函数作为
send
或emit
的最后一个参数传递。 重要的是当使用emit
时,确认是由你完成的,这意味着你也可以传递数据:
Server
服务端const wss= new require('wssio')() wss.on('connection',socket=>{ socket.on('event', (data, fn: function) => { fn('服务端已收到消息、回执确认', data) }) })
Client
客户端<script> const socket= wscio('ws://127.0.0.1:3000') socket.on('connect',()=>{ socket.emit('event', { data: '数据' }, function(data) { console.log(data)//应该是 '服务端已收到消息、回执确认, data' }) }) </script>
2.1.8 广播消息
- 广播时不支持回调
要进行广播,只需添加广播标志即可发出和发送方法调用,广播消息是指除了自己向其他所有连接的
socket
发送消息。
Server
服务端const wss = new require('wssio')() wss.on('connection', (socket)=>{ socket.broadcast.emit('event') socket.broadcast.to('socketId').emit('event', '数据') //给指定人发送 })
2.2 命名空间和房间
2.2.1 命名空间
Wssio
允许 namespace
到 sockets
,这样做的目的是为了同一个连接通过逻辑层面实现多个路径进行隔离通讯,这样可以最大限度地减少资源(TCP连接)的数量,同时通过在通信通道之间通过逻辑控制来分离应用程序中的问题。
默认命名空间 (
Namespace
)/为默认命名空间、它管理默认连接到的一个
Wssio
客户端,以及默认情况下服务器监听的那个。该命名空间是在创建Wssio
服务时自动创建。以下都被emit到所有socket连接
const wss = new require('wssio')()
wss.sockets.emit('event', '数据')
wss.emit('event', '数据')
自定义命名空间
要设置自定义命名空间,可以在服务端调用of函数
const wss = new require('wssio')()
const nsp= wss.of('/my-namespace')
nsp.on('connection', (socket) => {})
nsp.emit('event', '数据')
在客户端,你告诉
Wssio
客户端连接到该命名空间
<script>
const test1 = wscio('ws://127.0.0.1:3000', {
nsp: '/my-namespace'
}); //通过配置参数设置Namespace
const test2 = wscio('ws://127.0.0.1:3000#/my-namespace'); //通过在连接url最后面加上#/my-namespace
</script>
向默认命名空间中的所有客户端广播
const wss = new require('wssio')()
wss.emit('event', '数据')
注意:在这两种情况下,这些消息都会到达连接到默认
/
命名空间的所有客户端,但不会到达其他命名空间中的客户端。重要提示:命名空间是逻辑层面上的控制,不是真正的URL连接,实质还是一个连接。
2.2.2 房间 (Room
)
在每个命名空间中,可以
join
(加入/连接)和level
(离开)的任意房间(Room
)。加入或离开
可以调用
join
来加入给定房间的socket
:const wss = new require('wssio')() wss.on('connection', (socket) => { socket.join('room' || ['room1', 'room2']) })
然后在广播消息或者
emit
时,可以使用使用to
来进行切换房间:const wss = new require('wssio')() wss.on('connection', (socket) => { socket.to('room').emit('event', '数据') }) wss.to('room').emit('event', '数据')
可以调用
level
离开指定房间:const wss = new require('wssio')() wss.on('connection', (socket) => { wss.level('room') })
调用
levelAll
离开所有房间:const wss = new require('wssio')() wss.on('connection', (socket) => { wss.levelAll() })
默认房间
Wssio
中的每个Socket
都由一个随机的、不可访问的、唯一的标识符id
标识。为了方便起见,每个Socket
都自动加入由该id
标识的房间,这使得向其他Socket
广播消息变得容易:const wss = new require('wssio')() wss.on('connection', (socket) => { socket.broadcast.to(id).emit('event', '数据') })
断开
断开连接后,
Sockets
会自动离开它们所属的所有房间以及连接,不需要进行特殊处理。
2.3 服务端启动
主要通过传入服务配置参数,必须是对象,回调函数是在
WebSocket
启动成功后调用,具体配置参数看Server
端Api
文档。const wss = new wssio(options, callback) wss.on('connection', (socket) => { //客户端连接成功 })
2.4 身份验证
2.4.1 建立初始连接时进行身份验证
有时候需要对客户端发起得连接进行身份验证,在身份验证失败时直接不让其建立连接,并其客户端不再自动进行重连尝试,从而到达一些非法请求一直重连服务器从而导致服务器压力,建立连接时认证失败时的断开是直接断开
URL
层面的连接。const http = require('http') const wssio = require('wssio') const server = http.createServer() // 返回值方式授权连接是否验证通过 functionverifyClient( data: { origin: string; secure: boolean; req: http.IncomingMessage}) { data.id = '自己手动绑定SocketId'; //可以手动绑定SocketId return true // 返回false为授权未通过, 返回true为授权通过 } // 回调方式授权连接是否验证通过 functionverifyClient( { origin: string; secure: boolean; req: http.IncomingMessage}, callback(verified: boolean, code?: number, message?: string, headers?: http.OutgoingHttpHeaders) => void) { data.id = '自己手动绑定SocketId'; //可以手动绑定SocketId callback(false) // 返回false为授权未通过, 返回true为授权通过 } const wss = new wssio({ server, verifyClient, }) wss.on('connection', (socket) => { console.log('客户端连接上', socket.id); }); server.listen(3000, '0.0.0.0');
2.4.2 创建命名空间连接时进行身份验证
虽然上面这种方式已经能达到连接时身份验证了,但是有时候我们系统中对各
Socket
进行分组连接管理(Namespace
),这种情况下我们就会导致连接不成功所有的Namespace
都不能使用,所以此时我们就需要对每个Namespace
下的Socket
连接进行单独的身份验证,此时是URL
层面保持着长连接,只是在逻辑层面上对相应Namespace
下Socket
连接进行断开处理。命名空间连接时验证例子
const http = require('http') const wssio = require('wssio') const server = http.createServer() // 这里的 auth 参数是由下面客户端发起连接时传入的对应参数 // auth = typeof auth === 'object' ? auth : auth ? { data: auth } : {}; // 只能通过返回授权状态 functionverifyClient(auth) { auth.id = '自己手动绑定SocketId'; //可以手动绑定SocketId auth.info = {}; //可以手动绑定一些连接时的信息,比如用户信息 return true // 返回false为授权未通过, 返回true为授权通过 } const testNsp = new wssio({ server }).of('/test', null, verifyClient) // 当需要命名空间连接验证是第二个参数必须传,传入null也可以
testNsp.on('connection', (socket) => { console.log('客户端连接上', socket.id); }); server.listen(3000, '0.0.0.0');
// 客户端 const testNsp1 = require('wscio')('ws://127.0.0.1:3000', { nsp: '/test', auth: { token: '' }}); const testNsp2 = require('wscio')('ws://127.0.0.1:3000', { nsp: '/test', autoConnect: false //禁止自动连接 }); testNsp2.open('token字符串'); //手动打开连接 testNsp1.on('connect',()=>{}); testNsp2.on('connect',()=>{});
## 2.5 中间件函数 **`use`**
>> 可以通过 `wssio.use()` 全局为指定 `Namespace` 下提供任意函数,该函数在创建 `socket` 时运行,`use` 中间件执行的顺序为注册时的顺序,查看此示例:
```TypeScript
const http = require('http')
const wssio = require('wssio')
const server = http.createServer()
const wss = new wssio({
server
});
let run = 0
wss.use((socket, next) => {
run++
next()
})
wss.use((socket, next) => {
run++
next()
})
wss.on('connection', (socket) => {
console.log(run) //输出为2
})
server.listen(3000, '0.0.0.0')
也可以使用中间件进行身份验证
const http = require('http') const wssio = require('wssio') const server = http.createServer() const wss = new wssio({ server }); wss.use((socket,next) => { const handshakeData= socket.request; // 确保握手数据看起来和以前一样好 // 如果错误,则执行这个: // next(new Error('not authorized')); // 否则回调next next(); })
注意:该种方式实现身份认证是不能到达真正断开
URL
层面的断开,只是起到逻辑层面断开。使用中间命名空间授权
const http = require('http') const wssio = require('wssio') const server = http.createServer() const wss = new wssio({ server }).of('namespace'); wss.use((socket,next) => { next(); //具体调用跟上面一样 })
针对于每个
Socket
的中间件函数针对于每个
Socket
的中间件函数是指对每一个Socket
监听事件响应之前的处理const http = require('http') const wssio = require('wssio') const server = http.createServer() const wss = new wssio({ server }); wss.on('connection', (socket) => { socket.use((events, next) => { if (events.length >= 2 && events[0] === 'count') { events[1]++; } next(); }); socket.on('count', (data) => { console.log(data); //输出3 }); }) server.listen(3000, '0.0.0.0')
const socket = require('wscio')('ws://127.0.0.1:3000'); socket.on('connect', () => { socket.emit('count', 2); });
## 2.6 常用 ***`emit`*** 事件
```TypeScript
const http = require('http')
const wssio = require('wssio')
const server = http.createServer()
const wss = new wssio({
server
});
wss.on('connection', (socket) => {
// 发消息到客户端
socket.emit('event', '数据1', '数据2', '数据3', ...) //最后一个参数也可以是函数
// 发送到是所有客户端除了发送者
socket.broadcast.emit('boradcast', '数据')
// 发送到房间名为 room 中除发件人以外的所有客户端
socket.to('room').emit('event', '数据1', '数据2', '数据3', ...) //此时参数不能是函数
// 发送到 room1 和/或 room1 房间中的所有客户端,发件人除外
socket.to('room1').to('room2').emit('event', '数据1', '数据2', '数据3', ...) //此时参数不能是函数
// 发送给房间名 room 中的所有客户,包括发件人
wss.to('room').emit('event', '数据1', '数据2', '数据3', ...) //此时参数不能是函数
// 发送到命名空间 test 中的所有客户端,包括发件人
wss.of('test').emit('event', '数据1', '数据2', '数据3', ...) //此时参数不能是函数
// 发送到特定命名空间中的特定房间,包括发件人
wss.of('test').to('room').emit('event', '数据1', '数据2', '数据3', ...) //此时参数不能是函数
// 警告:`socket.to(socket.id).emit()` ,不会工作,因为它会发送给房间里的每个人
// 命名 `socket.id` ,但为发件人。请改用经典的 socket.emit()
// 带确认发送
socket.emit('event', '数据1', '数据2', '数据3', ...,(answer)=>{
})
// 不压缩发送
socket.compress(false).emit('event', '数据1', '数据2', '数据3', ...)
// 指定要发送的数据是否具有二进制数据
socket.binary(false).emit('event', '数据1', '数据2', '数据3', ...)
// 发送到此节点上的所有客户端(使用多个节点时
wss.local.emit('event', '数据1', '数据2', '数据3', ...)
// 发送到所有连接的客户端
wss.emit('event', '数据1', '数据2', '数据3', ...)
})
注意:以下事件是保留的,应用程序不应将其用作事件名称:
- erorr
- connect
- disconnect
- disconnecting
- newListener
- removeListener
3 客户端 API
4 服务端-API(Server-API)
5 例子
服务端
const http = require('http');
const wssio = require('./build');
const server = http.createServer();
function verifyClient() {
return true; // 返回false为授权未通过, 返回true为授权通过
}
const wss = new wssio({
server,
verifyClient,
}).of('/test');
let count = 0
wss.use((socket, next) => {
count++
next()
})
wss.use((socket, next) => {
count++
socket.count = count
next()
})
wss.on('connection', (socket) => {
console.log('客户端连接上', socket.id);
console.log(socket.count)
socket.use((events, next) => {
if (events.length >= 2 && events[0] === 'count') {
events[1] = '客户端加一';
}
next();
});
socket.emit('aaa', socket.id, (data) => {
console.log('客户端回执', data);
});
socket.on('bbb', (data, cb) => {
console.log('客户端数据', data);
cb('服务端回执');
});
});
server.listen(3000, '0.0.0.0');
客户端
const socket = require('./build')('ws://127.0.0.1:3000?bbbbb=123#/test');
socket.on('connect', () => {
console.log('第一次连接上', socket.id);
socket.emit('bbb', '客户端', (data) => {
console.log('服务器端回执', data);
});
});
socket.on('reconnect', () => {
console.log('重新连接上', socket.id);
});
socket.on('reconnecting', function () {
console.log('正在重新连接上', arguments);
});
socket.on('disconnecting', () => {
console.log('正在断开连接', socket.id);
});
socket.on('disconnect', (data) => {
console.log('连接断开', data);
});
socket.on('connect_error', (data) => {
console.log('错误', data);
});
socket.on('aaa', (data, cb) => {
console.log('服务器端数据', data);
cb(true);
});