Search code examples
pythonsocket.ioflask-socketiopython-socketio

SocketIO to emit to a particular user (without using separate room for every client)


I am trying to develop a web application that supports a long task at the backend. I am using flask-socketio package on my server along with celery. My workflow is following :

  1. When a client opens the Html page -- I initiate a socket connection to the server which creates a uid for the user and emits it back.
  2. Now once the user posts a request for the long task -- I schedule it using celery and once finished I need to emit it to the user who posted the request. (I stored the relevant userid in post request)

I have looked at @Miguels's answer for 39423646/flask-socketio-emit-to-specific-user which creates a separate room for each user and then broadcasts the message on that room. But I wanted to ask if there is some other simpler way to do this since it seems inefficient or forced way to do this.

I also came across the nodejs solution (how-to-send-a-message-to-a-particular-client-with-socket-io) which I felt to be a more natural way to accomplish this. Is there a similar solution in python-socketio too?


Update: After some more search I came across the following solution on a github gist. According to this -- ** flash-socketIO already puts all clients in the separate room given by request.sid **.

I would still wish to discuss other ways to do this. Specifically if the site traffic is quite high -- wouldn't it lead to too many rooms?


Update (2): my current (working) server code which makes use of rooms. This is borrowed and modified from flask-SocketIO celery example

@celery.task(bind=True)
def long_task(self, userid, url):
    # LONG TASK
    time.sleep(10)
    # meta = some result
    post(url, json=meta) 
    # It seems i can't directly emit from celery function so I mimic a post request and emit from that function 
    return meta

@app.route('/longtask', methods=['POST'])
def longtask():
    userid = request.json['userid']
    task = long_task.delay(elementid, userid, url_for('event', _external=True))
    return jsonify({}), 202

@socketio.on('connect', namespace='/custom')
def events_connect():
    userid = str(uuid.uuid4())
    session['userid'] = userid
    current_app.clients[userid] = request.sid
    emit('userid', {'userid': userid})

@app.route('/event/', methods=['POST'])
def event():
    userid = request.json['userid']
    data = request.json
    roomid = app.clients.get(userid)
    socketio.emit('celerystatus', data, namespace='/custom', room=roomid)
    return 'ok'
    



Solution

  • You don't have to create any rooms to address an individual user. Just set the to argument to the sid of the user you want to address:

    emit('my event', my_data, to=user_sid)
    

    The sid value that is assigned to each user is given to you in the connect event handler as request.sid.