关于 socket.io room的问题


#1

书中关于room的代码

@socketio.on('join')
def on_join(data):
    username = data['username']
    room = data['room']
    join_room(room)
    emit('status', username + 'has entered the room.', room=room)

我不太明白这段代码是怎么把这个用户加进去的?join_room方法不是传入了room作为参数么?感觉和user没关系啊…
服务端的这段代码是怎么和用户联系起来的?
另外书中关于客户端的代码

socket.on('connect', function(){
    socket.emit('join');
});

只是触发了join事件,是省略了什么么?


#2

这个问题有点意思,能挖出点深度。先mark。

简单来说,每个socketio链接都会有一个隐式sidjoin_room(room)后,也就是把这个sid加一个集合里去了。印象中是这么回事,回头有时间找资料确认一下。

socket.emit('join');

这一句就是触发了下面服务端的代码。

@socketio.on('join')  # `join`对应上面的`emit('join')`
def on_join(data):
    ...

#3

感谢回答!
我查了一下相关的代码,也觉得是服务端和客户端连接后有一个唯一标识,当服务端调用了join_room这个函数后,通过标识把此连接加入了指定的room中。

期待你的update!再次感谢!


#4

既然你也看过相关代码了,那么我把源码放出来交流下

首先是join_room()函数:

def join_room(room, sid=None, namespace=None):
    """Join a room.

    This function puts the user in a room, under the current namespace. The
    user and the namespace are obtained from the event context. This is a
    function that can only be called from a SocketIO event handler. Example::

        @socketio.on('join')
        def on_join(data):
            username = session['username']
            room = data['room']
            join_room(room)
            send(username + ' has entered the room.', room=room)

    :param room: The name of the room to join.
    :param sid: The session id of the client. If not provided, the client is
                obtained from the request context.
    :param namespace: The namespace for the room. If not provided, the
                      namespace is obtained from the request context.
    """
    socketio = flask.current_app.extensions['socketio']
    sid = sid or flask.request.sid
    namespace = namespace or flask.request.namespace
    socketio.server.enter_room(sid, room, namespace=namespace)

函数明显还有一个关键字参数sid,如果没有提供则从flask.request.sid获取。实际上如果打开浏览器控制台(浏览器按F12),查看socketio的连接,可以发现在query string里都会带有sid参数。

继续往下看,join_room()把几个参数赋给了socketio.serverenter_room()方法。

import socketio

...

class SocketIO(object):
    def __init__(self, app=None, **kwargs):
        ...
        if app is not None or 'message_queue' in kwargs:
            self.init_app(app, **kwargs)

    def init_app(self, app, **kwargs):
        ...
        self.server = socketio.Server(**self.server_options)

...

def join_room(room, sid=None, namespace=None):
    ...
    socketio.server.enter_room(sid, room, namespace=namespace)

下面开始进入socketiopython_socketio)的源码

class Server(object):

    def __init__(self, client_manager=None, logger=False, binary=False,
                 json=None, async_handlers=True, always_connect=False,
                 **kwargs):
        ...
        if client_manager is None:
            client_manager = base_manager.BaseManager()
        self.manager = client_manager
        ...

    ...

    def enter_room(self, sid, room, namespace=None):
        namespace = namespace or '/'
        self.logger.info('%s is entering room %s [%s]', sid, room, namespace)
        self.manager.enter_room(sid, namespace, room)

继续追踪base_manager.BaseManager(),找enter_room()方法的实现:

class BaseManager(object):
    def __init__(self):
        ...
        self.rooms = {}
        
    def enter_room(self, sid, namespace, room):
        """Add a client to a room."""
        if namespace not in self.rooms:
            self.rooms[namespace] = {}
        if room not in self.rooms[namespace]:
            self.rooms[namespace][room] = {}
        self.rooms[namespace][room][sid] = True  # 把sid加入该namespace的room

    def leave_room(self, sid, namespace, room):
        """Remove a client from a room."""
        try:
            del self.rooms[namespace][room][sid]  # 从namespace的room移除sid
            if len(self.rooms[namespace][room]) == 0:
                del self.rooms[namespace][room]
                if len(self.rooms[namespace]) == 0:
                    del self.rooms[namespace]
        except KeyError:
            pass

自此就可以看到一个room就是把sid存到相关字典里。


#5

感谢~~~我再好好研究一下!