从路由发送websocket消息

6
我正在尝试使用WebSockets设置我的服务器,这样当我通过路由更新某些内容时,也可以在更新该路由上的内容时发出WebSocket消息。
想法是当有人点击路由/add-team-member并将其保存到我的Mongo db时,向所有连接到WebSocket且属于与该团队对应的WebSocket房间的人员发出消息。
我已经按照socket.io的文档设置了我的应用程序,如下所示: App.js
// there's a lot of code in here which sets what to use on my app but here's the important lines

const app = express();
const routes = require('./routes/index');

const sessionObj = {
    secret: process.env.SECRET,
    key: process.env.KEY,
    resave: false,
    saveUninitialized: false,
    store: new MongoStore({ mongooseConnection: mongoose.connection }),
             secret : 'test',
             cookie:{_expires : Number(process.env.COOKIETIME)}, // time im ms    
}

app.use(session(sessionObj));
app.use(passport.initialize());
app.use(passport.session());

module.exports = {app,sessionObj};

start.js

const mongoose = require('mongoose');
const passportSocketIo = require("passport.socketio");
const cookieParser = require('cookie-parser');

// import environmental variables from our variables.env file
require('dotenv').config({ path: 'variables.env' });

// Connect to our Database and handle an bad connections
mongoose.connect(process.env.DATABASE);

// import mongo db models
require('./models/user');
require('./models/team');

// Start our app!
const app = require('./app');
app.app.set('port', process.env.PORT || 7777);

const server = app.app.listen(app.app.get('port'), () => {
  console.log(`Express running → PORT ${server.address().port}`);
});

const io = require('socket.io')(server);

io.set('authorization', passportSocketIo.authorize({
  cookieParser: cookieParser,
  key:         app.sessionObj.key,       // the name of the cookie where express/connect stores its session_id 
  secret:      app.sessionObj.secret,    // the session_secret to parse the cookie 
  store:       app.sessionObj.store,        // we NEED to use a sessionstore. no memorystore please 
  success:     onAuthorizeSuccess,  // *optional* callback on success - read more below 
  fail:        onAuthorizeFail,     // *optional* callback on fail/error - read more below 
}));


function onAuthorizeSuccess(data, accept){}

function onAuthorizeFail(data, message, error, accept){}

io.on('connection', function(client) {  
  client.on('join', function(data) {
      client.emit('messages',"server socket response!!");
  });

  client.on('getmessage', function(data) {
    client.emit('messages',data);
});  

});

我的问题是,我有很多在./routes/index文件中进行的mongo DB保存操作,我希望能够从我的路由中发送消息,而不是从连接了socket.io的start.js的结尾处发出。

即使IO在start.js中设置,是否有任何方法可以从我的./routes/index文件中发出websocket消息?

例如像这样:

router.get('/add-team-member', (req, res) => {
  // some io.emit action here
});

也许我需要移动初始化socket.io的位置,但是我没有找到任何文档说明这一点,或者我可以在路由中已经以某种方式访问socket.io吗?
谢谢并感谢您的帮助,如果有不清楚的地方,请告诉我!

1
你应该能够使用 io 实例向所有客户端广播消息。https://dev59.com/UWs05IYBdhLWcg3wQvoe - WoodyWoodsta
5个回答

2
如上所述,io位于全局作用域。如果您这样做:
router.get('/add-team-member', (req, res) => {
    io.sockets.emit('AddTeamMember');
});

然后,每个连接的客户端,如果监听到AddTeamMember事件,将在其各自的客户端上运行相应的.on函数。这可能是最简单的解决方案,除非您预计会有大量用户而没有负载平衡计划,否则在目前这个时候应该是适合的。
另一个选择是: socket.io库具有房间功能,您可以加入并使用io对象本身进行发射,详情请参见https://socket.io/docs/rooms-and-namespaces/,如果您对此有兴趣,它看起来会像这样:
io.sockets.in('yourroom').broadcast('AddTeamMember');

这将与顶部的操作基本相同,只是不会广播给每个客户端,而是只广播给那些专属于该房间的客户端。你需要在他们发出get请求之前或者换句话说,在他们成为专属用户之前,想办法将他们的socket加入到房间中。这样一来,每当路由请求被发出时,您可以减少服务器需要推送的负载。

最后,如果以上两种选项都不适用于您,并且您绝对必须在用户发起请求时向其发送消息,那么情况就会变得混乱,因为您必须拥有某种id来标识该人,而由于没有参考,您必须在连接时存储所有socket,然后进行比较。我并不完全建议使用这样的方法,因为我从未测试过它,并不知道可能会产生什么后果,但以下是我所想到的一个简要想法:

app.set('trust proxy', true)
var SOCKETS = []
io.on('connection', function(client) {
  SOCKETS.push(client);
  client.on('join', function(data) {
    client.emit('messages',"server socket response!!");
  });

  client.on('getmessage', function(data) {
    client.emit('messages',data);
  });
});

router.get('/add-team-member', (req, res) => {
    for (let i=0; i< SOCKETS.length; i++){
        if(SOCKETS[i].request.connection.remoteAddress == req.ip)
          SOCKETS[i].emit('AddTeamMember');
    }
});

请记住,如果你选择这条路,当用户断开连接时,你需要维护那个数组。如果你正在进行会话管理,那么这将变得非常复杂。

祝好运,让我们知道你的结果。


这个答案的问题在于你在 start.js 中在运行时定义了 io,而 router 是在 routes/Index 文件中定义的,该文件在 app.js 中被引用,因此在那一点上 router 将是未定义的,不是吗? - red house 87
如果在app.js中定义了routes/index,并且app.js在io之前定义,那么就没有问题。 - simon
基本上流程如下:应用程序和io在服务器启动时被定义。用户连接,从而定义路由器。根据用户行为调用Router.route,即使是这种情况,io仍应在服务器上定义。 - simon
io 已在我的服务器上定义,并且我已经成功将其传递到我的路由,但是当我调用 io.sockets.sockets 时,它不会附加到任何套接字会话的路由上。这就是问题所在。 - red house 87

2

是的,这是可能的,只需在服务器收到请求时附加socket.io实例即可。查看您的start.js文件,您只需要更改以下函数:

// Start our app!
const app = require('./app');
app.app.set('port', process.env.PORT || 7777);
const io = require('socket.io')(app.app);

const server = app.app.listen(app.app.get('port'), () => {
server.on('request', function(request, response){
    request.io = io;
}
console.log(`Express running → PORT ${server.address().port}`);
});

现在当您收到一个事件并希望向客户端发送一些消息时,您可以使用请求对象中的io实例。
router.get('/add-team-member', (req, res) => {
    req.io.sockets.emit('addteammember', {member: 6});
    //as you are doing a broadcast you just need broadcast msg
    ....
    res.status(200)
    res.end()
});

我还可以将其与像 mocha 这样的测试框架集成,并测试发出的事件...

我做了一些类似的整合,根据我的经验,最后要做的是将消息发射到 socket 实例中。

作为一个良好的实践,中间件函数的开头应该进行数据验证、数据清洗和数据净化。下面是我的工作示例:

var app = require('../app');
var server = require('http').Server(app);
var io = require('socket.io')(server);

io.on('connection', function(client) {
        client.emit('connected');
        client.on('disconnect', function() {
            console.log('disconnected', client.id);
        });
});

server.on('request', function(request, response) {
    request.io = io;
});

pg.initialize(app.config.DATABASEURL, function(err){
  if(err){
    throw err;
  }

  app.set('port', process.env.PORT || 3000);

    var server1 = server.listen(app.get('port'), function(){
    var host = 'localhost';
    var port = server1.address().port;

    console.log('Example app listening at http://%s:%s', host, port);
  });
});

解决方案似乎不起作用!如果我将io实例附加到请求对象并尝试调用req.io.sockets.sockets,它应该返回所有活动套接字,但它却返回一个空对象... - red house 87
你能在 const server 之前放置 server.on('request', function(request, response){ request.io = io; } 并查看它是否有效吗?我有一个可工作的示例,我可以向你展示它的工作情况。 - Danizavtz
是的,恐怕那并不能解决问题,你有一个可以给我展示的仓库链接吗? - red house 87
我刚刚更新了我的答案,但是在Github上没有可用的示例。 - Danizavtz
这是我解决问题所依据的代码库:https://gist.github.com/patrickbrandt/1cd98a02c42e9e22a5a9 - Danizavtz

1

我曾经做过类似的事情,使用命名空间

假设您的客户端使用“Frontend”作为命名空间连接到您的服务器。 我的解决方案是在一个单独的文件中将socket.io的实例创建为一个类:

websockets/index.js

const socket = require('socket.io');

class websockets {
  constructor(server) {
    this.io = socket(server);
    this.frontend = new Frontend(this.io);

    this.io.use((socket, next) => {
      // put here the logic to authorize your users..
      // even better in a separate file :-)
      next();
    });
  }
}

class Frontend {
  constructor(io) {
    this.nsp = io.of('/Frontend');

    [ ... ]
  }
}

module.exports = websockets;

然后在 App.js 中。
const app = require('express')();
const server = require('http').createServer(app);
const websockets = require('./websockets/index');
const WS = new websockets(server);

app.use('/', (req, res, next) => {
  req.websocket = WS;
  next();
}, require('./routes/index'));

[ ... ]

最终,你的路由可以做到以下事情:

routes/index.js

router.get('/add-team-member', (req, res) => {
  req.websocket.frontend.nsp.emit('whatever', { ... });

  [ ... ]
});

1

您的 io 实际上是 socket 对象,您可以通过此对象向任何特定用户发出事件 -

io.to(userSocketId).emit('eventName', data);

或者您可以通过以下方式广播 -

io.emit('eventName', data);

在使用socket.io之前,请先创建require socket.io :)


1
你可以使用emiter-adapter将数据发送到其他进程/服务器的客户端。它使用redis数据库作为发出消息的后端。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接