【问题标题】:Socket.io double emits when executing express (v4) route执行 express (v4) 路由时 Socket.io 双发出
【发布时间】:2020-10-22 00:42:54
【问题描述】:

我在执行 express (v4) 路由时遇到双重发射问题。

例如,让我们运行下面的代码。请求(管道)和 mkdir 将立即启动(竞争条件)并且会有错误,关于该文件夹不存在(mkdir 无法足够快地创建它,在请求启动并开始管道数据之前)。此错误的原因是用户被重定向到需要重新填写表单的页面 X。现在(重定向并重新填充表单后)一切正常(因为文件夹在数据到达时确实存在,因为它是之前创建的 - 导致异步操作必须完成? - 即使用户已经在“后面”执行代码重定向)但是发出两次 - 一次用于旧数据,一次用于新数据(复制 HTML 页面结构的一部分)。

x.js 文件中的路由:

router.post('/Y', function(req, res) {
    request(opt, function(err, resp, body) {
        if (resp.statusCode === 200) {
            res.render('Y'); // so the emits can target something (they build html structure on page 'Y' with proper data)

            function socCon()
            {
                var room = '';
                return new Promise(function(resolve, reject)
                {
                    req.io.on('connection', function(socket)
                    {
                        room = 'room-' + socket.id;
                        socket.join(room);

                        if (socket.join(room)) {
                            resolve(room);
                        }
                        else {
                            reject(console.log('error'));
                        }
                    });
                });
            }
            socCon().then(data).catch(function(error)
            {
                console.log(error);
            });

            function data(room) {

                var myFolder = 'myFolder/';

                fs.mkdir(myFolder, {recursive : true}, function(err) {
                    if (err) {console.log(err);}
                });

                var file = fs.createWriteStream(myFolder + 'data.html', 'utf8');

                sio.emit('msg', {emit : emit}); // socket.io emits - with use of express route POST on page 'X'

            });
        }
        else {
            res.render('X', {ERRORS : ERRORS});
            res.end();
        }
    }).on('response', function(response) {
    }).pipe(file).on('error', function(err) {
        res.render('X', {ERRORS : ERRORS}); // we inform the user that he put wrong data in input field or that there is internal server error (whatever)
        res.end();
        // so we end 'Y' page connection here (but socket.io somehow collect all the data, emit it later on - when same route is hit again) and socket.sendBuffer does not work)
    });
)};

有没有办法阻止旧的排放?我试过socket.sendBuffer = [];(@jfreind00 建议在 -socket.io stop re-emitting event after x seconds/first failed attempt to get a response)无济于事。理想的情况是......当有重定向时停止整个请求。但是你不能阻止异步代码的执行(或者我只是不知道怎么做)。

客户页面 Y:

<!DOCTYPE html>
<html>
<head>
    <title>TITLE</title>
</head>
<body>
<script src="/socket.io/socket.io.js"></script>
<script type="text/javascript">
    socket.on('connect', function() {
        socket.sendBuffer = []; // does not work...
    });
    socket.on('msg', function(msg) {
        // some HTML mutation here
    });
</script>
</body>
</html>

当然代码要复杂得多,但这足以(我认为)显示问题。这个“应用程序”可能是一个糟糕的设计,但我不知道如何修复它。

index.js 文件(主“app”文件):

var x = require('./routes/x.js');
var app = express();
app.use('/', x);
app.use(function(req, res, next) {
    req.io = io;
    next();
});
var io = require('socket.io')(server);

io.on('connection', function (soc) {
    soc.on('disconnect', function(reason) {
        console.log(reason);
    });
    // etc.
});

【问题讨论】:

  • 我实际上不明白您在这里问的是哪个特定问题。请概述发生的确切步骤以及您想要发生的事情。它是否从浏览器开始转到http://somedomain.com/Y,那么究竟会发生什么?目前尚不清楚您要向谁发送 .emit(),因为您无法 .emit() 访问正在被浏览器加载的页面。
  • 另外,request() 和 `.pipe() 背后的原因是什么?你想用你创建的文件做什么?
  • somedomain.com/X 是带有输入字段的起始域。如果用户填写它,路线会将他带到somedomain.com/Y。在 Y 处,当他浏览页面时,请求的结果(在路由 POST Y 中)被发送并更新页面。但有时(例如,当文件夹未按时创建时 - 用户被重定向到需要再次填写输入的页面 X)发出两次发送。请求检查并下载数据,我想存储文件(带有请求的数据),以便稍后与新文件进行比较。
  • 那么,为什么不从解决您显然已经知道的mkdir() 问题开始呢?谁得到双重发射?我还不明白那部分。
  • 是的,它很容易通过 asyncs/await 修复,我只是想说明问题 - 但仍然不应将排放量加倍。用户获得双重发射(旧数据,可能导致异步代码在“后面”运行+他刚才传递的新数据)。当服务器例如给我们错误时也会发生这种情况 - 当再次检查相同的数据时,用户确实会得到两倍/三倍等。 Socket.io 不知何故知道此 id 已连接到此用户(代理?),即使每个新的套接字 io 连接都使用新 ID,它也会设法将旧用户 ID 与新用户 ID(?)连接并发出数据。跨度>

标签: javascript node.js socket.io


【解决方案1】:

这行代码:

req.io.on('connection', function(socket) {...});

每次运行此路由时只需添加一个新的重复事件处理程序。因此,在两次运行路由之后,您将拥有两个用于 connection 事件的事件处理程序。运行该路由 3 次后,您将拥有三个用于 connection 事件的事件处理程序,依此类推。它们只是积累,永远不会消失。

所以,这就解释了重复处理。您有重复的事件处理程序,因此相同的 connection 事件会被处理多次。

我真的无法提出修复建议,因为我没有遵循您要完成的任务,并且试图捕捉下一个 connection 事件并假设属于特定页面的整个计划是有缺陷的(完整竞争条件问题) - 更不用说它添加了一组永无止境的重复事件处理程序。您将需要一个侦听器来监听任何路由处理程序之外的connection 事件,然后使用该传入事件来执行您想做的任何事情。

【讨论】:

  • 我想通过 socket.io 与用户连接并仅为他发送数据 - 没有连接和自定义房间,我只能针对所有用户。以及移动到新页面(重定向)时socket.io断开连接的原因我确实尝试为此使用路由(移动到Y时从页面X保存数据)。我在 index.js 上确实有连接 - 但这无法正常工作 - 移动到“/Y”时用户断开连接,需要检查/下载的数据丢失。在页面之间移动时,我无法保持 socket.io 连接。
  • 我还是不太明白 socket.io 是如何向同一个用户发出目标的,当两次运行这条路由时(因为每次都有不同的连接 ID,当 scoket.io 时用户会自动与房间解除绑定连接丢失 - 与 void ID 的旧连接如何工作?)。当我从页面'/Y'移动到页面'/X'(没有错误时)并再次填写表格时 - 它工作正常(相同路线)。当我刷新页面'/Y'时它工作正常(再次使用相同的路线)。
  • 只有当出现错误(用户被自动重定向到页面'/X')并再次使用表单(路由)时,才会出现重复。为什么 socket.sendBuffer = [];不工作?很抱歉在这里很慢,这对我来说都是新的。感谢您的帮助。
  • @b4rtekb - 你了解每次运行req.io.on('connection', function(socket) {...}); 时,它会为connection 事件添加一个新的全局socket.io 处理程序,并且由于你从未删除这些事件处理程序,它们会累积起来在到达您的路线 5 次后,您是否安装了 5 个该事件处理程序的副本?那么,此后每次connection 事件发生时,您运行事件处理程序代码5 次?您是否了解其结构的基本缺陷。这解释了您看到的重复消息。
  • @b4rtekb - 你从来没有真正从第一原理描述过你想在这里做什么,但在我看来,你需要一种完全不同的方法来解决这个问题,因为你有多个有缺陷的假设以及您在此处所拥有的实现(竞争条件、与尚未初始化的网页的通信等...)。你根本不能这样做。
猜你喜欢
  • 2016-09-20
  • 1970-01-01
  • 2015-09-07
  • 1970-01-01
  • 2016-08-22
  • 2016-09-06
  • 2021-05-14
  • 2014-06-10
  • 1970-01-01
相关资源
最近更新 更多