1.2.2 • Published 3 years ago

wss-adapter v1.2.2

Weekly downloads
18
License
MIT
Repository
github
Last release
3 years ago

1 wssio

随着各大浏览的发展,现在各大浏览器都已支持 WebSocket,而人们也逐渐开始使用新版本的浏览器,而老版本浏览器实现长连接通讯一般都基于 轮询长轮询长连接 等技术栈,而这些技术栈都有各自的优点与缺点,而目前也有相关库对这些对这些技术做了相关兼容性处理,例如 Socket.io-Client 一个 JavaScript 语言库,该库在进行长连接通讯时会根据当前浏览器底层支持情况进行选择通讯方式,从而到达长连接,那么与之相应的就有服务端库 Socket.io 库,该库是运行在 Node.js 端,虽然 Socket.io 已做了兼容性处理,但还是在进行长连接通讯存在一些不足之处,比如单机服务维护连接数不够高、连接认证不够灵活等不足之处,而目前老版本浏览器的市场占有率也在逐渐淡出,各大主流浏览器新版也基本上支持了 WebSocket 通讯,而 WebSocket 实现了真正的 TCP 的连接,从而在长连接通讯上有更好的优势,而单纯 WebSocket 又不具备 Socket.ioNamespacesRoom 等特性,从而又在具体项目应用中不是那么的方便,那么本项目 WssioWscio 就基于 ws 库对 Socket.ioSocket.io-Client 相关库二次开发,使其单机服务维护更多的连接数、连接认证更加的灵活等有点。

2 文档

2.1 概览

2.1.1 什么是 Wssio

Wssio 是一个基于 ws 库对 Socket.ioSocket.io-Client 相关库二次开发的类库,可以实现浏览器与服务器之间实现实时、双向、基于事件的通信,它包括:

  • 一个在浏览器端运行的 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 发送和接收事件

WssioWscio 允许发送和接收自定义事件,除了 errorconnectdisconnectdisconnectingnewListenerremoveListener 几个事件为默认事件不能使用外,其余都可以发送自定义事件,发送事件为 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 发送和获取数据(确认)

有时候我们希望在客户端确认消息接收时收到回执消息。为此,我们只需将函数作为 sendemit 的最后一个参数传递。 重要的是当使用 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 允许 namespacesockets ,这样做的目的是为了同一个连接通过逻辑层面实现多个路径进行隔离通讯,这样可以最大限度地减少资源(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 启动成功后调用,具体配置参数看 ServerApi 文档。

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 层面保持着长连接,只是在逻辑层面上对相应 NamespaceSocket 连接进行断开处理。

命名空间连接时验证例子

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);
});
1.2.2

3 years ago

1.2.1

3 years ago

1.2.0

4 years ago

1.1.8

4 years ago

1.1.5

4 years ago

1.1.1

4 years ago

1.1.2

4 years ago

1.1.0

4 years ago