【问题标题】:Working with multiple tabs with Socket.io使用 Socket.io 处理多个选项卡
【发布时间】:2017-04-22 16:03:36
【问题描述】:

以下代码在我的服务器端运行,一切正常。但是,我想在n 标签之间保持相同的连接,因为当我打开一个新标签时,看起来我已经从第一个标签断开了......那么,我怎样才能保持相同的连接?

client.js

socket.emit("connected", {user: inputUser.val()};

app.js

var express = require("express"),
app = express(),
http = require("http").Server(app),
io = require("socket.io")(http),
users = {};

io.on("connection", function(socket) {
    socket.on("connected", function(data) {
        socket.user = data.user;

        users[socket.user] = socket;

        updateUsers();
    });

    function updateUsers() {
        io.emit("users", Object.keys(users));
    }

    socket.on("typing", function(data) {
        var userMsg = data.user;

        if(userMsg in users) {
            users[userMsg].emit("typing", {user: socket.user});
        }
    });

    socket.on("disconnect", function(data) {
        if(!socket.user) {
            return;
        }

        delete users[socket.user];

        updateUsers();
    });

});

var port = Number(process.env.PORT || 8000);

http.listen(port, function() {
    console.log("Server running on 8000!");
});

更新: 上面的typing 事件工作正常...所以我根据答案尝试了typing 事件:

var express = require("express"),
    app = express(),
    http = require("http").Server(app),
    io = require("socket.io")(http),
    users = {};

io.on("connection", function(socket) {
    socket.on("connected", function(data) {
        socket.user = data.user;

        // add this socket to the Set of sockets for this user
        if (!users[socket.user]) {
            users[socket.user] = new Set();
        }
        users[socket.user].add(socket);

        updateUsers();
    });

    function updateUsers() {
        io.emit("users", Object.keys(users));
    }

    socket.on("typing", function(data) {
        var userMsg = data.user;

        if(userMsg in users) {
            users[userMsg].emit("typing", {user: socket.user});
        }
    });

    socket.on("disconnect", function(data) {
        if(!socket.user) {
            return;
        }

        // remove socket for this user
        // and remove user if socket count hits zero
        if (users[socket.user]) {
            users[socket.user].delete(socket);
            if (users[socket.user].size === 0) {
                delete users[socket.user];
            }
        }

        updateUsers();
    });

});

var port = Number(process.env.PORT || 8000);

http.listen(port, function() {
    console.log("Server running on 8000!");
});

但它给出了以下错误:

users[userMsg].emit("typing", {u​​ser: socket.user}); ^

TypeError: users[userMsg].emit 不是函数

更新²: 修复typing事件错误,我只是改成:

socket.on("typing", function(data) {
    var userMsg = data.user;

    if(userMsg in users) {
        for(let userSet of users[userMsg]) {
            userSet.emit("typing", {user: socket.user});
        }
    }
});

【问题讨论】:

标签: javascript node.js sockets socket.io real-time


【解决方案1】:

没有简单的方法可以在同一浏览器中的多个选项卡之间共享单个 socket.io 连接。多个选项卡的通常模型是每个选项卡都有自己的 socket.io 连接。

打开一个新选项卡和一个新的 socket.io 连接本身不应导致您的服务器认为任何东西都已断开连接。如果您的代码正在这样做,那么这就是您的代码中的错误,修复该特定错误可能更容易。

事实上,如果您想显式支持多个选项卡并能够识别多个选项卡可能都被同一个用户使用,那么您可能需要更改服务器端代码,以便它可以跟踪多个套接字对于单个用户,而不是当前如何编码为每个用户只跟踪一个套接字。

如果您的服务器代码真的只是试图跟踪哪些用户在线,那么通过引用计数每个用户可能有一种更简单的方法来做到这一点。稍后我会发布一个代码示例。

var express = require("express"),
app = express(),
http = require("http").Server(app),
io = require("socket.io")(http),
users = {};

io.on("connection", function(socket) {
    socket.on("connected", function(data) {
        socket.user = data.user;

        // increment reference count for this user
        if (!users[socket.user]) {
            users[socket.user] = 0;
        }
        ++users[socket.user];

        updateUsers();
    });

    function updateUsers() {
        io.emit("users", Object.keys(users));
    }

    socket.on("disconnect", function(data) {
        if(!socket.user) {
            return;
        }

        // decrement reference count for this user
        // and remove user if reference count hits zero
        if (users.hasOwnProperty(socket.user)) {
            --users[socket.user];
            if (users[socket.user] === 0) {
                delete users[socket.user];
            }
        }

        updateUsers();
    });

});

var port = Number(process.env.PORT || 8000);

http.listen(port, function() {
    console.log("Server running on 8000!");
});

如果您需要users 对象中包含socket 对象,那么您可以将存储在用户对象中的内容更改为Set 的套接字,如下所示:

var express = require("express"),
app = express(),
http = require("http").Server(app),
io = require("socket.io")(http),
users = {};

io.on("connection", function(socket) {
    socket.on("connected", function(data) {
        socket.user = data.user;

        // add this socket to the Set of sockets for this user
        if (!users[socket.user]) {
            users[socket.user] = new Set();
        }
        users[socket.user].add(socket);

        updateUsers();
    });

    function updateUsers() {
        io.emit("users", Object.keys(users));
    }

    socket.on("disconnect", function(data) {
        if(!socket.user) {
            return;
        }

        // remove socket for this user
        // and remove user if socket count hits zero
        if (users[socket.user]) {
            users[socket.user].delete(socket);
            if (users[socket.user].size === 0) {
                delete users[socket.user];
            }
        }

        updateUsers();
    });

});

var port = Number(process.env.PORT || 8000);

http.listen(port, function() {
    console.log("Server running on 8000!");
});

【讨论】:

  • 感谢您的回答,我正在尝试触发typing 事件,但没有成功...我已经更新了问题中的代码...我该如何解决?
  • @Igor - 我在回答您最初提出的问题时付出了很多努力,现在您已经更改了问题。请不要那样做。试着把你的真正的问题放在原来的问题中,这样你就不会改变别人的事情了。我猜var userMsg = data.user; 可能应该是var userMsg = socket.user;,然后如果您使用Set 解决方案,您必须更改users[userMsg].emit(...) 以遍历集合并发送到集合中的每个套接字,如果你'正在尝试向该用户打开的所有选项卡发送相同的消息。
  • 很抱歉,我认识到您的响应的努力和效果如何,但现在我尝试实施此事件并没有成功,所以我回来寻求帮助。 ..
  • 现在可以了!我用更正更新了我的问题。非常感谢您的回答和关注,再次向您道歉。非常感谢!
  • @user3304007 - 正如我的回答试图解释的那样,如果您想发送到多个选项卡,您必须跟踪每个传入连接属于哪个用户,然后何时要发送到所有连接用户,您必须遍历当前连接并发送到该用户的所有连接。这将是多个.emit() 调用。或者,您可以将来自给定用户的每个连接放入一个房间并使用一个.emit() 连接到该房间。
【解决方案2】:

对于仍然遇到此问题的任何人。这是我修复它的方法。 让我解释。 一旦页面刷新或打开一个新选项卡,socket 并不真正关心,所以它每次都会打开一个新连接。这更多的是优点而不是缺点。解决此问题的最佳方法是在服务器端,一旦用户使用他或她的用户名登录,您可以将该名称与客户端上的查询选项一起发送,以便将其用作唯一标识符。就我而言,我使用了令牌

this.socket = io.connect(`${environment.domain}` , {
      query: {token: this.authservice.authToken}
    });

然后在服务器端,您可以为键和值数组创建一个空数组。用户的用户名将用作键,相应的套接字数组作为值。在我自己的情况下,就像我说我使用了一个令牌

const users = [ ]
socket.nickname = (decoded token username);
users[socket.nickname] = [socket];

然后你可以执行一个简单的逻辑来检查一个用户是否已经存在于一个数组中,如果存在,则将新的套接字推送到用户的数组中

if ( user.username in users) {
                    console.log('already exists')
                    users[user.username].push(socket);
                } 

如果没有,只需创建一个新密钥并将套接字添加为密钥。(确保它是一个数组,因为用户始终可以使用相同的帐户刷新或打开一个新选项卡,并且您不希望发送聊天消息在一个标签中,而不是在另一个标签中交付)

else {
                    socket.nickname = username;
                    users[socket.nickname] = [socket];

                }

然后要发出消息,您只需遍历数组并相应地发出消息。这样每个选项卡都会收到消息

socket.on('chat', (data) => {

            if (data.to in users) { 

                for(let i = 0; i < users[data.to].length; i++) {
                    users[data.to][i].emit('chat', data)
                }
                for(let i = 0; i < users[data.user].length; i++) {
                    users[data.user][i].emit('chat', data)
                }


            }
       })

您也可以添加断开逻辑以从用户数组中删除套接字以节省内存,因此只有当前打开的选项卡是活动的和关闭的选项卡会被删除。希望能解决你的问题

【讨论】:

  • 我把你所说的和上面@jfriend00所说的混合在一起,它让我明白了。但这是一个棘手的问题,仅适用于优秀的开发人员;)
  • 我正在为非登录用户使用这个系统。您认为 IP 地址是一个不错的选择吗?如果这个人在同一个 wifi 上使用平板电脑、智能手机和 Mac,会好吗?当我有动力时,我会测试大声笑
  • @ThomasAumaitre 抱歉回复晚了,我不会收到通知。好吧,如果您使用的是未登录用户,您不妨直接发送到该用户的 socketId
【解决方案3】:

我的解决方案是将套接字连接到具有特定用户 ID 的房间。

io.on('connection', async (socket) => {

socket.join('user:' + socket.handshake.headers.uid) // The right way is getting `uid` from cookie/token and verifying user


})

一个优点是将数据发送到特定用户(发送到所有选项卡)

io.to('user:' + uid).emit('hello');

希望对你有帮助!

【讨论】:

    【解决方案4】:

    我相信最好的方法是为用户创建一个频道,并通过他们的 ID 来区分它,因此,当您需要接收或发送某些内容时,您可以使用该频道,并且连接到该频道的每个套接字都会接收到。

    【讨论】:

      【解决方案5】:

      另一种解决方案是将标志保存到localStorage并使用eventListener更改localStorage。 当另一个连接存在时不要连接。 并将消息保存在本地存储中以使用主选项卡发送。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-08-23
        • 2022-07-11
        • 2018-07-05
        • 1970-01-01
        • 2013-03-21
        • 2019-02-07
        • 2012-08-08
        相关资源
        最近更新 更多