用node實現一個簡單的聊天室——websocket實踐

websocket是HTML5開始提供的一種在單個TCP鏈接上進行全雙工通信的協議。大多數 Web 應用程序將經過頻繁的異步JavaScript和XML(AJAX)請求實現長輪詢。輪詢的效率低,很是浪費資源。而websocket可以很好的解決相似的問題。經常使用於即時通信、監控等狀況。 在本案例中, 使用egg+typescript做爲後端框架, 使用egg官方封裝的Socket.IO庫。前端使用vue。html

egg配置

TS下的egg許多設置和文檔有所不一樣, 可是稍稍看一下仍是能理解的。 按照egg的文件規劃,Socket.IO應該獨立的做爲一個文件夾, 內部包含本身的controller和middleware, 而後和其餘頁面共享router配置前端

// router
export default (app: Application) => {
  const { controller, router, io } = app;

    // Socket.IO會經過io暴露出來, io.of('/')則是命名空間
  router.get('/', controller.home.index); // 這個是正常的http請求
  io.of('/').route('online', io.controller.nsp.exchange); // 這個就是websocket請求
  io.of('/').route('newmsg', io.controller.nsp.newmsg);
  io.of('/').route('sendMsg', io.controller.nsp.sendMsg);
};
複製代碼
// config/plugin  這裏是egg開啓插件插件的地方
const plugin: EggPlugin = {
  // static: true,
  nunjucks: {
    enable: true,
    package: 'egg-view-nunjucks',  // 模板渲染插件
  },
  io: {
    enable: true,
    package: 'egg-socket.io', // 官方的封裝的socke插件
  },
};
複製代碼
// config/config.default 根據不一樣環境, 配置插件參數
export default (appInfo: EggAppInfo) => {
  // ...
  config.middleware = [];
  // 配置模板引擎
  config.view = {
    cache: false,
    defaultExtension: 'nunjucks',
    mapping: {
      '.html': 'nunjucks',
    },
  };
  // 配置socket
  config.io = {
    init: { }, // passed to engine.io
    namespace: {
    // 命名空間
      '/': {
        connectionMiddleware: [
          'connection', // 這個是鏈接中間件, 只在connection的時候觸發
        ],
        packetMiddleware: [], // 這個會在每次消息的時候觸發
      },
      '/example': {
        connectionMiddleware: [],
        packetMiddleware: [],
      },
    },
  };

  return config;
};

複製代碼
// app/router
export default (app: Application) => {
  const { controller, router, io } = app;

  router.get('/', controller.home.index);
  
  // 這裏的sendMsg至關於一個接口, 負責處理客戶端發送的sendMsg事件
  // 這個controller是io模塊的controller, 和egg的controller不一樣
  io.of('/').route('sendMsg', io.controller.nsp.sendMsg);
};
複製代碼
// app/io/controller

import { Controller } from 'egg';

export default class NspController extends Controller {

  public async sendMsg() {
    const { ctx, app } = this;
    const nsp = app.io.of('/');
    const message = ctx.args[0] || {}
    // 向客戶端廣播消息, 在客戶端監聽broadcast事件就能夠獲取消息了
    nsp.emit('broadcast', message)
  }
}

複製代碼
// app/io/middleware/connection
import { Context } from 'egg';
// io模塊的中間件, 在config/config.default裏配置成connectionMiddleware, 只在
connection的時候觸發
export default function robotMiddleware() {
  return async (ctx: Context, next: any) => {
    const { app } = ctx;
    const nsp = app.io.of('/');
    // 向客戶端推送online事件
    nsp.emit('online', '有新成員加入聊天室了')
    await next();
  };
}

複製代碼

這樣, 服務器端的邏輯就完成了,接下來經過vue來實現客戶端邏輯vue

簡單的一個頁面, 分爲MsgItem和Send 兩個組件, 具體實現就不寫了。

// 客戶端 src/utils/io
import io from 'socket.io-client';
// 稍微封裝一下socket.io, 而後暴露出去。
const socket = function ():any {
  const _io = io('http://127.0.0.1:7001/');
  _io.on('connect', function(){
    console.log('連接成功');
  });
  _io.on('disconnect', function(){
    console.log('斷開連級');
  });
  return _io
}


export default socket

複製代碼

// 客戶端 app/App.vue

  export default class Index extends Vue {
    // 頭像
    user = {
      avatar: 'https://f12.baidu.com/it/u=4263977612,1595937908&fm=76'
    }
    // 消息列表
    msgList: Array<string> = []

    //發送消息, 觸發sendMsg事件
    sendMsg(msg: string):void {
      socket.emit('sendMsg', msg)
    }

    // 頁面加載以後觸發
    mounted() {
      // 監聽online事件
      socket.on('online', (data: string) => {
        this.msgList.push(data)
      })
      // 監聽broadcast事件, 獲取服務器消息
      socket.on('broadcast', (data: string) => {
        this.msgList.push(data)
      })
    }
  }
複製代碼

socket是基於事件監聽來進行的, 經過on來註冊監聽事件, 經過emit來觸發事件。 經過服務器和客戶端的配合, 就能夠事件即時的消息推送。這篇文章只寫了最簡單功能, 基於socket.io還有分房間, 踢人,身份識別,私聊等功能, 等下篇文章再寫吧。web