Have an app that is using (Python 3.6) Tkinter & Tornado. Would like it send a websocket message when a button is pressed.
The sendSocket is in my class that handles the interface. I am able to open my sockets ok, and can send data into the socket handler ok. Additionally, it serves up my html file ok from my RequestHandler.
I can see that my code hits the sendSocketMessage line ok. However, I never get the print from within the SocketHandler.send_message def. There are no errors in the console.
def sendSocketMessage(self, data = "whatever"):
print("sending")
#WebSocketeer.send_message(data)
ioloop.IOLoop.current().add_callback(WebSocketeer.send_message, data)
class WebSocketeer(websocket.WebSocketHandler):
def open(self):
print("WebSocket opened")
def on_message(self, message):
print("got message: " + message)
def on_close(self):
print("WebSocket closed")
#classmethod
def send_message(self, message):
print("sending message: " + message)
for session_id, session in self.session.server._sessions._items.iteritems():
session.conn.emit(event, message)
Code based off of these SO responses
Send a websocket message:
How do I send a websocket message in Tornado at will?
Send to all clients:
Is it possible to send a message to all active WebSocket connections? Using either node.js or python tornado websockets
Found a way to make it work here: How to run functions outside websocket loop in python (tornado)
But am still wondering why the add_callback doesn't work - as, from what I've read, this is the recommended way.
This is what I've got working, taken from: https://github.com/tornadoweb/tornado/issues/2802
clients = [];
class WSHandler(tornado.websocket.WebSocketHandler):
def open(self):
print('connection opened...')
clients.append(self);
def on_message(self, message):
self.write_message("The server says: " + message + " back at you")
print('received:', message)
def on_close(self):
clients.remove(self);
print('connection closed...')
#classmethod
def send_message(self, message):
print("sending message: " + message)
for client in clients:
client.write_message(message);
#for session_id, session in self.session.server._sessions._items.iteritems():
# session.conn.emit(event, message);
return True;
def sendRandom():
global thread, data;
try:
print("sendRandom()");
time.sleep(0.125);
n = random.randint(0,1000);
data = str(n);
data = {"msg":"data","data":data};
if eventLoop is not None:
#If response needed
#sendData(eventLoop,WSHandler.send_message,json.dumps(data));
#else
eventLoop.add_callback(WSHandler.send_message,json.dumps(data));
except:
print("Err");
traceback.print_exc();
clients = [];
def sendData(loop,f,*a,**kw):
print("%s %s" % (type(loop),type(f)));
concurrent_future = concurrent.futures.Future();
async def wrapper():
try:
rslt = f(*a,**kw);
except Exception as e:
concurrent_future.set_exception(e);
else:
concurrent_future.set_result(rslt);
loop.add_callback(wrapper);
return concurrent_future.result();
eventLoop = None;
application = tornado.web.Application([
(r'/data', WSHandler),
])
def startServer():
global eventLoop;
try:
print("Starting server #%s:%d" %("localhost",9090));
asyncio.set_event_loop(asyncio.new_event_loop());
eventLoop = tornado.ioloop.IOLoop();
application.listen(9090)
eventLoop.start();
except KeyboardInterrupt:
print("^C");
except:
print("ERR");
traceback.print_exc();
if __name__ == "__main__":
thread = Thread(target=startServer,);
thread.setDaemon(True);
thread.start();
time.sleep(5);
while True:
sendRandom();
Related
all.
In my project,I built two websocket connections. one is used for sending pictures from server to client, and the other is used for sending some control data from client to server. now I use only one client.
var ws2 = new WebSocket('ws://xx.xx.xx.xx:9002/control')
var ws1 = new WebSocket('ws://xx.xx.xx.xx:9002/data')
ws.binaryType = 'blob'
ws2.onopen =()=> console.log('ws2 connected.')
On the server side, once ws1 is open, it constently send data to the client. q is a global queue from which I get the data. This part works fine.
The problem is that the program can not run into on_message(self, message) in function in ws2 after I send message from the client side.
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
class myWebSocket(tornado.websocket.WebSocketHandler):
def check_origin(self, origin):
return True;
def open(self):
print "websocket1 open."
while self.ws_connection is not None:
print "send..."
try:
self.write_message(q.get(),True)
except tornado.websocket.WebSocketClosedError:
self.close()
def on_message(self, message):
print("websocket1 received message.")
print message
def on_close(self):
print("websocket1 closed.")
class controlWebSocket(tornado.websocket.WebSocketHandler):
def check_origin(self, origin):
return True;
def open(self):
print "websocket2 open"
def on_message(self, message):
print("websocket2 received message.")
print message
def on_close(self):
print("websocket2 closed.")
and I am sure the client successfully send the control data by click a button.
<script>
function test(){
ws2.send("this is from ws2.")
alert("home clicked.")
}
</script>
and I also found that once the open function in ws1 stopped, the ws2 can receive the message. I don't understand why ws1 sending data caused ws2 unable to receive data.
I'm new in python programming and tornado websocket. anyone can help on this problem, thanks a lot~!
Tornado is built on non-blocking concurrency. This means you must generally avoid blocking methods (or run them in a thread pool). q.get() is a blocking method, so nothing else in Tornado can run while it is waiting for a message. You should probably replace this queue with a tornado.queues.Queue.
I'm setting up a tornado web socket and want to use it to send log data over my network. Seems to work fine, but:
(1) Can I send data from client to server arbitrarily once the connection is established, or do I have to establish a new connection and use the on_open method to print/work with the sent message every time?
(2) In my client specifically (link): why isn't ws.close() called? Doesn't seem to happen. How can I terminate a connection then?
(3) Is there a better way to identify clients other that connection.request.remote_ip?
Code: Server
import tornado.ioloop
import tornado.web
import tornado.websocket
host = "localhost:2000"
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("<h3>HELLO</h3>")
class EchoWebSocket(tornado.websocket.WebSocketHandler):
connections = set()
ips = set()
def open(self):
self.connections.add(self)
self.ips.add(self.request.remote_ip)
print("[MASTER]: WebSocket opened by {}".format(self.request.remote_ip))
def on_message(self, message):
print("[CLIENT {}]: {}".format(self.request.remote_ip, message))
[con.write_message("[MASTER]: {}".format(message)) for con in self.connections]
def on_close(self):
self.connections.remove(self)
self.ips.remove(self.request.remote_ip)
print("[MASTER]: WebSocket closed - Client {}".format(self.request.remote_ip))
def setup():
return tornado.web.Application([(r"/", MainHandler),
(r"/ws", EchoWebSocket)])
if __name__ == "__main__":
s = tornado.httpserver.HTTPServer(setup())
s.listen(2000)
print("------------- INFO -------------\nStarted Server at {}\nSocket available at /ws".format(host))
tornado.ioloop.IOLoop.current().start()
Client:
from tornado.websocket import websocket_connect
import tornado
import time
url = "ws://localhost:2000/ws"
class Client(object):
def __init__(self, url, log=None):
self.url = url
self.ioloop = tornado.ioloop.IOLoop.current()
self.conn = None
self.log = log
def start(self):
websocket_connect(
self.url,
self.ioloop,
callback=self.on_connected,
on_message_callback=self.on_message)
self.ioloop.start()
def on_connected(self, f):
try:
self.conn = f.result()
self.conn.write_message("Client #1 connected")
except Exception as e:
print("[ERROR]: {}".format(e))
self.conn.write_message("ERROR: {}".format(e))
self.ioloop.stop()
def on_message(self, message):
if message is None:
print("[ERROR]: No message received")
self.conn.write_message("[ERROR]: No message received")
self.ioloop.stop()
else:
print(message)
def close():
self.conn.close()
if __name__ == '__main__':
ws = Client(url)
ws.start()
ws.close()
Is it possible, if multiple socket clients are connected to a tornado websocket server, to send a message to a specific one?
I don't know if there is a possibility of getting a client's id and then send a message to that id.
My client code is:
from tornado.ioloop import IOLoop, PeriodicCallback
from tornado import gen
from tornado.websocket import websocket_connect
class Client(object):
def __init__(self, url, timeout):
self.url = url
self.timeout = timeout
self.ioloop = IOLoop.instance()
self.ws = None
self.connect()
PeriodicCallback(self.keep_alive, 20000, io_loop=self.ioloop).start()
self.ioloop.start()
#gen.coroutine
def connect(self):
print "trying to connect"
try:
self.ws = yield websocket_connect(self.url)
except Exception, e:
print "connection error"
else:
print "connected"
self.run()
#gen.coroutine
def run(self):
while True:
msg = yield self.ws.read_message()
print msg
if msg is None:
print "connection closed"
self.ws = None
break
def keep_alive(self):
if self.ws is None:
self.connect()
else:
self.ws.write_message("keep alive")
if __name__ == "__main__":
client = Client("ws://xxx", 5 )
When your client connects to the websocket server will be invoked method 'open' on WebSocketHandler, in this method you can keep socket in the Appliaction.
Like this:
from tornado import web
from tornado.web import url
from tornado.websocket import WebSocketHandler
class Application(web.Application):
def __init__(self):
handlers = [
url(r"/websocket/server/(?P<some_id>[0-9]+)/", WebSocketServer),
]
web.Application.__init__(self, handlers)
self.sockets = {}
class WebSocketServer(WebSocketHandler):
def open(self, some_id):
self.application.sockets[some_id] = self
def on_message(self, message):
self.write_message(u"You said: " + message)
def on_close(self):
print("WebSocket closed")
You also may use message for connection, in this message you have to tell socket id and save socket into the Application.
I'm using Tornado to serve a html file and, at the same time, I want my Python program to dynamically change a value in the same page through websockets. The purpose is that the value can be seen changing, without the need to refresh on the client side.
In this case, I want to send a message with a variable that changes from "1234" to "4321" every 4 seconds.
I have a thread doing the value toggling and I want that same thread to send that value to the websocket, so I can handle it with a script using the onmessage function in the client side.
The following code will give me the following error: 'Application' object has no attribute 'write_message'
import tornado.ioloop
import tornado.web
import tornado.websocket
import threading
import time
flag = True
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("mypage.html")
class wshandler(tornado.websocket.WebSocketHandler):
def open(self):
self.write_message('connected')
print "WebSocket opened"
def on_message(self, message):
self.write_message("You have sent: " + message)
def on_close(self):
print "WebSocket closed"
def write_message(self, message):
print ("writing message", message)
self.write_message(message)
application = tornado.web.Application(
handlers=[
(r"/", MainHandler),
(r"/ws", wshandler),
])
def toggle():
global flag
while True:
if flag==True:
print 'now on 1234'
flag = False
application.write_message('1234')
time.sleep(4)
elif flag==False:
flag = True
application.write_message('4321')
print 'Now on 4321'
time.sleep(4)
def main():
global response
valueThread=threading.Thread(target=toggle, name="Toggle Value Thread")
valueThread.setDaemon(True)
valueThread.start()
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
if __name__ == "__main__":
main()
My problem is that every single tutorial/guide I've seen about Tornado only uses the function write_message within the open and onmessage function within the websocket class.. so I don't really get why this won't work.
You should only use the function write_message within the open and onmessage function within the websocket class. You are using application, which is a tornado.web.Application. It certainly won't have the method of tornado.websocket.WebSocketHandler. They are not the same class. Hence the error.
I would do something like:
class wshandler(tornado.websocket.WebSocketHandler):
def toggle(self):
flag = True
while True:
if flag==True:
print 'now on 1234'
flag = False
self.write_message('1234')
time.sleep(4)
elif flag==False:
flag = True
self.write_message('4321')
print 'Now on 4321'
time.sleep(4)
def on_message(self, message):
if message == 'toggle':
self.toggle()
I'm starting to get into WebSockets as way to push data from a server to connected clients. Since I use python to program any kind of logic, I looked at Tornado so far. The snippet below shows the most basic example one can find everywhere on the Web:
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
class WSHandler(tornado.websocket.WebSocketHandler):
def open(self):
print 'new connection'
self.write_message("Hello World")
def on_message(self, message):
print 'message received %s' % message
self.write_message('ECHO: ' + message)
def on_close(self):
print 'connection closed'
application = tornado.web.Application([
(r'/ws', WSHandler),
])
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
As it is, this works as intended. However, I can't get my head around how can get this "integrated" into the rest of my application. In the example above, the WebSocket only sends something to the clients as a reply to a client's message. How can I access the WebSocket from the "outside"? For example, to notify all currently connected clients that some kind event has occured -- and this event is NOT any kind of message from a client. Ideally, I would like to write somewhere in my code something like:
websocket_server.send_to_all_clients("Good news everyone...")
How can I do this? Or do I have a complete misundersanding on how WebSockets (or Tornado) are supposed to work. Thanks!
You need to keep track of all the clients that connect. So:
clients = []
def send_to_all_clients(message):
for client in clients:
client.write_message(message)
class WSHandler(tornado.websocket.WebSocketHandler):
def open(self):
send_to_all_clients("new client")
clients.append(self)
def on_close(self):
clients.remove(self)
send_to_all_clients("removing client")
def on_message(self, message):
for client in clients:
if client != self:
client.write_message('ECHO: ' + message)
This is building on Hans Then's example. Hopefully it helps you understand how you can have your server initiate communication with your clients without the clients triggering the interaction.
Here's the server:
#!/usr/bin/python
import datetime
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
class WSHandler(tornado.websocket.WebSocketHandler):
clients = []
def open(self):
print 'new connection'
self.write_message("Hello World")
WSHandler.clients.append(self)
def on_message(self, message):
print 'message received %s' % message
self.write_message('ECHO: ' + message)
def on_close(self):
print 'connection closed'
WSHandler.clients.remove(self)
#classmethod
def write_to_clients(cls):
print "Writing to clients"
for client in cls.clients:
client.write_message("Hi there!")
application = tornado.web.Application([
(r'/ws', WSHandler),
])
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=15), WSHandler.write_to_clients)
tornado.ioloop.IOLoop.instance().start()
I made the client list a class variable, rather than global. I actually wouldn't mind using a global variable for this, but since you were concerned about it, here's an alternative approach.
And here's a sample client:
#!/usr/bin/python
import tornado.websocket
from tornado import gen
#gen.coroutine
def test_ws():
client = yield tornado.websocket.websocket_connect("ws://localhost:8888/ws")
client.write_message("Testing from client")
msg = yield client.read_message()
print("msg is %s" % msg)
msg = yield client.read_message()
print("msg is %s" % msg)
msg = yield client.read_message()
print("msg is %s" % msg)
client.close()
if __name__ == "__main__":
tornado.ioloop.IOLoop.instance().run_sync(test_ws)
You can then run the server, and have two instances of the test client connect. When you do, the server prints this:
bennu#daveadmin:~$ ./torn.py
new connection
message received Testing from client
new connection
message received Testing from client
<15 second delay>
Writing to clients
connection closed
connection closed
The first client prints this:
bennu#daveadmin:~$ ./web_client.py
msg is Hello World
msg is ECHO: Testing from client
< 15 second delay>
msg is Hi there! 0
And the second prints this:
bennu#daveadmin:~$ ./web_client.py
msg is Hello World
msg is ECHO: Testing from client
< 15 second delay>
msg is Hi there! 1
For the purposes of the example, I just had the server send the message to the clients on a 15 second delay, but it could be triggered by whatever you want.
my solution for this: first add "if __name__ == '__main__':" - to the main.py. then import main.py into the websocket module. e.g. (import main as MainApp) . it is now possible to call a function in 'main.py' from within the ws.py/WebSocketHandler-function. - inside the Handler pass the message like so:
MainApp.function(message)
i dunno if this is the opposite of elegant but it works for me.
..plus create and import a custom 'config.py' (thats looks like: someVar = int(0) ) into the 'mainApp.py' .. like so: import config as cfg --> now you can alter variables with cfg.someVar = newValue from inside the function in 'main.py' that once was called by the Handler from 'ws.py'.