环境
服务端:Egg
- egg2.15.1
- egg-socket.io4.1.6
客户端:小程序
问题
1.小程序不支持socket.io
推荐使用weapp.socket.io
2.小程序端socket配置
// weapp.socket.io项目打包后的文件
const io = require('../static/js/weapp.socket.io');
io(url, {
transports: ['websocket'], // 此项必须设置
});
3.服务端配置
4.服务端namespace理解
-
namespace的键需要和路由配置中io.of后面保持一致
config.io = { // namespace命名空间配置为 namespace: { '/api': { // 预处理器中间件, 我们这里配置了一个auth, 进行权限判断, 它对应的文件是/app/io/middleware/auth.js, 这里可以配置多个文件, 用逗号隔开 connectionMiddleware: [ 'auth' ], // 这里我们可以做一些权限校验之类的操作 packetMiddleware: [], // 通常用于对消息做预处理,又或者是对加密消息的解密等操作 }, }, }; io.of('/api').route('connect', io.controller.home.connect);
-
namespace的键需要与客户端建立连接的url的路径部分一样(端口后面,不包含查询)
config.io = { // namespace命名空间配置为 namespace: { '/api': { // 预处理器中间件, 我们这里配置了一个auth, 进行权限判断, 它对应的文件是/app/io/middleware/auth.js, 这里可以配置多个文件, 用逗号隔开 connectionMiddleware: [ 'auth' ], // 这里我们可以做一些权限校验之类的操作 packetMiddleware: [], // 通常用于对消息做预处理,又或者是对加密消息的解密等操作 }, }, }; const io = require('../static/js/weapp.socket.io'); io('http://localhost:8080/api', { transports: ['websocket'], // 此项必须设置 });
5.服务端io的路由配置中的route第一个参数
io.of('/api').route('connect', io.controller.home.connect);
io.of('/api').route('updatelist', io.controller.home.updatelist);
- connect是sockte.io的自有事件(建立连接),还有disconnect(断开连接)等
- 客户端自定义的事件updatelist,服务端要对应的修改route第一个参数updatelist
6.sockte.io客户端请求路径
io('http://localhost:8080/api', {
transports: ['websocket'], // 此项必须设置
});
- 请求路径是
http://localhost:8080/socket.io
,io会自动截取url并修改 - 配置中url的路径部分
/api
,需要服务端config的io中namespace配置,并且路由中io.of参数保持一致
特殊情况
当egg服务端被Nginx代理出带有路径时,需要特殊处理,例如:请求路径需要变成http://localhost:8080/dev/socket.io
Nginx
location ^~/dev/socket.io {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://localhost:8080/socket.io;
}
sockte.io客户端
io('http://localhost:8080/api', {
path: '/dev/socket.io',
transports: ['websocket'], // 此项必须设置
});
上面的io第一参数url一定不要写成http://localhost:8080/dev/api
,写成这样会造成服务端的namespace不匹配,看ws消息可以知道会变成/dev/api
,之前是/api
7.sockte.io客户端请求配置自定义请求头等
io('http://localhost:8080/api', {
path: '/dev/socket.io',
transports: ['websocket'], // 此项必须设置
extraHeaders: {
token: wx.getStorageSync(TOKEN), // header增加了token
},
reconnectionAttempts: 3, // 失败后重新连接次数,一直失败总共尝试四次
reconnectionDelay: 2000, // 重新连接间隔时间毫秒
});
8.小程序端关闭和建立连接(个人理解)
- 变量clientSocket存储socket.io用于不同生命周期操作
- onShow建立连接并且赋值给clientSocket,监听权限事件,成功进行下一步操作,失败断开连接
- onHide,onUnload进行断开连接操作,断开连接操作时需要判断是否存在clientSocket,并且clientSocket.connected为true
- 监听error事件,关闭连接
9.服务端controller定时器轮询接口,清除定时器时机
- 定时器变量timer不要挂载在this上面,挂载在this上面没效果
- disconnect和connect时都清除定时器
- 先请求一次接口(防止页面更新慢),成功后再进入轮询定时器
- 接口报错时,清除定时器(否则会一直轮询)
- 错误中间件不会捕获到定时器中的异常,这里要特别注意,可以尝试在请求方法中判断或者可以在定时器内部写try catch处理,用try catch包裹定时器也无法捕获到错误
10.服务端请求接口无响应
小程序请求过来的请求头直接透传给接口,由于小程序请求egg是websocket所以有握手环节的协议升级
Connection: Upgrade
Upgrade: websocket
但是接口不支持websocket协议,因此握手环节就失败,更不会返回结果,处理是把这两个请求头去掉。
11.客户端建立多个websocket连接
socket.io默认只会建立一个连接,前一个执行的很可能不会触发(监听)收消息回调。此时需要配置forceNew: true
io('http://localhost:8080/api', {
path: '/dev/socket.io',
transports: ['websocket'], // 此项必须设置
extraHeaders: {
token: wx.getStorageSync(TOKEN), // header增加了token
},
reconnectionAttempts: 3, // 失败后重新连接次数,一直失败总共尝试四次
reconnectionDelay: 2000, // 重新连接间隔时间毫秒
forceNew: true,
});
12.断开真机调试后无法连接websocket
开发工具模拟器、真机调试、断开真机之后打开调试都可以连接websocket,原因是没有配置socket安全域名。小程序遇到未知报错可以使用下面代码查看
wx.onError(err => {
wx.showModal({
title: 'err',
content: err
})
})