EggJs框架日记(四):Socket.IO实现(1)

  • 2021年5月21日
  • 技术

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的跑通了。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注