WebSocket 的產生源於 Web 開發中日益增長的實時通信需求,對比基於 http 的輪詢方式,它大大節省了網絡帶寬,同時也降低了服務器的性能消耗; socket.io 支持 websocket、polling 兩種數據傳輸方式以兼容瀏覽器不支持 WebSocket 場景下的通信需求。
框架提供了 egg-socket.io 插件,增加了以下開發規約:
namespace: 通過配置的方式定義 namespace(命名空間)
middleware: 對每一次 socket 連接的建立/斷開、每一次消息/數據傳遞進行預處理
controller: 響應 socket.io 的 event 事件
router: 統一了 socket.io 的 event 與 框架路由的處理配置方式
安裝
$ npm i egg-socket.io --save
開啟插件:config/plugin.js
exports.io = { enable: true, package: 'egg-socket.io', };
配置插件config/config.default.js
/ 和 new2 屬於不同的命名空間 即如果你有兩個業務用到了socket,可以分別用不同的命名空間去管理,如果只用到一個寫一個及可
exports.io = { init: { }, // passed to engine.io namespace: { '/': { connectionMiddleware: [], packetMiddleware: [], }, '/news': { connectionMiddleware: [], packetMiddleware: [], }, }, };
router\io.js 路由可以分別為不同的命名空間配置路由
of 來划分命名空間
io.of('/').route('chat', io.controller.chat.index); io.of('/').route('message', io.controller.chat.message); io.of('/').route('user', io.controller.chat.online); io.of('/news').route('news', io.controller.news.index);
在生產環境下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:7001; # http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_bind # proxy_bind $remote_addr transparent; }
開啟 egg-socket.io 的項目目錄結構如下:
chat ├── app │ ├── extend │ │ └── helper.js │ ├── io │ │ ├── controller │ │ │ └── default.js │ │ └── middleware │ │ ├── connection.js │ │ └── packet.js │ └── router.js ├── config └── package.json
對應的文件都在io下
app/io

配置socket的中間件在 app/io/middleware 下 新建auth.js
在每一個客戶端連接或者退出時發生作用,故而我們通常在這一步進行授權認證,對認證失敗的客戶端做出相應的處理
/** * Created by bear on 2018/2/12. */ const PREFIX = 'room'; //定義房間號 module.exports = app => { return async (ctx, next) => { const { app, socket, logger, helper } = ctx; const id = socket.id; const nsp = app.io.of('/'); const query = socket.handshake.query; // 用戶信息 const { room, userId } = query; //獲取socket鏈接傳過來的參數 const rooms = [ room ]; console.log(room, userId); const tick = (id, msg) => { logger.debug('#tick', id, msg); // 踢出用戶前發送消息 socket.emit(id, helper.parseMsg('deny', msg)); // 調用 adapter 方法踢出用戶,客戶端觸發 disconnect 事件 nsp.adapter.remoteDisconnect(id, true, err => { logger.error(err); }); }; // 檢查房間是否存在,不存在則踢出用戶 // 備注:此處 app.redis 與插件無關,可用其他存儲代替 const hasRoom = await app.redis.get(`${PREFIX}:${room}`); console.log(hasRoom,`${PREFIX}:${room}`) // if (!hasRoom) { // tick(id, { // type: 'deleted', // message: 'deleted, room has been deleted.', // }); // return; // } // 用戶加入 logger.debug('#join', room); socket.join(room); // 在線列表 nsp.adapter.clients(rooms, (err, clients) => { // 更新在線用戶列表 nsp.to(room).emit('online', { clients, action: 'join', target: 'participator', message: `User(${id}) joined.`, }); console.log(123,clients) }); // socket.emit('connect', 'packet received!'); await next(); console.log('disconnect!'); }; };
app/io/middleware/filter.js
module.exports = (app) => { return async (ctx, next) => { // console.log(ctx.packet); await next(); // console.log('packet response!'); }; };
踢出用戶示例:
const tick = (id, msg) => { logger.debug('#tick', id, msg); socket.emit(id, msg); app.io.of('/').adapter.remoteDisconnect(id, true, err => { logger.error(err); }); };
Controller
Controller 對客戶端發送的 event 進行處理;由於其繼承於 egg.Contoller, 擁有如下成員對象:
ctx
app
service
config
logger
app/io/controller/chat.js
/** * Created by bear on 2018/2/12. */ module.exports = app => { class chatController extends app.Controller { async index() { this.ctx.socket.emit('res', 'test'); } async message() { //方法通過 客戶端 this.emit('message',{})//觸發 this.ctx.socket.emit('message', 'test'); const params = this.ctx.args[0]; // this.ctx.service.message.sendPeerMessage(params); console.log(2,params); } async online() {// modelMessage.sendOfflineMessage(socket, data.userId); } } return chatController; };
