Socket.IO 是一个基于 Node.js 的实时应用程序框架,在即时通讯、通知与消息推送,实时分析等场景中有较为广泛的应用。
框架提供了 egg-socket.io 插件,增加了以下开发规约:
- namespace: 通过配置的方式定义 namespace(命名空间)
- middleware: 对每一次 socket 连接的建立/断开、每一次消息/数据传递进行预处理
- controller: 响应 socket.io 的 event 事件
- router: 统一了 socket.io 的 event 与 框架路由的处理配置方式
1,部署
引入插件:
npm i egg-socket.io --save
配置config文件:
// config/plugin.js import { EggPlugin } from 'egg'; const plugin: EggPlugin = { io: { enable: true, package: 'egg-socket.io', }, }; export default plugin;
// app/config/config.default.ts import { EggAppConfig, EggAppInfo, PowerPartial } from 'egg'; export default (appInfo: EggAppInfo) => { const config = {} as PowerPartial<EggAppConfig>; // override config from framework / plugin // use for cookie sign key, should change to your own and keep security config.keys = appInfo.name + '_1563517881513_7550'; // add your special config in here const bizConfig = { io: { init: {}, // 默认: ws namespace: { //命名空间为 /io, 不是io '/io': { connectionMiddleware: [], packetMiddleware: [], }, }, }, }; // the return config will combines to EggAppConfig return { ...config, ...bizConfig, }; };
这里配和redis使用,egg-socket.io 内置了 socket.io-redis
,在 cluster 模式下,使用 redis 可以较为简单的实现 clients/rooms 等信息共享。
注意: 如果项目中同时使用了 egg-redis
, 请单独配置,不可共用。
在config配置redis:
// app/config/config.default.ts const bizConfig = { io: { init: {}, // 默认: ws namespace: { //命名空间为 /io, 不是io '/io': { connectionMiddleware: [], packetMiddleware: [], }, }, redis: { host: '127.0.0.1', port: 6379, auth_pass: 'root', db: 0, }, }, };
注意: 框架是以 Cluster 方式启动的,而 socket.io 协议实现需要 sticky 特性支持,否则在多进程模式下无法正常工作。
由于 socket.io 的设计,在多进程中服务器必须在 sticky 模式下工作,故需要给 startCluster 传递 sticky 参数。
修改 package.json 中 npm scripts 脚本:
{ "scripts": { "dev": "egg-bin dev --sticky", "start": "egg-scripts start --sticky" } }
前期部署完成,接下来进行代码编辑。
2,使用
在app目录下创建io目录,对应的socket .io文件都在 app/io 目录下处理。
编写socket .io的中间件:
// app/io/middleware/auth.ts import { Context } from 'egg'; export default function Auth() { return async (ctx: Context, next: () => Promise<any>) => { //打印消息 ctx.socket.emit('res', 'packet received!'); await next(); } }
加入中间件:
// app/config/config.default.ts '/io': { connectionMiddleware: ['auth'], packetMiddleware: [], },
控制器:
// app/io/controller/chat.ts import {Controller} from 'egg'; export default class ChatController extends Controller { public async message() { const {ctx, app} = this; const nsp = app.io.of('/'); const message = ctx.args[0] || {}; try { const {target, msg} = message; if (!target) return; // 格式化数据 nsp.emit(target, msg); } catch (error) { app.logger.error(error); } } }
视图:
// app/view/chat.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Chat</title> <body> <script src="https://cdn.bootcss.com/socket.io/2.1.0/socket.io.js"></script> <script> window.onload = function () { const socket = io('http://127.0.0.1:7001/io', { query: { userId: `client_${Math.random()}`, }, transports: ['websocket'] }); socket.on('connect', () => { const id = socket.id; console.log('#connect,', id, socket); socket.on(id, msg => { console.log('#receive,', msg); }); }); // 接收在线用户信息 socket.on('online', msg => { console.log('#online,', msg); }); socket.on('disconnect', msg => { console.log('#disconnect', msg); }); socket.on('disconnecting', () => { console.log('#disconnecting'); }); socket.on('error', () => { console.log('#error'); }); socket.emit('msg', { target: 'message', msg: 'hello,world!', }); window.socket = socket; }; </script> </body> </html>
最后,配置路由:
// app/router.ts // socket.io io.of('/io').route('msg', io.controller.chat.message); // 聊天 router.get('/chat', controller.chat.index);
启动项目后访问 /chat 路由,在浏览器控制台Network中的WS便可以看到连接情况,这样一个基本的Socket.IO的跑通了。