从路由中发出 websocket 消息

Posted

技术标签:

【中文标题】从路由中发出 websocket 消息【英文标题】:Emiting websocket message from routes 【发布时间】:2018-06-23 07:58:08 【问题描述】:

我正在尝试使用 websockets 设置我的服务器,这样当我通过路由更新某些内容时,我还可以在该路由上的某些内容更新时发出 websocket 消息。

这个想法是当有人点击路由/add-team-member 时将一些东西保存到我的 Mongo 数据库中,然后向通过 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 保存操作,我希望能够从我的路由而不是从 start.js 的末尾发出消息,其中套接字.io 已连接。

有什么方法可以从我的./routes/index 文件中发出 websocket 消息,即使在 start.js 中进一步设置了 IO?

例如这样的:

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

也许我需要移动我正在初始化 socket.io 的地方,但找不到任何关于此的文档,或者我可能已经以某种方式从路由访问 socket.io?

感谢并感谢您的帮助,如果有任何不清楚的地方请告诉我!

【问题讨论】:

您应该能够使用io 实例向所有客户端广播消息。 ***.com/questions/7352164/… 【参考方案1】:

是的,有可能,只要您在服务器上收到请求,您只需附加 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 等测试框架集成,并测试发出的事件...

我做了一些类似的集成,根据我的经验,最后要做的就是将 msg 发送到套接字中的实例。

作为一个很好的实践,我最初使用的中间件功能是进行数据验证、数据清理和清理数据。 这是我的工作示例:

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 它应该返回我所有的活动套接字,而不是返回我一个空白对象... 你能把 server.on('request', function(request, response) request.io = io; 放在 const 服务器之前看看它是否有效吗?我有一个工作示例和我可以告诉你它的工作原理 是的,恐怕不能解决它,你有指向你可以给我看的回购的链接吗? 我刚刚更新了我的答案,我在 github 上没有工作示例。 这是我基于我的解决方案的存储库:gist.github.com/patrickbrandt/1cd98a02c42e9e22a5a9【参考方案2】:

您的 io 实际上是套接字对象,您可以通过 -

从该对象向任何特定用户发出事件
io.to(userSocketId).emit('eventName', data);

或者你可以通过 -

进行广播
io.emit('eventName', data);

在使用前创建 require socket.io :)

【讨论】:

【参考方案3】:

您可以使用发射器适配器向其他进程/服务器中的客户端发出数据。它使用 redis DB 作为后端来发送消息。

【讨论】:

【参考方案4】:

如上所述,io 在您的全局范围内。如果你这样做了

router.get('/add-team-member', (req, res) => 
    io.sockets.emit('AddTeamMember');
);

然后每个连接的客户端,如果监听该事件 AddTeamMember,将在各自的客户端上运行它的关联 .on 函数。这可能是最简单的解决方案,除非您预计会有大量用户没有任何负载平衡计划,否则暂时应该是合适的。

你可以去的另一种选择: socket.io lib 有一个房间功能,你可以使用 io 对象本身https://socket.io/docs/rooms-and-namespaces/ 加入和发射,如果你有这方面的诀窍,它看起来像这样:

io.sockets.in('yourroom').broadcast('AddTeamMember');

这基本上与顶部做同样的事情,只是不是向每个客户广播,它只会向那些专属于该房间的人广播。您必须基本上想办法让用户在//之前// 他们发出获取请求之前进入房间,或者换句话说,让他们独占。这样,您可以减少每次发出路由请求时服务器必须推出的负载量。

最后,如果上述选项都不适合你,而你必须在他们启动它时发送给那个单一的客户,那么它会变得一团糟,因为你必须对那个人有某种 id ,并且由于您没有参考,因此您必须在连接时存储所有套接字,然后进行比较。我不完全推荐这样的东西,因为我从未测试过它,也不知道会发生什么类型的影响,但这是我的一个想法:

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 并且路由器是在 app.js 所需的路由/索引文件中定义的,因此路由器将在那个时候未定义点没有? 如果app.js中定义了routes/index,并且在io之前定义了app.js,则没有问题 基本上流程是这样的:app 和 io 是在服务器启动时定义的。用户连接,从而定义路由器。 Router.route 是根据用户行为调用的,即使是这样,io 仍然应该在服务器上定义。 io 是在我的服务器上定义的,我已经设法将它发送到我的路由,但是当我调用 io.sockets.sockets 时,它不会通过附加任何套接字会话进入我的路由。这就是问题【参考方案5】:

我过去做过类似的事情,使用namespaces。

假设您的客户端使用“前端”作为命名空间连接到您的服务器。 我的解决方案是在一个单独的文件中创建 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',  ... );

  [ ... ]
);

【讨论】:

以上是关于从路由中发出 websocket 消息的主要内容,如果未能解决你的问题,请参考以下文章

Python在tkinter中发送websocket消息

使用rabbitmq广播模式来处理集群下的websocket消息推送

带有异步计时器的 Python 异步 websocket 客户端

WebSocket实现Java后台消息推送

如何将 spring-data-rest 与 spring websocket 混合到一个实现中

web socket 入门