There must be a simple mistake in my understanding of reactjs or socket.io. I have a server that sends "speed" values iterating from 2 to 5, and a client that receives them and displays them.
The issue:
Expected behavior: client displays numbers iterating from 1 to 5 every second and stops at 5. Client logs that it has received 4 messages with updated speed value (a new message every second)
Actual behavior: client displays a speed of 1. Client waits 4 seconds. Client displays a speed of 5 and, in that moment, logs having received the 4 messages with iterating speed from the server.
What is the issue? It's almost as if the server is sending all 4 speed messages at the same time.
Client code:
import React from 'react';
import {CircleGauge} from 'react-launch-gauge';
import io from 'socket.io-client';
class App extends React.Component {
constructor(props, context) {
super(props, context)
this.state = {
speed: 1
};
}
componentDidMount() {
const socket = io('http://localhost:5000');
socket.on('data update', data =>
this.setState( { speed: data },
() => console.log("got the speed: " + this.state.speed)));
socket.open();
}
render() {
return (
<div>
<p> The velocity received is: {this.state.speed} </p>
</div>
);
}
}
export default App;
Server Code:
from flask import Flask, render_template
from flask_socketio import SocketIO, emit
import time
sendData = False;
app = Flask(__name__)
socketio = SocketIO(app)
#socketio.on('connect')
def dataSent():
print('they connected**********************************')
for i in range(2,6):
socketio.emit('data update', i)
time.sleep(1)
print(i)
if __name__ == '__main__':
socketio.run(app, debug = True)
When the client requests the websocket connection via
const socket = io(‘ws://localhost:5000’);
it will have to wait until the connection is established first, before it can do anything with the message. The connection is established only when
#socketio.on(‘connect’)
def ...
is finished. So it looks like the client waits until all the emit is done, and then start reacting to the messages.
You probably need to make your for-loop asynchronous so that the server responds to the client about the connection first before it emits the data. For example, you can carve out the for-loop into a separate function and use flask’s background task:
#socketio.on(‘connect’)
def dataSent():
print(‘...’)
socketio.start_background_task(target=emitloop)
def emitloop():
for i in range(2,6):
socketio.emit(‘data update’, i)
time.sleep(1)
(If flask is configured to use gevent for the background task, then you’ll need to install gevent or flask’s gevent plug-in.)
You might also try to use function based setState (i.e. passing a function to setState rather than the state object), because setState is asynchronous and react can combine them together otherwise.
Related
I'm trying to make a simple script to push events from a python app to a client. I made a Console React component, which uses SocketIO to receive the events, and I'm pushing the messages with Flask SocketIO.
This is my app.py:
from flask import Flask, request
from flask_socketio import SocketIO, send
app = Flask(__name__)
socketio = SocketIO(app, cors_allowed_origins='*')
#app.route('/send/<int:n>')
def send_n(n):
for i in range(n):
socketio.emit('console', str(i+1))
socketio.sleep(0.1)
return 'OK'
if __name__ == '__main__':
socketio.run(app)
The function send_n(n) simply pushes n events to the client spaced 0.25s.
This is the simplified Console component (it reproduces the undesired behavior):
import { useState, useEffect } from 'react'
import '../styles/console.css'
import { Socket } from 'socket.io-client'
interface ConsoleProps {
socket: Socket
}
export default function Console(props: ConsoleProps) {
const [messages, setMessages] = useState<string[]>([])
useEffect(() => {
props.socket.on('console', msg => {
setMessages([...messages, `${new Date().toLocaleTimeString()} ${msg}`])
})
})
return (
<div className='console' style={{ height: '200px' }}>
<ul>
{messages.map((msg, i) => <li key={i}>{msg}</li>)}
</ul>
</div>
)
}
It receives the already connected socket via props.
The component renders fine until it has ~10 elements, but then it slows down and by the 13th line it freezes, and I can't even close the tab without having to kill the process first.
Flask's debugger registers the message for every line rendered, but the lines not rendered (I tried with 25 as value for n) appear to have never been sent.
So I have 2 questions:
Why is flask not sending every message? Some kind of bad queuing?
Why does react freezes when the backend socket freezes?
Also, if I make msg a timestamp, the first messages render quickly, but the difference between the backend timestamp and the React timestamp grows very quickly with the number of messages.
I solved it following #Miguel's comment.
Long story short, the useEffect() function is executing on every render, and every time it executes it adds a new handler without deleting the previous one, so after a few renders things get out of control exponentially. So I just added a line to delete the handler when it finishes.
useEffect(():any => {
props.socket.on('console', msg => {
setMessages([...messages, `${new Date().toLocaleTimeString()} ${msg}`])
})
return () => props.socket.off('console')
})
And now it is rendering without problems.
I have almost read every piece of article available on the internet but nothing seems to work for my case. I have installed flask-socketio and everything works fine until I emit the messages from a module other than app.py.
I have tried several ways to accomplish this and I have also read in the doc about it by using Redis but it also did not work for me. Here are the code snippets that I have.
app.py
from flask import Flask
from flask import request
from flask_socketio import send, SocketIO, emit, join_room
app = Flask(__name__)
# This is to stop force sorting in response, by default jsonify sorts the response keys alphabetically
app.config["JSON_SORT_KEYS"] = False
socketio = SocketIO(app, cors_allowed_origins="*")
#socketio.on('join')
def client_join_room(data):
print(type(data))
room = data['room']
join_room(room)
send('you have entered the room.', room=room)
#app.route('/msg')
def send_message():
socketio.emit("message", "Server message", room='my_room')
return "I got you."
if __name__ == '__main__':
socketio.run(host="0.0.0.0", port=5001, debug=True, app=app)
my_module.py
def some_method():
import app
app.socketio.emit("message", "Some information about process", room='my_room', broadcast=True)
Note that I have imported app inside the method because app.py also imports my_module.py
I am able to join room.
When I call localhost:5001/msg it does emit to 'my_room'.
The emit does not work inside my_module.py and I have no idea why.
I am consoling the messages that I get from the server at the front-end so I know for sure which messages are received and which are not.
Also, the some_method() here is called by an API request from app.py. Just in case if that is relevant.
I have made logger=True and then I get this message printed on the terminal for each emit call. Even with the one inside some_method()
emitting event "message" to my_room [/]
Does that mean message is actually sent? If yes, then why am I not getting it in the jquery at front-end.
This is what I am doing in html page
$(document).ready(function () {
// start up the SocketIO connection to the server
var socket = io.connect('http://localhost:5001/');
// this is a callback that triggers when the "message" event is emitted by the server.
socket.on('message', function(msg){
console.log(msg)
});
socket.emit('join', {room: 'my_room'});
});
Please try and install Redis and eventlet for asynchronous calls and to send messages from other modules. As described in the documentation then you can change your line in app.py to
socketio = SocketIO(app, cors_allowed_origins="*", message_queue='redis://', async_mode='eventlet')
I've got a simple nodejs server that runs socketio and I can do 2 way communication with the client from the HTML. Now I'm also trying to connect to the same nodejs websocket from a Python script, but not getting connected.
Simplified Node JS webserver:
var http = require('http').createServer(handler);
var fs = require('fs');
var io = require('socket.io')(http)
http.listen(8080);
function handler (req, res) {
fs.readFile(__dirname + '/public/index.html', function(err, data) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(data);
return res.end();
});
}
io.sockets.on('connection', function (socket) {
setInterval(() => {
socket.emit('temp', 'tempfromserver')
}, 1000)
});
The Python script that wants to connect to the nodejs with websocket:
import socketio
sio = socketio.Client();
sio.connect('http://localhost:8080')
#sio.event
def message(data):
print('received message')
def connect():
print('socketio connected')
Is it possible to connect with a websocket via socketio to the nodejs server? If so, what am I doing wrong?
End goal is to collect sensor data from python script on raspberry pi and send to nodejs server which in its turn saves it to DB and sends through to HTMl client. Perhaps there is a better setup to do this.
I believe I asked a similar question and got an answer. My setup was a little different, and used a web-socket, but it will at least allow you to connect between Node JS and Python, and then maybe you can build from there, Using Socket IO and aiohttp for data transfer between node JS and Python. The setup was also originally based off a html script on the node JS side, so that may help as well in comparing it to your setup.
in my flask based http server designed to remotely manage some services on RPI I've approached a problem I cannot solve alone, thus a kind request to you to give me a hint.
Concept:
Via flask and gevent I can stop and run some (two) services running on RPI. I use gevent and server side event with respect javascript in order to listen to the html updates.
The html page shows the status (on/off/processing) of the services and provides buttons to switch them on/off. Additionally display some system parameters (CPU, RAM, HDD, NET).
As long as there is only one user/page opened everything works as desired. As soon as there are more users accessing the flask server there is a race between greenlets serving each user/page and not all pages are getting reloaded.
Problem:
How can I send a message to all running greenlets sse_worker() and process it on top of their regular job?
Below a high level code. The complete source can be found here: https://github.com/petervflocke/flasksse_rpi check the sse.py file
def sse_worker(): #neverending task
while True:
if there_is_a_change_in_process_status:
reload_page=True
else:
reload_page=False
Do some other tasks:
update some_single_parameters_to_be_passed_to_html_page
yield 'data: ' + json.dumps(all_parameters)
gevent.sleep(1)
#app.route('/stream/', methods=['GET', 'POST'])
def stream():
return Response(sse_worker(), mimetype="text/event-stream")
if __name__ == "__main__":
gevent.signal(signal.SIGTERM, stop)
http_server = WSGIServer(('', 5000), app)
http_server.serve_forever()
...on the html page the streamed json data are processed accordingly. If a status of a service has been changed based on the reload_page variable javascript reload the complete page - code extract below:
<script>
function listen() {
var source = new EventSource("/stream/");
var target1 = document.getElementById("time");
....
source.onmessage = function(msg) {
obj = JSON.parse(msg.data);
target1.innerHTML = obj.time;
....
if (obj.reload == "1") {
location.reload();
}
}
}
listen();
</script>
My desired solution would be to extend the sse_worker() like this:
def sse_worker():
while True:
if there_is_a_change_in_process_status:
reload_page=True
# NEW: set up a semaphore/flag that there is a change on the page
message_set(reload)
elif message_get(block=false)==reload: # NEW: check the semaphore
# issue: the message_get must retun "reload" for _all_ active sse_workers, that all of them can push the reload to "their" pages
reload_page=True
else:
reload_page=False
Do some other tasks:
update some_single_parameters_to_be_passed_to_html_page
yield 'data: ' + json.dumps(all_parameters)
gevent.sleep(1)
I hope I could pass on my message. Any idea from your side how I can solve the synchronization? Please notice that we have here the producer and consumer in the same sse_worker function.
Any idea is very welcome!
best regards
Peter
I'm basically building a visual trace route application. The trace route is basically done using a python code and the results are send to the HTML page in real time using web socket. I basically need to do long polling( the server receives one request, process it and sent a maximum of 30 replies to each client at regular or irregular intervals), as well as handle multiple clients. I basically manipulated the below code to work for my application. I found the code from Asynchronous Bottle Framework
from bottle import request, Bottle, abort
app = Bottle()
#app.route('/websocket')
def handle_websocket():
wsock = request.environ.get('wsgi.websocket')
if not wsock:
abort(400, 'Expected WebSocket request.')
while True:
try:
message = wsock.receive()
wsock.send("Your message was: %r" % message)
except WebSocketError:
break
from gevent.pywsgi import WSGIServer
from geventwebsocket import WebSocketHandler, WebSocketError
server = WSGIServer(("0.0.0.0", 8080), app,
handler_class=WebSocketHandler)
server.serve_forever()
It does work on a single request. When I issue the second one.. 'wsock.send()' fails... it shows socket dead error. Could someone guide me on, how to handle multiple clients as well. Like, should I spawn a different process for each client ? What if a client requests trace for one domain, and again(before the full result is provided to him) requests for another. Thanks in advice
Client side code :
<script type="text/javascript">
var ws = new WebSocket("ws://example.com:8080/websocket");
ws.onopen = function() {
ws.send("Hello, world");
};
ws.onmessage = function (evt) {
alert(evt.data);
};