I am wanting to run a program in Python that sends a message every second via web sockets to a Tornado server. I have been using the example on websocket-client;
This example does not work, because ws.run_forever() will stop the execution of the while loop.
Can somebody give me an example of how to correctly implement this as a threaded class which I can both call the send method of, but also receive messages?
import websocket
import thread
import time
def on_message(ws, message):
print message
def on_error(ws, error):
print error
def on_close(ws):
print "### closed ###"
def on_open(ws):
pass
if __name__ == "__main__":
websocket.enableTrace(True)
ws = websocket.WebSocketApp("ws://echo.websocket.org/", on_message = on_message, on_error = on_error, on_close = on_close)
ws.on_open = on_open
ws.run_forever()
while True:
#do other actions here... collect data etc.
for i in range(100):
time.sleep(1)
ws.send("Hello %d" % i)
time.sleep(1)
There's an example in their github page that does exactly that. It seems like you started out of that example and took the code that sends messages every second out of the on_open and pasted it after the run_forever call, that BTW runs until the socket is disconnected.
Maybe you are having issues with the basic concepts here. There's always going to be a thread dedicated to listening to the socket (in this case the main thread that enters a loop inside the run_forever waiting for messages). If you want to have some other thing going on you'll need another thread.
Below is a different version of their example code, where instead of using the main thread as the "socket listener", another thread is created and the run_forever runs there. I see it as a bit more complicated since you have to write code to assure the socket has connected while you could use the on_open callback, but maybe it will help you understand.
import websocket
import threading
from time import sleep
def on_message(ws, message):
print message
def on_close(ws):
print "### closed ###"
if __name__ == "__main__":
websocket.enableTrace(True)
ws = websocket.WebSocketApp("ws://echo.websocket.org/", on_message = on_message, on_close = on_close)
wst = threading.Thread(target=ws.run_forever)
wst.daemon = True
wst.start()
conn_timeout = 5
while not ws.sock.connected and conn_timeout:
sleep(1)
conn_timeout -= 1
msg_counter = 0
while ws.sock.connected:
ws.send('Hello world %d'%msg_counter)
sleep(1)
msg_counter += 1
In 2023, they have an updated example for dispatching multiple WebSocketApps using an asynchronous dispatcher like rel.
import websocket, rel
addr = "wss://api.gemini.com/v1/marketdata/%s"
for symbol in ["BTCUSD", "ETHUSD", "ETHBTC"]:
ws = websocket.WebSocketApp(addr % (symbol,), on_message=lambda w, m : print(m))
ws.run_forever(dispatcher=rel, reconnect=3)
rel.signal(2, rel.abort) # Keyboard Interrupt
rel.dispatch()
Hope it helps!
Related
I have a WebSocket I run generally as follows:
import websocket
def on_message(ws, message):
...
ws = websocket.WebSocketApp(socket, on_open=on_open, on_message=on_message,
on_close=on_close)
ws.run_forever(ping_interval=5, ping_timeout=-1)
I want this to run but also perform another check from a function on every minute that elapses. Is there any way I can do this. I have heard of maybe threading or asyncio but have never used either. Any advice would be greatly appreciated.
The simplest approach would be using threading
import websocket
import threading
def runs_every_minute():
while True:
# do something
time.sleep(60)
def on_message(ws, message):
...
t = threading.Thread(target=runs_every_minute)
t.start() # starts the thread
ws = websocket.WebSocketApp(socket, on_open=on_open, on_message=on_message,
on_close=on_close)
ws.run_forever(ping_interval=5, ping_timeout=-1)
TL;DR: Calling future.set_result doesn't immediately resolve loop.run_until_complete. Instead it blocks for an additional 5 seconds.
Full context:
In my project, I'm using autobahn and asyncio to send and receive messages with a websocket server. For my use case, I need a 2nd thread for websocket communication, since I have arbitrary blocking code that will be running in the main thread. The main thread also needs to be able to schedule messages for the communication thread to send back and forth with the server. My current goal is to send a message originating from the main thread and block until the response comes back, using the communication thread for all message passing.
Here is a snippet of my code:
import asyncio
import threading
from autobahn.asyncio.websocket import WebSocketClientFactory, WebSocketClientProtocol
CLIENT = None
class MyWebSocketClientProtocol(WebSocketClientProtocol):
# -------------- Boilerplate --------------
is_connected = False
msg_queue = []
msg_listeners = []
def onOpen(self):
self.is_connected = True
for msg in self.msg_queue[::]:
self.publish(msg)
def onClose(self, wasClean, code, reason):
is_connected = False
def onMessage(self, payload, isBinary):
for listener in self.msg_listeners:
listener(payload)
def publish(self, msg):
if not self.is_connected:
self.msg_queue.append(msg)
else:
self.sendMessage(msg.encode('utf-8'))
# /----------------------------------------
def send_and_wait(self):
future = asyncio.get_event_loop().create_future()
def listener(msg):
print('set result')
future.set_result(123)
self.msg_listeners.append(listener)
self.publish('hello')
return future
def worker(loop, ready):
asyncio.set_event_loop(loop)
factory = WebSocketClientFactory('ws://127.0.0.1:9000')
factory.protocol = MyWebSocketClientProtocol
transport, protocol = loop.run_until_complete(loop.create_connection(factory, '127.0.0.1', 9000))
global CLIENT
CLIENT = protocol
ready.set()
loop.run_forever()
if __name__ == '__main__':
# Set up communication thread to talk to the server
threaded_loop = asyncio.new_event_loop()
thread_is_ready = threading.Event()
thread = threading.Thread(target=worker, args=(threaded_loop, thread_is_ready))
thread.start()
thread_is_ready.wait()
# Send a message and wait for response
print('starting')
loop = asyncio.get_event_loop()
result = loop.run_until_complete(CLIENT.send_and_wait())
print('done') # this line gets called 5 seconds after it should
I'm using the autobahn echo server example to respond to my messages.
Problem: The WebSocketClientProtocol receives the response to its outgoing message and calls set_result on its pending future, but loop.run_until_complete blocks an additional ~4.9 seconds until eventually resolving.
I understand that run_until_complete also processes other pending events on the event loop. Is it possible that the main thread has somehow queued up a bunch of events that have to now get processed once I start the loop? Also, if I move run_until_complete into the communications thread or move the create_connection into the main thread, then the event loop doesn't block me.
Lastly, I tried to recreate this problem without using autobahn, but I couldn't cause the extra delay. I'm curious if maybe this is an issue with the nature of autobahn's callback timing (onMessage for example).
Below is an example of a websocket that uses threads to maintain a web socket connection.
import websocket
import threading
from time import sleep
def on_message(ws, message):
print message
def on_close(ws):
print "### closed ###"
if __name__ == "__main__":
websocket.enableTrace(True)
ws = websocket.WebSocketApp("ws://echo.websocket.org/", on_message = on_message, on_close = on_close)
wst = threading.Thread(target=ws.run_forever)
wst.daemon = True
wst.start()
conn_timeout = 5
while not ws.sock.connected and conn_timeout:
sleep(1)
conn_timeout -= 1
msg_counter = 0
while ws.sock.connected:
ws.send('Hello world %d'%msg_counter)
sleep(1)
msg_counter += 1
I'm wondering, how can I modify this code to use a Process instead? Threads aren't parallel due to the GIL and I want to speed up this code. I've tried converting this code myself, however, my main issue is how to pass data from the socket process to the main process.
Does anyone know how to convert this code? I'm having difficulties.
While using websocket client to send test messages to a django server, I cannot get a script to work which can both send and receive messages.
The following python script is what I have attempted:
import websocket
import threading
import json
from time import sleep
# handle message event
def on_message(ws, message):
print("message recieved: %s" % message)
# handle close event
def on_close(ws):
print("channel closed")
# execute as main script
if __name__ == "__main__":
websocket.enableTrace(True)
# new app object connecting to headstation
ws = websocket.WebSocketApp("ws://192.168.0.106:8000/?testI123", on_message = on_message, on_close = on_close)
# run in a new thread - kill if script ends
ws_listener = threading.Thread(target=ws.run_forever())
ws_listener.daemon = True
# start second thread
ws_listener.start()
# attempt connection 5 times
timeout = 5
while not ws.sock.connected and timeout:
sleep(1)
timeout -= 1
# error on timeout
if (timeout == 0):
print("Connection to server timed out")
print("test 1")
# periodically send test message to server
message_num = 0
while ws.sock.connected:
# send node id and message
message = 'hello %d'%message_num
ws.send(message)
sleep(1)
message_num += 1
This connections successfully, indicted by the server, and receives messages sent from the server, but does not send anything.
Periodically, something like this is displayed on the terminal:
send: b'\x8a\x84\xe2\xe9\xa8\xe2\x8f\xdc\xe2\x84'
If I simply use
ws = websocket.WebSocket()
ws.connect(url)
ws.send("hello")
then this works perfectly. Suggesting it is something wrong with my little python script displayed above.
Found the problem, stupid mistake of course:
ws_listener = threading.Thread(target=ws.run_forever())
should be:
ws_listener = threading.Thread(target=ws.run_forever)
without parentheses.
First one passes result of ws.run_forever to the target, second one sets ws.run_forever as the target, which was the intended outcome.
I am trying to connect to multiple channels concurrently and receive messages from a push API through the python websocket library.
Considering the following code below, how would you connect to multiple channels? this code was obtained and slightly modified from here: https://pypi.python.org/pypi/websocket-client
What confuses me is the second last line: ws.on_open = on_open. on_open is defined as a function above and takes 1 argument but no argument is passed when calling the function, I don't recall encountering this before in python code, so I'm unsure what is really going on in this line.
How can I modify this code so that I can pass a variable that contains a string to the function on_open so that I can specify the name of the Chanel that I want to subscribe to? My main goal is to be able to use the multiprocessing library to pass multiple channels to subscribe to concurrently.
Would I accomplish this by creating multiple ws objects or, one ws object and calling on_open multiple times with different channels as arguments?
import websocket
import thread
import time
import json
def on_message(ws, message):
print(message)
def on_error(ws, error):
print(error)
def on_close(ws):
print("### closed ###")
def on_open(ws):
def run(*args):
ws.send(json.dumps({'channel':'channel1'}))
while True:
time.sleep(1)
ws.close()
print("thread terminating...")
thread.start_new_thread(run, ())
if __name__ == "__main__":
websocket.enableTrace(True)
ws = websocket.WebSocketApp("wss://random.example.com",
on_message = on_message,
on_error = on_error,
on_close = on_close)
ws.on_open = on_open
ws.run_forever()
Use partial to pass in additional arguments
from functools import partial
def on_open(ws, channel_name):
"""
Notice the channel_name parameter
"""
# create a new function with the predefined variable
chan1 = partial(on_open, channel_name='channel 1')
# set the new function as the on_open callback
ws1.on_open = chan1
# do the same for the rest
chan2 = partial(on_open, channel_name='channel 2')
ws2.on_open = chan2
As a side note, consider using Tornado or Crossbar.io (aka autobahn). Those are proper asynchronous frameworks and make websocket development simpler as opposed to threads and multiprocessing.