How to use flask-socketio for heartbeat detection.
The current requirement is to create an interface. When the client connects to the corresponding interface, the server periodically sends a message to the client, the client response a message to server, it proves that the client is alive, heartbeat detection.
I have tried to write the code using flask-socketio and it runs successfully, but I don't know how to test the multiplayer connection.If the connection is broken, I don't know how the thread stops.
class MyCustomNamespace(Namespace):
clients = []
def on_connect(self):
sid = request.sid
print('client connected websocket: {}'.format(sid))
self.clients.append(sid)
global thread
with thread_lock:
if thread is None:
thread = socketio.start_background_task(target=self.background_thread, args=(current_app._get_current_object()))
def on_disconnect(self):
sid = request.sid
print('close websocket: {}'.format(sid))
self.clients.remove(sid)
def on_message(self, data):
print('received message: ' + data["param"])
if data["param"] is not None:
print('websocket {} is alive'.format(request.sid))
# else:
# print('websocket {} is die'.format(*rooms()))
# self.disconnect(*rooms())
def background_thread(self, app):
with app.app_context():
while True:
time.sleep(6)
data = {"status": "ok"}
socketio.emit('server_detect', data, namespace='/testnamespace', broadcast=True)
print("服务器探测客户端是否还活着")
socketio.on_namespace(MyCustomNamespace("/testnamespace"))
html
$(document).ready(function () {
namespace = '/testnamespace';
webSocketUrl = location.protocol + '//' + document.domain + ':' + location.port + namespace;
console.log(webSocketUrl);
var socket = io.connect(webSocketUrl);
socket.on('server_detect', function (res) {
console.log(res);
socket.emit('message',{'param':'value'});
});
});
Related
I want to send data (like endless stream) from one vds machine to another. I've read that it's possible to do with python-socket.io. First of all, I try to do it on my laptop (server script runs in one terminal, client - in another). And send numbers from "server" to "client" in infinite loop. I want to get data on client side in real-time. (Server sent "1", client got "1", etc) But, when I run both scripts I see that server is sending data and client gets nothing. Only when I stop (ctrl+c) server, all sent data is printing on client's terminal.
How to fix code to get real-time connection?
server.py
import eventlet
import socketio
import time
sio = socketio.Server()
app = socketio.WSGIApp(sio)
#sio.event
def connect(sid, environ):
print('connect ', sid)
my_message(sid, "Client connected")
# f(sid)
#sio.event
def my_message(sid, data):
sio.send(data)
print('Send message ', data)
#sio.event
def disconnect(sid):
print('disconnect ', sid)
#sio.on('subscribe_to_data')
def subscribe(sid, data):
counter = 0
while True:
sio.send(counter)
print('Send message from server ', counter)
counter += 1
# my_message(sid, i)
time.sleep(1)
eventlet.wsgi.server(eventlet.listen(('', 5000)), app)
client.py
import socketio
sio = socketio.Client()
#sio.event
def connect():
print('connection established')
sio.emit('subscribe_to_data', "I want to subscribe")
#sio.event
def message(data):
print('message received with ', data)
#sio.event
def disconnect():
print('disconnected from server')
sio.connect('http://localhost:5000')
sio.wait()
A better implementation will be to maintain a list of connected clients and do something like
while sid in CONNECTED_CLIENTS:
pass
Also instead of time.sleep() use sio.sleep()
Hi I created async websocket client which I can receive and send messages asynchronously.
Here is the clint class that I use.
import websockets
import asyncio
from models import Heartbeat
from model_helper import ModelHelper
from json_helper import JSONHelper
from os.path import dirname, abspath
import logging
import time
waiting_time = 0.5
class WebSocketClient():
def __init__(self, websocket_queue, configs):
self.Heartbeat = Heartbeat().message
self.websocket_queue = websocket_queue
self.configs = configs
def get_connection_string(self):
server_url = self.configs.URL + ":" + str(self.configs.Port) + self.configs.Root
return server_url
async def connect(self):
try:
server_url = self.get_connection_string()
self.connection = await websockets.client.connect(server_url)
if self.connection.open:
print("Connection stablished. Client correcly connected")
# Send greeting
await self.connection.send(self.Heartbeat)
return self.connection
else:
print("Can not connect")
except ConnectionRefusedError as err:
print("Connection Error: {}".format(err))
async def send_message_to_socket(self, connection):
while True:
message = self.websocket_queue.get_send_queue()
try:
if message is not None:
message_ = ModelHelper.to_outgoing_request_model(message)
await connection.send(message_)
else:
await asyncio.sleep(waiting_time)
except websockets.exceptions.ConnectionClosed:
print('Connection with server closed')
self.connect()
async def receive_message_from_socket(self, connection):
while True:
try:
message = await connection.recv()
obj = JSONHelper.toObject(message)
print("Received object from websocket: {}".format(obj))
#If a websocket entry has SendMessage in its Action property
#Consider it as an sms content to be sent.
if(obj.Action == "SendMessage"):
self.websocket_queue.add_received_queue(obj)
except websockets.exceptions.ConnectionClosed:
print('Connection with server closed1')
async def send_heartbeat_to_socket(self, connection):
while True:
#print("Heartbeat loop\n")
try:
await connection.send(self.Heartbeat)
await asyncio.sleep(3)
except websockets.exceptions.ConnectionClosed:
print('Connection with server closed2')
And this is the code where I listen for websocket messages:
def listen_websocket_routine(sms_queue, websocket_queue):
while True:
time.sleep(waiting_time)
#Check Websocket queue for new messages
message = websocket_queue.get_received_queue()
# If Incoming websocket JSON has SendMessage string in its Action attribute transform it to request_model
if message is not None and send_sms.Action == "SendMessage":
# Transform it to outgoing_sms_model
reset = ModelHelper.to_outgoing_sms_model(message.Data)
# Add To send_queue of sms
sms_queue.add_send_queue(reset)
finally How I initiate them with asyncio and threading.
client = WebSocketClient(websocket_queue, configs)
loop = asyncio.get_event_loop()
connection = loop.run_until_complete(client.connect())
task1 = asyncio.ensure_future(client.receive_message_from_socket(connection))
task2 = asyncio.ensure_future(client.send_heartbeat_to_socket(connection))
task3 = asyncio.ensure_future(client.send_message_to_socket(connection))
listen_websocket_thread = threading.Thread(target=listen_websocket_routine, args=(sms_queue, websocket_queue))
listen_websocket_thread.start()
loop.run_forever()
So my question is, whenever connetion breaks, I need to re establish the connection. But I am not sure where should I do that. Should it be before everytime I try to send message or receive or should I do that in more general approach?
Since update 10.0 you could do it with an async iterator, like this(example from the official doc):
async for websocket in websockets.connect(...):
try:
...
except websockets.ConnectionClosed:
continue
I want to send data through websockets as soon as a client is connected.
The Data is at an other place then the Websocket Handler. How can i get the data to the client ?
The server should hold the loop and the Handler. In the connector i connect to a tcp socket to get the data out of some hardware. I expect to have not more then 6 Websockets open once a time. The Data comes as a stream out of the TCP socket.
server.py
import os
from tornado import web, websocket
import asyncio
import connector
class StaticFileHandler(web.RequestHandler):
def set_default_headers(self):
self.set_header("Access-Control-Allow-Origin", "*")
def get(self):
self.render('index.html')
class WSHandler(websocket.WebSocketHandler):
def open(self):
print('new connection')
self.write_message("connected")
def on_message(self, message):
print('message received %s' % message)
self.write_message("pong")
def on_close(self):
print('connection closed')
public_root = 'web_src'
handlers = [
(r'/', StaticFileHandler),
(r'/ws', WSHandler),
]
settings = dict(
template_path = os.path.join(os.path.dirname(__file__), public_root),
static_path = os.path.join(os.path.dirname(__file__), public_root),
debug = True
)
app = web.Application(handlers, **settings)
sensorIP = "xxx.xxx.xxx.xxx"
if __name__ == "__main__":
app.listen(8888)
asyncio.ensure_future(connector.main_task(sensorIP))
asyncio.get_event_loop().run_forever()
connector.py
import yaml
import asyncio
class RAMReceiver:
def __init__(self, reader):
self.reader = reader
self.remote_data = None
self.initParams = None
async def work(self):
i = 0
while True:
data = await self.reader.readuntil(b"\0")
self.remote_data = yaml.load(data[:-1].decode("utf-8",
"backslashreplace"))
# here i want to emit some data
# send self.remote_data to websockets
if i == 0:
i += 1
self.initParams = self.remote_data
# here i want to emit some data after open event is
# triggered
# send self.initParams as soon as a client has connected
async def main_task(host):
tasks = []
(ram_reader,) = await asyncio.gather(asyncio.open_connection(host,
51000))
receiver = RAMReceiver(ram_reader[0])
tasks.append(receiver.work())
while True:
await asyncio.gather(*tasks)
You can use Tornado's add_callback function to call a method on your websocket handler to send the messages.
Here's an example:
1. Create an additional method on your websocket handler which will receive a message from connector.py and will send to connected clients:
# server.py
class WSHandler(websocket.WebSocketHandler):
# make it a classmethod so that
# it can be accessed directly
# from class without `self`
#classmethod
async def send_data(cls, data):
# write your code for sending data to client
2. Pass the currently running IOLoop and WSHandler.send_data to your connector.py:
# server.py
from tornado import ioloop
...
if __name__ == "__main__":
...
io_loop = ioloop.IOLoop.current() # current IOLoop
callback = WSHandler.send_data
# pass io_loop and callback to main_task
asyncio.ensure_future(connector.main_task(sensorIP, io_loop, callback))
...
3. Then modify main_task function in connector.py to receive io_loop and callback. Then pass io_loop and callback to RAMReceiver.
4. Finally, use io_loop.add_callback to call WSHandler.send_data:
class RAMReceiver:
def __init__(self, reader, io_loop, callback):
...
self.io_loop = io_loop
self.callback = callback
async def work(self):
...
data = "Some data"
self.io_loop.add_callback(self.callback, data)
...
The HandlerWebsockets does work fine and is just replying with what has been send at the moment throught messageToSockets(msg). However both tries to send messages to the websocket from the coroutine of the web application are not working. Looks like everything is blocked by these attempts...
class webApplication(tornado.web.Application):
def __init__(self):
handlers = [
(r'/', HandlerIndexPage),
(r'/websocket', HandlerWebSocket, dict(msg='start')),
]
settings = {
'template_path': 'templates'
}
tornado.web.Application.__init__(self, handlers, **settings)
#gen.coroutine
def generateMessageToSockets(self):
while True:
msg = str(randint(0, 100))
print ('new messageToCon: ', msg)
yield [con.write_message(msg) for con in HandlerWebSocket.connections]
yield gen.sleep(1.0)
if __name__ == '__main__':
ws_app = webApplication()
server = tornado.httpserver.HTTPServer(ws_app)
port = 9090
print('Listening on port:' + str(port))
server.listen(port)
IOLoop.current().spawn_callback(webApplication.generateMessageToSockets)
IOLoop.current().set_blocking_log_threshold(0.5)
IOLoop.instance().start()
Here the WebSockets Handler
class HandlerWebSocket(tornado.websocket.WebSocketHandler):
connections = set()
def initialize(self, msg):
print('HWS:' + msg)
def messageToSockets(self, msg):
print ('return message: ', msg)
[con.write_message(msg) for con in self.connections]
def open(self):
self.connections.add(self)
print ('new connection was opened')
pass
def on_message(self, message):
print ('from WebSocket: ', message)
self.messageToSockets(message)
def on_close(self):
self.connections.remove(self)
print ('connection closed')
pass
I am a bit lost in the examples, questions here, documentation etc. So any hint how to properly start a continous calling websocket routine is greatly appreciated
generateMessageToSockets will loop endlessly, generating messages as fast as it can without waiting for those messages to be sent. Since it starts first and never yields, the HTTPServer will never actually be able to accept a connection.
If you really want to send messages as fast as you can, the minimal solution without blocking would be
yield [con.write_message(msg) for con in HandlerWebSocket.connections]
yield gen.moment
But it would probably be better to use gen.sleep to send messages at regular intervals, instead of "as fast as possible".
unfortunately all the gen.routines tries didn't work for me. Moved back to threads
def generateMessageToSockets():
while True:
msg = str(randint(0, 100))
print ('new messageToCon: ', msg)
[con.write_message(msg) for con in HandlerWebSocket.connections]
sleep(1.0)
class WebApplication(tornado.web.Application):
def __init__(self):
handlers = [
(r'/', HandlerIndexPage),
(r'/websocket', HandlerWebSocket, dict(msg='start')),
]
settings = {
'template_path': 'templates'
}
tornado.web.Application.__init__(self, handlers, **settings)
if __name__ == '__main__':
tGenarate = threading.Thread(target=generateMessageToSockets)
tGenarate.start()
ws_app = WebApplication()
server = tornado.httpserver.HTTPServer(ws_app)
port = 9090
print('Listening on port:' + str(port))
server.listen(port)
ioloop.IOLoop.instance().start()
which works
I need some help. I have this code below and the logs tell me that I'm not passing the durable parameter, but as you can see I'm passing. I have tried everything and there is always the same mistake.
To mount this code I used these links:
https://github.com/sk2/ANK-NG/blob/master/rabbitmq/rabbit_websocket.py
https://pika.readthedocs.io/en/0.10.0/examples/tornado_consumer.html
websocket.py:
import pika
import tornado
import tornado.websocket as websocket
from tornado.options import options, define, parse_command_line
import tornado.httpserver
import tornado.ioloop
import tornado.wsgi
from pika.adapters.tornado_connection import TornadoConnection
from witbot import recebeInput
import logging
LOG_FORMAT = ('%(levelname) -10s %(asctime)s %(name) -30s %(funcName) '
'-35s %(lineno) -5d: %(message)s')
LOGGER = logging.getLogger(__name__)
class MyWebSocketHandler(websocket.WebSocketHandler):
def allow_draft76(self):
# for iOS 5.0 Safari
return True
def check_origin(self, origin):
return True
def open(self, *args, **kwargs):
self.application.pc.add_event_listener(self)
print ("WebSocket opened")
def on_close(self):
print ("WebSocket closed")
self.application.pc.remove_event_listener(self)
def on_message(self, message):
print (message)
self.application.pc.send_message(message)
class PikaClient(object):
def __init__(self, io_loop):
print ('PikaClient: __init__')
self.io_loop = io_loop
self.connected = False
self.connecting = False
self.connection = None
self.channel = None
self.event_listeners = set([])
def connect(self):
logging.basicConfig(level=logging.DEBUG)
if self.connecting:
print ('PikaClient: Already connecting to RabbitMQ')
return
print ('PikaClient: Connecting to RabbitMQ')
self.connecting = True
port = 5672
cred = pika.PlainCredentials(username='guest', password='guest')
param = pika.ConnectionParameters(host='192.168.99.100',port=port,credentials=cred,heartbeat_interval=5)
print(param)
self.connection = TornadoConnection(param,
on_open_callback=self.on_connected)
print(self.connection)
self.connection.add_on_close_callback(self.on_closed)
def on_connected(self, connection):
print ('PikaClient: connected to RabbitMQ')
self.connected = True
self.connection = connection
self.connection.channel(self.on_channel_open)
def on_channel_open(self, channel):
print ('PikaClient: Channel open, Declaring exchange')
self.channel = channel
self.setup_exchange('amq.topic')
def setup_exchange(self, exchange_name):
print('Declaring exchange %s', exchange_name)
self.channel.exchange_declare(self.on_exchange_declareok, exchange=exchange_name,exchange_type='topic',durable=True)
def on_exchange_declareok(self, unused_frame):
print('Exchange declared')
self.setup_queue('bot.commands')
def setup_queue(self, queue_name):
print('Declaring queue %s', queue_name)
self.channel.queue_declare(self.on_queue_declareok, queue_name)
def on_queue_declareok(self, method_frame):
print ('Binding %s to %s with %s', 'amq.topic', 'bot.commands', 'bot.commands')
self.channel.queue_bind(self.on_bindok, 'bot.commands',
'amq.topic', 'bot.commands')
def on_bindok(self, unused_frame):
print('Queue bound')
self.start_consuming()
def start_consuming(self):
print('Issuing consumer related RPC commands')
self._consumer_tag = self.channel.basic_consume(self.on_message,'bot.commands')
def on_closed(self, connection):
print ('PikaClient: rabbit connection closed')
self.io_loop.stop()
def on_message(self, channel, method, header, body):
print ('PikaClient: message received: %s' % body)
self.notify_listeners(body)
def send_message(self, body):
self.channel.basic_publish(exchange='topic',
routing_key='bot.commands',
body=body)
def notify_listeners(self, body):
bot = recebeInput.decisoes(recebeInput.devolveEntidade(recebeInput.trataJson(body)))
for listener in self.event_listeners:
listener.write_message(bot)
print ('PikaClient: notified %s' % repr(listener))
def add_event_listener(self, listener):
print ("added listener")
self.event_listeners.add(listener)
print ('PikaClient: listener %s added' % repr(listener))
def remove_event_listener(self, listener):
try:
self.event_listeners.remove(listener)
print ('PikaClient: listener %s removed' % repr(listener))
except KeyError:
pass
def main():
parse_command_line()
aplication = tornado.web.Application([
(r'/ws', MyWebSocketHandler)
])
# server = tornado.httpserver.HTTPServer(aplication)
io_loop = tornado.ioloop.IOLoop.instance()
# PikaClient is our rabbitmq consumer
pc = PikaClient(io_loop)
aplication.pc = pc
aplication.pc.connect()
aplication.listen(8081)
io_loop.start()
if __name__ == '__main__':
main()
Error log:
websocket_1 | PikaClient: connected to RabbitMQ
websocket_1 | PikaClient: Channel open, Declaring exchange
websocket_1 | Declaring exchange %s amq.topic
websocket_1 | Exchange declared
websocket_1 | Declaring queue %s bot.commands
rabbit1_1 | 2018-07-17 15:48:18.792 [info] <0.6936.0> connection
<0.6936.0> (172.18.0.1:37556 -> 172.18.0.6:5672): user 'guest' authenticated and granted access to vhost '/'
websocket_1 | [W 180717 15:48:18 channel:1034] Received remote Channel.Close (406): "PRECONDITION_FAILED - inequivalent arg 'durable' for queue 'bot.commands' in vhost '/': received 'false' but current is 'true'" on <Channel number=1 OPEN conn=<TornadoConnection OPEN socket=('172.18.0.2', 47986)->('192.168.99.100', 5672) params=<ConnectionParameters host=192.168.99.100 port=5672 virtual_host=/ ssl=False>>>
websocket_1 | [I 180717 15:48:24 web:2162] 101 GET /ws (192.168.99.1) 1.29ms
websocket_1 | added listener
websocket_1 | PikaClient: listener <__main__.MyWebSocketHandler object at 0x7f483b277cf8> added
websocket_1 | WebSocket opened
Perhaps the queue already exists and is durable so when your library attempts to subscribe to it and sees a durable it throws an error because it is expecting a non-durable queue
For me, the queue exists. I deleted it by rabbitmqadmin delete queue name=name_of_queue and run program again
As you are using nodeJs amqp lib and you faced with the same issue, try to specify options when specifying queue :
connection.queue('your_queue_name',{'durable':true}, function (q) {
// Catch all messages
q.bind('#');
// Receive messages
q.subscribe(function (message) {
// Print messages to stdout
console.log(message);
});
});