I have a question very similar to this one: Send data every 10 seconds via socket.io
But: My server is written in Python, the client is in JavaScript
The goal:
Clients connect to server via socket.io
Clients receive push messages ping from server every n seconds
When a client sends a ping message, the server broadcasts a pong message
What works:
Socket.io connection works fine
Client ping is received by server, answered with pong, which is again received by client
Server executes ping_in_intervals every 5 seconds
What doesn't work:
When server executes ping_in_intervals (which triggers sending a ping), that ping is not received by any client
When ping_in_intervals loop is active, socket connections crash every minute or so. If the method is commented out, then socket connection stays stable.
Observations:
The thread, that ping_in_intervals is running in doesn't seem to properly work together with the wsgi server thread.
The ping_in_intervals thread destabilizes the server thred, causes it to loose connections (which are reestablished right away, but they do drop every minute or so)
I think, that I'm doing something terribly wrong with threading. I have very little experience with threading in Python and don't know, where to look for the problem
Server:
import eventlet
import socketio
import threading
sio = socketio.Server(cors_allowed_origins="*", async_mode='eventlet')
app = socketio.WSGIApp(sio)
def ping_in_intervals():
threading.Timer(5.0, ping_in_intervals).start()
print("send ping")
sio.emit('ping')
#sio.on('ping')
def ping(*args):
print("received ping - send pong")
sio.emit('pong')
ping_in_intervals()
eventlet.wsgi.server(eventlet.listen(('', 8080)), app)
Client:
const socket = io.connect('localhost:8080', {secure: true, transports: ['websocket']});
socket.on('pong', () => {
console.log('received pong');
});
socket.on('ping', () => {
console.log('received ping');
});
socket.on('connect', () => {
socket.emit('ping')
});
Found the solution at https://github.com/miguelgrinberg/python-socketio/blob/main/examples/server/wsgi/app.py#L16-L22
The thread, which pushes server messages every n seconds, shouldn't be started using threading, but instead using the start_background_task function of socketio.
Here's the working code:
import eventlet
import socketio
sio = socketio.Server(cors_allowed_origins="*", async_mode='eventlet')
app = socketio.WSGIApp(sio)
def ping_in_intervals():
while True:
sio.sleep(10)
sio.emit('ping')
#sio.on('ping')
def ping(*args):
sio.emit('pong')
thread = sio.start_background_task(ping_in_intervals)
eventlet.wsgi.server(eventlet.listen(('', 8080)), app)
Related
I am developing an app and using the websockets for the server-client communication. The concept is to have the client requesting from the server for messages and a few times the server needs to push some messages to the client (without the latter has requested for them). (I'll use front-end and back-end to describe my app)
The app works when the front-end requests from the back-end but it doesn't for the case where the back-end needs to be the originator of the message (i.e. emit data without the front-end has requested for that). In that case the websocket seems to stall and blocked for a few seconds, until the client disconnects (reason timeout) and connects again. Of course, the topic sent by the back-end/server is never received by the client, i.e. the /non_requested_topic as seen below.
For the BACK-END I am using flask-socketio in PY2.7 and
events_handling.py
#!/usr/bin/env python
from flask import request
from emit_topics import emit_topic
def on_connect():
print("Client {} connected".format(request.sid))
# set the client ID to unrequested.py blah blah
def on_disconnect():
print("Client {} disconnected".format(request.sid))
def on_topic_request(data):
data_rx = "blah blah"
to = request.sid # client
namespace = "my_namespace"
emit_topic(topic_name="/topic_name", data_rx, to, namespace)
emit_topics.py
def emit_topic(topic_name, data, to, namespace):
socket_io.emit(topic_name, data=data, to=to, namespace=namespace)
And then there is a function that calls emit_topic and pushes some data to the client (without the client has requested for them):
unrequested.py
#!/usr/bin/env python
from emit_topics import emit_topic
def function_a(self):
to = self.client_id # client id that is set every time the client connects
namespace = "my_namespace"
data_rx="Msg from the server"
print("Check the client id {}".format(to)) # this matches with the one observed for the ws
emit_topic (topic_name="/non_requested_topic", data_rx, to, namespace)
For the FRONT-END:
There is an HTML file where:
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.5/socket.io.min.js"></script>
var socket = io("my_namespace", {'forceNew': true});
var interval_timer;
socket.on('connect', function() {
console.log('Connected to the server');
interval_timer = setInterval(topics_request, 5000);
});
socket.on('disconnect', function() {
console.log('Disconnected from the server');
clearInterval(interval_timer);
});
socket.on("/topic_name", (msg) => {
console.log("/topic_name rx ", msg);
});
socket.on("/non_requested_topic", (msg) => {
console.log("Non requested topic rx ", msg);
});
function topics_request(){
socket.emit("topic_request", {"topic": "/topic_name"}) // some topic name
}
Is a request from the client required first to trigger the /non_requested_topic or is there a way for the client to keep listening to that?
You have several issues with your code.
First of all, Python 2.7 has not been a supported version of Python for Flask-SocketIO for a while. I strong advise you to start using Python 3.
Your use of namespaces in the server is very inconsistent. Your connect and disconnect handlers do not use a namespace. Your on_topic_request function doesn't either. But then the emit_topic function accepts a namespace as an argument, which you set to my_namespace. Why do you use a namespace only when emitting but not when receiving events? Also, namespaces are supposed to start with a slash.
On a website I have a very simple Live chat setup that uses SSE/Redis and pub/sub structure.
The basic setup (without going into details) is:
Client-side using EventSource
Opens SSE connection and subscribes to live events sent by SSE daemon. Sends messages to an API endpoint
connect(hash, eventListener) {
const url = `${url}?client=$hash=${hash}`;
sseSource = new EventSource(url);
sseSource.onopen = function(e) {
reconnectFrequencySeconds = 1;
}
sseSource.onerror = err => {
this.closeSSEStream();
this.reconnectSSEStream(hash, eventListener);
};
sseSource.addEventListener('messages', event => {
const messages = JSON.parse(event.data);
eventListener(messages);
});
},
API endpoint
That stores message in the database and pushes it to a Redis channel.
Redis DB
That keeps and serves the messages.
Server-side SSE daemon
Subscribes client to a channel in a Redis DB and forwards messages to the subscribers using SSE stream.
const subscriber = redis.createClient();
subscriber.select(config.redisDatabase);
subscriber.on('message', function (channel, message) {
log(connectionId, 'Redis: new msg on channel: ' + channel, message);
let event = {
event: 'messages',
data: message
};
currentClient.connection.write(event);
});
The whole thing works pretty well, however, it is one tweak away from perfection.
During deploy we restart our workers (including SSE daemon) and while it goes offline users do not receive LIVE updates. It reconnects just fine but messages that have been sent during down time are lost (as daemon starts listening to messages on reconnect only).
My only idea for a workaround involves an overengineered solution where "lost" messages are collected with a separate API endpoint on reconnect and displayed to the user.
Is there an out-of-the-box way to receive messages that have been stored to Redis BEFORE subscribing to a channel? E.g. "pop" unprocessed messages or something like that?
when you have reconnected send request to check if you are new msg with time of last msg
and if you are newer msg send it in result msg to avoid new request
I have node.js service and angular client using socket.io to transport some message during long time http request.
Service:
export const socketArray: SocketIO.Socket[] = [];
export let socketMapping: {[socketId: string]: number} = {};
const socketRegister: hapi.Plugin<any> = {
register: (server) => {
const io: SocketIO.Server = socket(server.listener);
// Whenever a session connected to socket, create a socket object and add it to socket array
io.on("connection", (socket) => {
console.log(`socket ${socket.id} connected`);
logger.info(`socket ${socket.id} connected`);
// Only put socket object into array if init message received
socket.on("init", msg => {
logger.info(`socket ${socket.id} initialized`);
socketArray.push(socket);
socketMapping[socket.id] = msg;
});
// Remove socket object from socket array when disconnected
socket.on("disconnect", (reason) => {
console.log(`socket ${socket.id} disconnected because: ${reason}`)
logger.info(`socket ${socket.id} disconnected because: ${reason}`);
for(let i = 0; i < socketArray.length; i ++) {
if(socketArray[i] === socket) {
socketArray.splice(i, 1);
return;
}
}
});
});
},
name: "socketRegister",
version: "1.0"
}
export const socketSender = async (socketId: string, channel: string, content: SocketMessage) => {
try {
// Add message to db here
// await storeMessage(socketMapping[socketId], content);
// Find corresponding socket and send message
logger.info(`trying sending message to ${socketId}`);
for (let i = 0; i < socketArray.length; i ++) {
if (socketArray[i].id === socketId) {
socketArray[i].emit(channel, JSON.stringify(content));
logger.info(`socket ${socketId} send message to ${channel}`);
if (content.isFinal == true) {
// TODO: delete all messages of the process if isFinal is true
await deleteProcess(content.processId);
}
return;
}
}
} catch (err) {
logger.error("Socket sender error: ", err.message);
}
};
Client:
connectSocket() {
if (!this.socket) {
try {
this.socket = io(socketUrl);
this.socket.emit('init', 'some-data');
} catch (err) {
console.log(err);
}
} else if (this.socket.disconnected) {
this.socket.connect();
this.socket.emit('init', 'some-data');
}
this.socket.on('some-channel', (data) => {
// Do something
});
this.socket.on('disconnect', (data) => {
console.log(data);
});
}
They usually work fine but produce disconnection error randomly. From my log file, we can see this:
2018-07-21T00:20:28.209Z[x]INFO: socket 8jBh7YC4A1btDTo_AAAN connected
2018-07-21T00:20:28.324Z[x]INFO: socket 8jBh7YC4A1btDTo_AAAN initialized
2018-07-21T00:21:48.314Z[x]INFO: socket 8jBh7YC4A1btDTo_AAAN disconnected because: ping timeout
2018-07-21T00:21:50.849Z[x]INFO: socket C6O7Vq38ygNiwGHcAAAO connected
2018-07-21T00:23:09.345Z[x]INFO: trying sending message to C6O7Vq38ygNiwGHcAAAO
And at the same time of disconnect message, front-end also noticed a disconnect event which saying transport close.
From the log, we can get the work flow is this:
Front-end started a socket connection and sent an init message to back-end. It also save the socket.
Back-end detected the connection and received init message
Back-end put the socket to the array so that it can be used anytime anywhere
The first socket was disconnected unexpectedly and another connection is published without front-end's awareness so front-end never send a message to initialize it.
Since front-end's saved socket is not changed, it used the old socket id when made http request. As a result, back-end sent message with the old socket which was already removed from socket array.
The situation doesn't happen frequently. Does anyone know what could cause the disconnect and unknown connect issue?
It really depends what "long time http request" is doing. node.js runs your Javascript as a single thread. That means it can literally only do one thing at a time. But, since many things that servers do are I/O related (read from a database, get data from a file, get data from another server, etc...) and node.js uses event-driven asynchronous I/O, it can often have many balls in the air at the same time so it appears to be working on lots of requests at once.
But, if your complex http request is CPU-intensive, using lots of CPU, then it's hogging the single Javascript thread and nothing else can get done while it is hogging the CPU. That means that all incoming HTTP or socket.io requests have to wait in a queue until the one node.js Javascript thread is free so it can grab the next event from the event queue and start to process that incoming request.
We could only really help you more specifically if we could see the code for this "very complex http request".
The usual way around CPU-hogging things in node.js is to offload CPU-intensive stuff to other processes. If it's mostly just this one piece of code that causes the problem, you can spin up several child processes (perhaps as many as the number of CPUs you have in your server) and then feed them the CPU-intensive work and leave your main node.js process free to handle incoming (non-CPU-intensive) requests with very low latency.
If you have multiple operations that might hog the CPU, then you either have to farm them all out to child processes (probably via some sort of work queue) or you can deploy clustering. The challenge with clustering is that a given socket.io connection will be to one particular server in your cluster and if it's that process that just happens to be executing a CPU-hogging operation, then all the socket.io connections assigned to that server would have bad latency. So, regular clustering is probably not so good for this type of issue. The work-queue and multiple specialized child processes to handle CPU-intensive work are probably better because those processes won't have any outside socket.io connections that they are responsible for.
Also, you should know that if you're using synchronous file I/O, that blocks the entire node.js Javascript thread. node.js can not run any other Javascript during a synchronous file I/O operation. node.js gets its scalability and its ability to have many operations in flight at the same from its asynchronous I/O model. If you use synchronous I/O, you completely break that and ruin scalability and responsiveness.
Synchronous file I/O belongs only in server startup code or in a single purpose script (not a server). It should never be used while processing a request in a server.
Two ways to make asynchronous file I/O a little more tolerable are by using streams or by using async/await with promisified fs methods.
So I am trying to make some sort of connection between my Java app and my Web app, I looked up websockets and they look really simple and easy to use :). And I created myself a Java Server, which uses the ServerSocket class.
Now the problem is I am able to connect to the server from the web, with the websocket, but I am unable to send data to the server... but when I tried to send data from a Java Client it worked fine... what might be the problem?
My Java/Scala (I followed this tutorial: https://www.tutorialspoint.com/java/java_networking.htm) server:
class Server(val port: Int) extends Thread {
private val serverSocket = new ServerSocket(port)
override def run(): Unit = {
try {
while(true) {
println("Waiting for client on port: " + serverSocket.getLocalPort)
val server = serverSocket.accept()
println(server.getRemoteSocketAddress)
val in = new DataInputStream(server.getInputStream())
println(in.readUTF())
val out = new DataOutputStream(server.getOutputStream())
out.writeUTF("Hello world!")
server.close()
}
} catch {
case s: SocketTimeoutException => println("Connection timed out!");
case e: Exception => e.printStackTrace()
}
}
}
My web js (I followed https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications ):
/**
* Created by samuelkodytek on 20/12/2016.
*/
var conn = new WebSocket('ws://127.0.0.1:8080');
conn.onopen = function(e) {
console.log("Connection established!");
conn.send("Hello!");
};
conn.onmessage = function(e) {
console.log(e.data);
};
A web socket server is not the same thing as a simple socket server. A server that offers web sockets must first offer HTTP or HTTPS services because the web socket is established when a web client sends an HTTP request with an Upgrade option and special fields for establishing the web socket. Even after the web socket is established, the connection still does not behave exactly like a regular socket. The Web Socket protocol uses frames to send or receive data. This is all considerably different from what you seem to expect.
One other thing that you should be aware of is that the browser will enforce the rule that the web socket must come from the same host as the page that is attempting to establish the web socket (the same protocol, address, and TCP port).
Whenever I disconnect using socket.disconnect(); and then reconnect using socket.connect();, the server runs my handshake code twice. The strange thing is, even though the server connection code runs twice, there is only one connection in my array after reconnecting. This happens on an accidental disconnection, intentional, or even if the server restarts. Bit of code:
io.on('connection', OnConnect);
function OnConnect(socket) {
var connection = { socket: socket, realIp: ip, token: GenerateConnToken() };
connections.push(connection);
console.log('Connected');
// Client will respond to this by emitting "client-send-info".
// This is emitted once on initial connect, but twice on reconnect.
socket.emit('acknowledge', connection.token);
socket.on('client-send-info', function() {
console.log('Client Sent Info');
});
socket.on('disconnect', function() {
console.log('Disconnected');
});
}
The above code, when a client connects, disconnects, and then reconnects once, will produce the following log:
Connected
Client Sent Info
Disconnected
Connected
Client Sent Info
Client Sent Info
Why is it that, when reconnecting, the connection code will run twice, but only create one connection object?
EDIT: Upon further inspection, it seems that a different piece of my connection code is being performed twice when the client reconnects. The above code is updated to reflect the relevant information.
Strangely, the solution is completely client side. Instead of the following code:
var socket = io.connect();
socket.on('connect' function() {
socket.on('acknowledge', function() {
});
});
You have to use:
var socket = io.connect();
socket.on('connect' function() {
});
socket.on('acknowledge', function() {
});
Otherwise, the server will appear to be sending multiple emits when it is in reality only sending one, and it's the client that falsely receives multiples. With the second code format, the client successfully connects initially, disconnects, and reconnects without receiving multiple emits.
Simply, don't put any additional socket.on('x') calls inside the on('connection') call. Leave them all outside it.