Tìm kiếm trang web

javascript - Phát tin nhắn websocket từ các tuyến


Tôi đang cố gắng thiết lập máy chủ của mình với websockets để khi tôi cập nhật một thứ gì đó qua các tuyến của mình, tôi cũng có thể phát đi một tin nhắn websocket khi có điều gì đó trên tuyến đó được cập nhật.

Ý tưởng là lưu một thứ gì đó vào Mongo db của tôi khi ai đó truy cập vào route /add-team-member chẳng hạn, sau đó gửi một tin nhắn đến tất cả mọi người đang kết nối qua websocket và là một phần của bất kỳ phòng websocket nào tương ứng với đội đó.

Tôi đã theo tài liệu của socket.io để thiết lập ứng dụng của mình theo cách sau:

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};

bắt đầu.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);
});  

});

Vấn đề của tôi là tôi có rất nhiều hành động lưu MongoDB đang diễn ra trong tệp ./routes/index của mình và tôi muốn có thể gửi thông điệp từ các route của mình thay vì từ cuối start.js nơi socket.io được kết nối.

Có cách nào để tôi có thể phát một thông điệp websocket từ file ./routes/index của mình mặc dù IO được thiết lập sau đó trong start.js không?

ví dụ gì đó như thế này:

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

Có lẽ tôi cần di chuyển nơi tôi đang khởi tạo các thứ của socket.io nhưng chưa thể tìm thấy tài liệu nào về điều này hoặc có lẽ tôi có thể truy cập socket.io từ các route đã có sẵn theo cách nào đó?

Cảm ơn và trân trọng sự giúp đỡ, cho tôi biết nếu có điều gì không rõ!

Câu trả lời #1

Như đã đề cập ở trên, io nằm trong phạm vi toàn cục của bạn. Nếu bạn làm

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

Sau đó mọi client được kết nối, nếu lắng nghe sự kiện AddTeamMember đó, sẽ chạy hàm .on tương ứng trên các client của họ. Đây có lẽ là giải pháp dễ dàng nhất, và trừ khi bạn đang mong đợi một lượng lớn người dùng mà không có kế hoạch cân bằng tải nào, điều này nên là phù hợp trong thời điểm hiện tại.

Một lựa chọn khác bạn có thể thử: thư viện socket.io có chức năng rooms, nơi bạn có thể tham gia và phát sự kiện bằng chính đối tượng io https://socket.io/docs/rooms-and-namespaces/ nếu bạn có năng khiếu về điều này, nó sẽ trông giống như sau:

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

Điều này về cơ bản sẽ làm cùng một việc như phía trên, chỉ khác là thay vì phát sóng tới mọi khách hàng, nó sẽ chỉ phát sóng tới những người chỉ có trong phòng đó. Bạn sẽ phải cơ bản tìm cách để đưa socket của người dùng vào phòng //trước khi// họ thực hiện yêu cầu get, hay nói cách khác là làm cho họ trở nên độc quyền. Bằng cách đó, bạn có thể giảm lượng tải mà server của bạn phải xử lý mỗi khi yêu cầu route đó được thực hiện.

Cuối cùng, nếu cả hai lựa chọn trên đều không phù hợp với bạn, và bạn thực sự phải gửi cho khách hàng đó khi họ khởi tạo, thì nó sẽ trở nên phức tạp, vì bạn phải có một dạng nhận dạng cho người đó, và vì bạn không có tham chiếu nào, bạn sẽ phải lưu tất cả các socket của mình khi kết nối, rồi sau đó thực hiện so sánh. Tôi không hoàn toàn khuyến nghị một điều như vậy, bởi vì, thật ra, tôi chưa bao giờ thử nghiệm nó và không biết sẽ xảy ra những hậu quả gì, nhưng đây là ý tưởng tổng quát mà tôi nghĩ đến:

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');
    }
});

Hãy nhớ, nếu bạn đi theo con đường này, bạn sẽ cần phải duy trì mảng đó khi người dùng ngắt kết nối, và nếu bạn đang quản lý phiên, điều đó sẽ trở nên phức tạp rất nhanh.

Chúc may mắn, cho chúng tôi biết kết quả của bạn nhé.

Câu trả lời #2

Vâng, điều đó là có thể, bạn chỉ cần gắn kết instance của socket.io miễn là bạn nhận được một yêu cầu trên server của mình. Nhìn vào file start.js của bạn, bạn chỉ cần thay thế các hàm của mình như sau:

// 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}`);
});

Bây giờ, khi bạn nhận được một sự kiện mà bạn muốn phát đi một số thông điệp tới các khách hàng, bạn có thể sử dụng thể hiện io của bạn từ đối tượng yêu cầu.

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()
});

Việc đó tôi cũng đã có thể tích hợp với khung kiểm thử như mocha, và kiểm thử các sự kiện được phát ra nữa...

Tôi đã thực hiện một số tích hợp như vậy, và theo kinh nghiệm của tôi thì điều cuối cùng cần làm là gửi tin nhắn đến các instance trong socket.

Theo một thực hành tốt, ngay từ đầu các hàm middleware mà tôi có đều thực hiện việc xác thực dữ liệu, làm sạch dữ liệu và vệ sinh dữ liệu. Đây là ví dụ đang hoạt động của tôi:

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);
  });
});