Node.js 中socket.io聊天室的问题

Node.js 中socket.io聊天室的问题

在跟随书上教程搭建socket.io的时候我遇到了以下问题:

	io.sockets.on('connection',function(socket){
	console.log('init');
	socket.on('getAllMessages',function(){
		socket.emit('allMessages',messages);
	})
	socket.on('createMessage',function(message){
		
		messages.push(message);
		io.sockets.emit('messageAdded',message);
	})
})

io.set('authorization',function(handshakeData,accept){
	handshakeData.cookie = Cookie.parse(handshakeData.headers.cookie);
	var connectSid = handshakeData.cookie['connect.sid'];
	
	if(connectSid){
		connectSid = parseSignedCookie(connectSid,'technode');
		sessionStore.get(connectSid,function(error,session){
			if(error){
				accept(error.message,false);
			}else{
				handshakeData.session = session;
				if(session._userId){
					accept(null,true);
				}else{
					accept('No login');
				}
			}
		})
	}else{
		accept('No session');
	}
})

这段代码中,如果我加入了io.set(‘authorization’,)的代码段,io.sockets.on(‘connection’)里面的代码就得不到执行。。请问是什么原因?


4 回复

在使用 socket.io 搭建聊天室时,你遇到的问题可能是由于 io.set('authorization') 的异步特性导致的。当 authorization 函数执行时,它会异步地检查用户的会话,而在这个过程中,io.sockets.on('connection') 事件处理函数可能已经被执行了。

原因分析

当你在 socket.io 服务器初始化时设置了 authorization 钩子,socket.io 会在每个连接到来时调用这个钩子进行权限验证。如果钩子中的异步操作没有完成,socket.io 无法确定连接是否应该被接受,因此可能导致后续的连接处理逻辑(如 io.sockets.on('connection'))不能正常执行。

解决方案

为了确保 authorization 钩子中的异步操作完成后,再执行连接处理逻辑,你需要正确处理异步回调。可以使用 callback 参数来通知 socket.io 验证结果。

示例代码

const io = require('socket.io')(server);
const cookie = require('cookie');
const connect = require('connect');
const session = require('express-session');
const RedisStore = require('connect-redis')(session);

// 创建一个 Redis 存储实例
const sessionStore = new RedisStore({
    // Redis 连接配置
});

io.set('authorization', function (handshakeData, accept) {
    const cookies = cookie.parse(handshakeData.headers.cookie || '');
    const connectSid = cookies['connect.sid'];

    if (!connectSid) {
        return accept('No session', false);
    }

    sessionStore.get(connectSid.replace(/(^.+; )?connect\.sid=|;.*$/g, ''), function (error, session) {
        if (error) {
            return accept(error.message, false);
        }

        if (!session || !session._userId) {
            return accept('No login', false);
        }

        handshakeData.session = session;
        return accept(null, true);
    });
});

io.sockets.on('connection', function (socket) {
    console.log('A user connected');

    socket.on('getAllMessages', function () {
        socket.emit('allMessages', messages);
    });

    socket.on('createMessage', function (message) {
        messages.push(message);
        io.sockets.emit('messageAdded', message);
    });
});

解释

  1. cookie 解析:我们使用 cookie 库解析客户端发送的 cookie。
  2. session 获取:通过 sessionStore.get 方法从 Redis 中获取会话数据。
  3. 错误处理:如果在获取会话数据时发生错误,或者会话不存在或用户未登录,直接返回错误信息并拒绝连接。
  4. 会话存储:将会话数据存储到 handshakeData 对象中,以便后续的连接处理逻辑可以访问这些数据。

通过这种方式,你可以确保 authorization 钩子中的异步操作完成后再执行 connection 事件处理逻辑,从而避免你的问题。


你的代码要求从客户端浏览器中发送connect.sid这个cookie 名称。你说无法执行io.sockets.on(‘connection’)这个,估计就是这个原因。你可以在客户端脚本中加入error事件, 应该很快就知道这个error事件中的参数的值是 No session 或 No login

你把io.set('authorization’)整个方法注释掉 的含义是指不启用认证。 你把io.set('authorization’)内的代码全部注释掉,这个是启用了认证,但没有明确表示认证通过,所以就无法连接了. 你可以加上io.set('authorization’)这个,然后这个事件中直接加上accept(null,true);应该就可以了。 另外,你使用的不是1.0以上的版本吧。。

你在代码中使用了io.set('authorization', ...)来实现基于会话的身份验证。然而,在authorization函数中的异步操作(即获取会话)可能没有正确完成,导致accept函数没有被调用。如果accept没有被调用,那么客户端的连接就不会被接受,因此io.sockets.on('connection')中的回调也不会被执行。

为了解决这个问题,你需要确保在accept中处理所有情况,并且始终调用它。你可以通过添加错误处理来确保accept总是被调用,即使发生了错误。以下是修改后的示例:

io.set('authorization', function(handshakeData, accept) {
    handshakeData.cookie = Cookie.parse(handshakeData.headers.cookie);
    var connectSid = handshakeData.cookie['connect.sid'];

    if (connectSid) {
        connectSid = parseSignedCookie(connectSid, 'technode');
        sessionStore.get(connectSid, function(error, session) {
            if (error) {
                return accept(error.message, false); // 确保错误时调用accept
            } else {
                handshakeData.session = session;
                if (session._userId) {
                    return accept(null, true); // 确保成功时调用accept
                } else {
                    return accept('No login', false); // 确保未登录时调用accept
                }
            }
        });
    } else {
        return accept('No session', false); // 确保无会话时调用accept
    }
});

这样可以确保无论成功、失败还是其他情况,accept都被调用。这将允许客户端连接并继续执行io.sockets.on('connection')中的代码。

如果你使用的是较新的socket.io版本,set方法已被弃用,你应该使用use方法来设置中间件。下面是使用use方法的示例:

io.use(function(socket, next) {
    var handshakeData = socket.request;
    handshakeData.cookie = Cookie.parse(handshakeData.headers.cookie);
    var connectSid = handshakeData.cookie['connect.sid'];

    if (connectSid) {
        connectSid = parseSignedCookie(connectSid, 'technode');
        sessionStore.get(connectSid, function(error, session) {
            if (error) {
                return next(new Error(error.message)); // 使用next传递错误
            } else {
                handshakeData.session = session;
                if (session._userId) {
                    return next(); // 没有错误,继续下一个中间件
                } else {
                    return next(new Error('No login')); // 未登录时传递错误
                }
            }
        });
    } else {
        return next(new Error('No session')); // 无会话时传递错误
    }
});

通过这种方式,你可以确保身份验证逻辑正确处理,并允许正常的连接流程继续进行。

回到顶部