miguelgrinberg / Flask-SocketIO

Socket.IO integration for Flask applications.
MIT License
5.37k stars 892 forks source link

Cannot receive binary packet from python-socketio client, and cannot combine Flask static files with websocket? #1004

Closed chenzx closed 5 years ago

chenzx commented 5 years ago
import os, sys
import flask_socketio
import logging

console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
formatter = logging.Formatter('[%(levelname)s]%(message)s')
console.setFormatter(formatter)

logger = logging.getLogger(__name__)
logger.addHandler(console)
logger.setLevel(logging.DEBUG)

from threading import Lock
from flask import Flask, render_template, session, request, \
    copy_current_request_context
from flask_socketio import SocketIO, emit, join_room, leave_room, \
    close_room, rooms, disconnect

async_mode = "threading"

app = Flask(__name__, static_url_path='')
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app, async_mode=async_mode)
thread = None
thread_lock = Lock()
gles2_cmds = []

@app.route('/static/<path:path>')
def send_static(path):
    logger.debug("path={}".format(path))
    return app.send_from_directory('static', path)

@app.route('/')
def index():
    logger.debug("/")
    return app.send_static_file('index.html')

def background_thread():
    logger.debug("in background_thread")
    count = 0
    global gles2_cmds
    while True:
        if len(gles2_cmds)==0:
            socketio.sleep(10)
            continue
        count += 1
        gles2_cmd = gles2_cmds.pop(0)
        socketio.emit('gles2_cmd', gles2_cmd, namespace='/gles2_cmd', broadcast=True)

@socketio.on('gles2_cmd_input', namespace='/gles2_cmd_input')
def handle_message(gles2_cmd):
    cmd_len = int.from_bytes(gles2_cmd[0:4], byteorder='little', signed=False)
    cmd_type = int.from_bytes(gles2_cmd[4:8], byteorder='little', signed=False)
    print('received gles2_cmd input: cmd_len={} cmd_type={}'.format(cmd_len, cmd_type))
    global gles2_cmds
    gles2_cmds.append(gles2_cmd)

if __name__ == '__main__':
    thread = socketio.start_background_task(background_thread)
    socketio.run(app, host="0.0.0.0", port=8888, debug=True)
chenzx commented 5 years ago

I'm writing a demo to streamming GLES2 cmd data to browser js via websocket and i'm following the docs.

There are 2 problems:

  1. when i access http://127.0.0.1:8888/ , i got a 404 error, but isn't this code sample for combining Flask and socketio together?
  2. i'm use Python-Socketio as a websocket client and do "emit" calls to transfer data, but log in handle_message does not hit.
chenzx commented 5 years ago

wait, problem 2 is solved, i found i had started 2 server scripts, and the later one didn't get any data -- that's ok, but problem 1 is still wierd...

miguelgrinberg commented 5 years ago

when i access http://127.0.0.1:8888/

How are you doing this. Can you show me client code?

chenzx commented 5 years ago

There are 2 clients:

1、python socketio client, to send data, this is now connected, and send OK, 2、native js client in Chrome M75:

    var canvas = document.getElementById("canvas");
    var gl = canvas.getContext("webgl");
    if (!gl) {
    return;
    }

    // 这里初始化ws连接:
    var websocket = new WebSocket("ws://127.0.0.1:8888/gles2_cmd");
    websocket.onmessage = function (event) {
        console.log(event.data);
    }

This fails, 404. I do not understand how Flask-SocketIO coordinates with Flask?

chenzx commented 5 years ago

Python socketio client script:

import os, sys, socketserver, logging
import socketio
from gles2_cmd_names_gen import GLES2_cmd_packet_type_names

LOCAL_TCP_LISTEN_PORT = 9999
UPSTREAM_WEBSOCKET_SERVER = 'http://127.0.0.1:8888/gles2_cmd_input'  # ?

console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
formatter = logging.Formatter('[%(levelname)s]%(message)s')
console.setFormatter(formatter)

logger = logging.getLogger(__name__)
logger.addHandler(console)
logger.setLevel(logging.DEBUG)  # Fk. this line call is needed!

sio = socketio.Client()
sio.connect(UPSTREAM_WEBSOCKET_SERVER)
print('websocket client sid is', sio.sid)

class MyTCPHandler(socketserver.StreamRequestHandler):
    def read_uint32(self):
        data = self.rfile.read(4)
        return data, int.from_bytes(data, byteorder='little', signed=False)

    def handle(self):
        logger.debug("enter handle ...")
        while True:
            l, packet_length = self.read_uint32()  # 读取1个uint32_t
            t, packet_type = self.read_uint32()
            logger.debug("packet_length={} packet_type={}({})".format(
                packet_length,
                GLES2_cmd_packet_type_names[packet_type],
                packet_type
                )
            )
            packet_data_left = self.rfile.read(packet_length - 8)
            total_packet_data = l + t + packet_data_left
            # now send as ws client:
            sio.emit("gles2_cmd_input", total_packet_data, namespace='/gles2_cmd_input')

if __name__ == "__main__":
    logger.debug("starting tcp2websocket server at {} ...".format(LOCAL_TCP_LISTEN_PORT))
    server = socketserver.ThreadingTCPServer(("", LOCAL_TCP_LISTEN_PORT), MyTCPHandler)  # 线程
    server.serve_forever()

This side is sending ok, and log hit in handle_message.

What i'm doing is: receive binary data in one "/gles2_cmd_input" namespace, and cache it in a memory queue, then broadcast to all browser clients in another namespace.

miguelgrinberg commented 5 years ago

You are using a websocket client for JS. Use the Socket.IO client. They're not the same thing.

chenzx commented 5 years ago

I see, i'll try...

chenzx commented 5 years ago

I'm using Socket.IO client lib now:

    /*
    var websocket = new WebSocket("ws://127.0.0.1:8888/gles2_cmd_input");
    websocket.onmessage = function (event) {
        console.log(event.data);
    }*/
    const socket = io('/gles2_cmd');
    socket.onmessage = function (event) {
        console.log(event.data);
    }
    socket.on("gles2_cmd", function (event) {
        console.log(event.data);
    })
    socket.connect();

But still can not receive message/data from server side. Why?

miguelgrinberg commented 5 years ago

@chenzx Why don't you start from the example in this repository and adapt it to your needs?

chenzx commented 5 years ago

@miguelgrinberg i made another mistake, i use a separate local port to server static index.html, and now it emits and receives ok!

Now there is only one problem left:

I have config'ed Flash static index.html to route '/', but why can't i load it using http://127.0.0.1:8888/ ?

does Flask-SocketIO call socketio.run(app, host="0.0.0.0", port=8888, debug=True) also start Flask app?

miguelgrinberg commented 5 years ago

@chenzx Yes. As I said above, please start from the example in this repository, which serves static files as well as Socket.IO.

Edit: after looking briefly at your application, my guess is that the unusual way you are handling static files is the source of your problems. I suggest you let Flask handle static files as usual.

chenzx commented 5 years ago

I'm using the following simple code:

@socketio.on('gles2_cmd_input', namespace='/gles2_cmd_input')
def handle_message(gles2_cmd):
    cmd_len = int.from_bytes(gles2_cmd[0:4], byteorder='little', signed=False)
    cmd_type = int.from_bytes(gles2_cmd[4:8], byteorder='little', signed=False)
    print('received gles2_cmd input: cmd_len={} cmd_type={}'.format(cmd_len, cmd_type))
    socketio.emit('gles2_cmd', gles2_cmd, namespace='/gles2_cmd', broadcast=True)
    #global gles2_cmds
    #gles2_cmds.append(gles2_cmd)

if __name__ == '__main__':
    # thread = socketio.start_background_task(background_thread)
    socketio.run(app, host="0.0.0.0", port=8888, debug=True)

Everything is ok. Note here i'm not starting a background thread, also the wierd thing is, Flask app static serve is always NOT ok, i have to using another Python http-server script (at different port) to serve static files.

Can't figure out why.

What does "let Flask handle static files as usual" mean? I thought Flask-SocketIO is a wrapper around the Flask app so it will also start flask app internally? But seems cases are not so.

miguelgrinberg commented 5 years ago

What does "let Flask handle static files as usual" mean?

I mean don't do this:

app = Flask(__name__, static_url_path='')

or this:

@app.route('/static/<path:path>')
def send_static(path):
    logger.debug("path={}".format(path))
    return app.send_from_directory('static', path)

Or in other words, let Flask handle static files as it does by default. Once you have that working, make the changes that you need.

chenzx commented 5 years ago

browser socket.io client code: (separate ws and static index.html, it's ok)

    const socket = io('ws://127.0.0.1:8888/gles2_cmd');
        //io("/gles2_cmd") not ok, since Flask-SocketIO server script cannot serve static index.html
    socket.on("gles2_cmd", function (data) {
        // data is ArrayBuffer type;
        const view = new Uint32Array(data);
        let packet_length = view[0]
        let packet_type = view[1]
        console.log(`packet_length=${packet_length} packet_type=${packet_type}`)
    })
    socket.connect();
chenzx commented 5 years ago

@miguelgrinberg but if i don't write any Flask static route, it(Flask) default cannot do static file serve!

app = Flask(__name__) #, static_url_path='')

'''
@app.route('/static/<path:path>')
def send_static(path):
    logger.debug("path={}".format(path))
    return app.send_from_directory('static', path)

@app.route('/')
def index():
    logger.debug("/")
    return app.send_static_file('index.html')
'''
chenzx commented 5 years ago

Hi, problem solved:

app.send_static_file seems assuming 'index.html' MUST be in 'static' sub dir. But i never config this in code! I guess this is Flask's Convention-over-Config?