简体   繁体   English

NodeJS + WS 访问当前运行的 WS 服务器实例

[英]NodeJS + WS access currently running WS server instance

I have implemented a simple REST API using NodeJS, ExpressJS and routing-controllers.我已经使用 NodeJS、ExpressJS 和路由控制器实现了一个简单的 REST API。 I have also implemented a basic WebSocket server running alongside the REST API and using WS.我还实现了一个与 REST API 一起运行并使用 WS 的基本 WebSocket 服务器。

const app = express();

app.use(bodyParser.json({limit: "50mb"}));
app.use(bodyParser.urlencoded({limit: "50mb", extended: true}));

useExpressServer(app, {
    controllers: [
        UserController
    ]
});

const server = app.listen(21443, (err: Error) => {
    console.log("listening on port 21443");
});

const wss = new WebSocket.Server({server});

wss.on("connection", (ws: WebSocket) => {
    ws.on("message", (message: string) => {
        console.log("received: %s", message);
        ws.send(`Hello, you sent -> ${message}`);
    });

    ws.send("Hi there, I am a WebSocket server");
});

My question is how to I get access to the currently running WS instance so that I am able to send or broadcast from my controller methods.我的问题是如何访问当前正在运行的 WS 实例,以便我能够从我的控制器方法sendbroadcast I have a number of POST methods that run long processes and so return a HTTP 200 to the client, I then would like to either send or broadcast to all connected WS clients.我有许多运行长进程的POST方法,因此向客户端返回 HTTP 200,然后我想向所有连接的 WS 客户端sendbroadcast

What is the correct way to access the WebSocket.Server instance from within my controller classes?从我的控制器类中访问WebSocket.Server实例的正确方法是什么?

List of connected clients are stored inside wss object. 连接的客户端列表存储在wss对象中。 You can receive and loop through them like this: 您可以像这样接收并遍历它们:

wss.clients.forEach((client) => {
    if (client.userId === current_user_id && client.readyState === WebSocket.OPEN) {
        // this is the socket of your current user
    }
})

Now you need to somehow identify your client. 现在,您需要以某种方式识别您的客户。 You can do it by assigning some id to this client on connection: 您可以通过在连接时为此客户端分配一些ID来做到这一点:

wss.on('connection', async (ws, req) => {
    // req.url is the url that user connected with
    // use a query parameter on connection, or an authorization token by which you can identify the user
    // so your connection url will look like
    // http://example.com/socket?token=your_token
    ws.userId = your_user_identifier
    ....
})

To broadcast use: 广播使用:

wss.clients.forEach((client) => {
    if (client.readyState === WebSocket.OPEN) {
        client.send(data);
    }
});

If your controller and socket will be in different files (and I am sure they will), you will have to export the wss object in your socket file and import it in controller. 如果您的控制器和套接字将位于不同的文件中(并且我相信它们会存在),则必须将wss对象导出到套接字文件中,并将其导入控制器中。

You can create the websocket earlier and pass the instance around:您可以更早地创建 websocket 并传递实例:

const notifier = new NotifierService(); 
notifier.connect(http.createServer(app));

app.get("/somethingHappened", () => {
  notifier.broadcast("new notification!!");
});

app.use(routes(notifier))

Full code:完整代码:

app.js

Pass the websocket to the other routes:将 websocket 传递给其他路由:

const express = require("express");
const http = require("http");
const NotifierService = require("../server/NotifierService.js");
const routes = require("./routes");

const app = express();
const server = http.createServer(app);
const notifier = new NotifierService();
notifier.connect(server);

app.get("/somethingHappened", () => {
  notifier.broadcast("new notification!!");
});

//   to demonstrate how the notifier instance can be
//   passed around to different routes
app.use(routes(notifier));

server
  .listen(4000)
  .on("listening", () =>
    console.log("info", `HTTP server listening on port 4000`)
  );

NotifierService.js class that handles the websocket处理 websocket 的NotifierService.js

const url = require("url");
const { Server } = require("ws");

class NotifierService {
  constructor() {
    this.connections = new Map();
  }

  connect(server) {
    this.server = new Server({ noServer: true });
    this.interval = setInterval(this.checkAll.bind(this), 10000);
    this.server.on("close", this.close.bind(this));
    this.server.on("connection", this.add.bind(this));
    server.on("upgrade", (request, socket, head) => {
      console.log("ws upgrade");
      const id = url.parse(request.url, true).query.storeId;

      if (id) {
        this.server.handleUpgrade(request, socket, head, (ws) =>
          this.server.emit("connection", id, ws)
        );
      } else {
        socket.destroy();
      }
    });
  }

  add(id, socket) {
    console.log("ws add");
    socket.isAlive = true;
    socket.on("pong", () => (socket.isAlive = true));
    socket.on("close", this.remove.bind(this, id));
    this.connections.set(id, socket);
  }

  send(id, message) {
    console.log("ws sending message");

    const connection = this.connections.get(id);

    connection.send(JSON.stringify(message));
  }

  broadcast(message) {
    console.log("ws broadcast");
    this.connections.forEach((connection) =>
      connection.send(JSON.stringify(message))
    );
  }

  isAlive(id) {
    return !!this.connections.get(id);
  }

  checkAll() {
    this.connections.forEach((connection) => {
      if (!connection.isAlive) {
        return connection.terminate();
      }

      connection.isAlive = false;
      connection.ping("");
    });
  }

  remove(id) {
    this.connections.delete(id);
  }

  close() {
    clearInterval(this.interval);
  }
}

module.exports = NotifierService;

routes.js

const express = require("express");

const router = express.Router();
module.exports = (webSocketNotifier) => {
  router.post("/newPurchase/:id", (req, res, next) => {
    webSocketNotifier.send(req.params.id, "purchase made");
    res.status(200).send();
  });
  return router;
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM