socket.ioclient-serverflask-socketiostress-testingpython-socketio

How to create multiple client connections using python-socketio client for load testing Flask-Socketio server


I would like to create around 10,000 clients and use them to send and recieve messages from Flask-Socketio server. I am using the default Flask Werkzeug development web server.

This is the app.py

from flask import Flask, render_template
from flask_socketio import SocketIO

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)

@socketio.on('message')
def handle_message(message):
    print(message)

if __name__ == '__main__':
    socketio.run(app,debug=True)

This is the test_client.py

import socketio
from multiprocessing.pool import ThreadPool

# standard Python
sio = socketio.Client()

def f(thread):
    server = 'http://127.0.0.1:5000/' + str(thread)
    s = f'/{thread}'
    sio.connect(server)
    sio.emit('message', {'Hello': i}, namespace=s)

threads = 5
t = ThreadPool(threads)
t.map(f, range(0, threads))

Current test_client.py's Terminal Output:

raise exceptions.BadNamespaceError(
socketio.exceptions.BadNamespaceError: /2 is not a connected namespace.

Expected app.py's Terminal Output:

Hello: 0
Hello: 1
Hello: 2
Hello: 3
Hello: 4

Please if there is a different/ better way of doing this, send me the doc link to check.


Solution

  • There are a couple of problems with your design.

    In the client, you are using five different namespaces to connect to the server, /0 to /4. But in the server you have not defined any of these, your server operates under the default namespace /. So all these client connections are going to fail, because the server does not accept connections on unknown namespaces. For a namespace to be known, you have to implement at least one handler for it.

    But it is impractical to have to define handlers for that many namespaces, though. Namespaces are not a good feature to use in this case, because they cannot be added dynamically.

    So the first change is to have your clients connect using the default namespace.

    The second problem is that you are using a single client instance in all your threads. This is not how the client works, a client instance can only hold one connection. The solution is to create a client instance inside each thread.

    The changes for the above two problems are all in the client. Here is how I changed your client to work:

    import socketio
    from multiprocessing.pool import ThreadPool
    
    def f(thread):
        sio = socketio.Client()
        server = 'http://127.0.0.1:5000'
        sio.connect(server)
        sio.emit('message', {'Hello': thread})
    
    threads = 5
    t = ThreadPool(threads)
    t.map(f, range(0, threads))
    

    Output in the server:

    {'Hello': 3}
    {'Hello': 0}
    {'Hello': 1}
    {'Hello': 2}
    {'Hello': 4}