egg 框架的 socket.io 插件
$ npm i egg-socket.io --save
- Node.js >= 8.0
- Egg.js >= 2.0
通过 config/plugin.js
配置启动 Socket.IO 插件:
// {app_root}/config/plugin.js
exports.io = {
enable: true,
package: 'egg-socket.io',
};
在 config/config.${env}.js
配置 Socket.IO :
exports.io = {
init: { }, // passed to engine.io
namespace: {
'/': {
connectionMiddleware: [],
packetMiddleware: [],
},
},
redis: {
host: '127.0.0.1',
port: 6379
}
};
Egg Socket 内部默认使用 ws
引擎,uws 因为某些原因被废止。
如坚持需要使用,请按照以下配置即可:
exports.io = {
init: { wsEngine: 'uws' },
};
- 有关更多
init
选项配置,请参考:engine.io 。 - 有关更多
Egg Socket
相关默认配置,请参考:config.default.js。
注意: 此函数作为接口预留,便于你按照自己的规则为每一个 socket 生成唯一的 ID:
exports.io = {
generateId: (request) => {
// Something like UUID.
return 'This should be a random unique ID';
}
};
由于 Socket.IO 的设计缘故,多进程的 Socket.IO 服务必须在 sticky
模式下才能工作,否则会抛出握手异常。
所以,必须开启 sticky
模式:
$ # 修改 package.json 对应的 npm scripts
$ egg-bin dev --sticky
$ egg-scripts start --sticky
这两个脚本都会使框架启动 cluster 时使用 sticky
模式
startCluster({
sticky: true,
...
});
如果你使用了 nginx 做代理转发:
location / {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:{ your node server port };
}
app
├── io
│ ├── controller
│ │ └── chat.js
│ └── middleware
│ ├── auth.js
│ ├── filter.js
├── router.js
config
├── config.default.js
└── plugin.js
middleware are functions which every connection or packet will be processed by.
- 编写连接中间件
app/io/middleware/auth.js
module.exports = app => {
return async (ctx, next) => {
ctx.socket.emit('res', 'connected!');
await next();
// execute when disconnect.
console.log('disconnection!');
};
};
- 配置使之生效
config/config.default.js
exports.io = {
namespace: {
'/': {
connectionMiddleware: ['auth'],
},
},
};
注意,必须配置在特定的命名空间下,才会生效
- 编写包中间件
app/io/middleware/filter.js
module.exports = app => {
return async (ctx, next) => {
ctx.socket.emit('res', 'packet received!');
console.log('packet:', this.packet);
await next();
};
};
- 配置使之生效
config/config.default.js
exports.io = {
namespace: {
'/': {
packetMiddleware: ['filter'],
},
},
};
注意,必须配置在特定的命名空间下,才会生效
controller is designed to handle the emit
event from the client.
example:
app/io/controller/chat.js
module.exports = app => {
class Controller extends app.Controller {
async ping() {
const message = this.ctx.args[0];
await this.ctx.socket.emit('res', `Hi! I've got your message: ${message}`);
}
}
return Controller
};
// or async functions
exports.ping = async function() {
const message = this.args[0];
await this.socket.emit('res', `Hi! I've got your message: ${message}`);
};
下一步,在 app/router.js
配置路由
module.exports = app => {
// or app.io.of('/')
app.io.route('chat', app.io.controller.chat.ping);
};
路由主要负责对特定的 socket 连接不同的事件进行分发处理到对应的控制器。路由配置写在 app/router.js
中,实例参照上一节。
除了,业务逻辑的路由之外,还有配置几个系统内置事件:
disconnecting
正在断开连接disconnect
连接已经断开error
发生了错误
示例:
app/router.js
app.io.route('disconnect', app.io.controller.chat.disconnect);
app/io/controller/chat.js
module.exports = (app) => {
class Controller extends app.Controller {
async disconnect() {
const message = this.ctx.args[0];
console.log(message);
}
}
return Controller
};
egg-socket.io
支持会话,与普通的 HTTP 会话几乎一样。
会话的创建和鉴权只发生在建立连接阶段。在包中间件和控制器中可以获取通过 ctx.session
获取会话对象,但是它只在连接创建。
会话功能由 egg-session 实现,请确保它已经开启。
如果你的 Socket.IO 服务由多台服务器提供,那么必须思考集群方案。比如,广播,房间等功能,必须依赖集群方案。
egg-socket.io 集成了 socket.io-redis ,能够非常方便的实现集群共享资源与事件分发。
配置起来也很简单:
只需要在 config/config.${env}.js
配置 :
exports.io = {
redis: {
host: { redis server host },
port: { redis server prot },
auth_pass: { redis server password },
db: 0,
}
};
egg 服务在启动时,会尝试连接 redis 服务,成功后,应用会顺利启动。
微信小程序所采用的 WebSocket 协议版本为 13 (可抓包查看),而 socket.io 支持的协议版本为 4 详见 socket.io-protocol
解决方案是可以封装一个适配小程序端的 socket.io.js,Github 上也有现成的库可以参考 wxapp-socket-io 示例代码如下:
// 小程序端示例代码
import io from 'vendor/wxapp-socket-io.js';
const socket = io('ws://127.0.0.1:7001');
socket.on('connect', function () {
socket.emit('chat', 'hello world!');
});
socket.on('res', msg => {
console.log('res from server: %s!', msg);
});
请访问 here.