I want to send two different messages to a websocket server but with different time intervalls.
For example:
The first message should be send every 2 seconds.
The second message should send every 5 seconds.
async def send_first_message(websocket):
while True:
await websocket.send("FIRST MESSAGE")
response = await websocket.recv()
await asyncio.sleep(2)
async def send_second_message():
while True:
async with websockets.connect(f"ws://{IP}:{PORT}") as websocket:
asyncio.create_task(send_first_message(websocket))
while True:
await websocket.send("SECOND MESSAGE")
response = await websocket.recv()
await asyncio.sleep(5)
asyncio.run(send_second_message())
If I run the code like this I get:
"RuntimeError: cannot call recv while another coroutine is already waiting for the next message"
If I comment out one of the "await websocket.recv()" it works fine for a few seconds and then it throws:
"RuntimeError no close frame received or sent"
There's a bit of a disconnect between what you are trying to do in the tasks (synchronous request-response interaction) and what the protocol and the library expects you to do (asynchronous messages).
When writing asynchronous code, you need to look at what the library/protocol/service expects to be an atomic operation that can happen asynchronously to everything else, and what you want to be a synchronous series of operations. Then you need to find the primitive in the library that will support that. In the case of websockets, the atomic operation is a message being sent in either direction. So you can't expect websockets to synchronize flow over two messages.
Or to put it another way, you are expecting synchronous responses for each send message, but websockets are not designed to handle interleaved synchronous requests. You've sent a message to the websocket server, and you want to get a response to that message. But you've also sent another message on the same websocket and want a response to that too. Your client websocket library can't differentiate between a message intended for the first request and a message intended for the second request (because from the websocket protocol layer, that is a meaningless concept - so the library enforces this by limiting the recv operations on a websocket that can be blocking to one).
So ...
Option 1 - multiple tasks on separate sockets
From the fact the library limits a websocket to one blocking recv, a primitive in the protocol that meets the requirement is the websocket itself. If these are separate requests that you need separate blocking responses to (so only continue in the requesting task once those responses are available) then you could have separate websocket connections and block for the response in each.
client1.py
async def send_first_message():
async with websockets.connect(f"ws://{IP}:{PORT}") as websocket:
while True:
await websocket.send("FIRST MESSAGE")
response = await websocket.recv()
print(response)
await asyncio.sleep(2)
async def send_second_message():
async with websockets.connect(f"ws://{IP}:{PORT}") as websocket:
while True:
await websocket.send("SECOND MESSAGE")
response = await websocket.recv()
print(response)
await asyncio.sleep(5)
async def main():
asyncio.create_task(send_first_message())
asyncio.create_task(send_second_message())
await asyncio.Future()
asyncio.run(main())
Option 1 is however not really the websocket or asynchronous way.
Option 2 - embrace the asynchronous
To do this on a single websocket, you will need to receive the response asynchronous to both sending tasks.
If you don't actually care that the send_* functions get the response, you can do this easily...
client2.py
async def send_first_message(websocket):
while True:
await websocket.send("FIRST MESSAGE")
await asyncio.sleep(2)
async def send_second_message(websocket):
while True:
await websocket.send("SECOND MESSAGE")
await asyncio.sleep(5)
async def receive_message(websocket):
while True:
response = await websocket.recv()
print(response)
async def main():
async with websockets.connect(f"ws://{IP}:{PORT}") as websocket:
asyncio.create_task(send_first_message(websocket))
asyncio.create_task(send_second_message(websocket))
asyncio.create_task(receive_message(websocket))
await asyncio.Future()
asyncio.run(main())
Option 3
But what if you want to line up responses to requests and keep on a single websocket? You need some way of knowing which request any particular response is for. Most web services that need this sort of interaction will have you send an ID in the message to the server, and it will respond once a response is ready using the ID as a reference.
There's also a way of getting your message tasks to block and wait for the response with the right ID by queuing up the responses and checking them periodically.
client3.py
unhandled_responses = {}
async def send_first_message(websocket):
while True:
req_id = random.randint(0,65535)
message = json.dumps({'id': req_id, 'message': 'FIRST MESSAGE'})
await websocket.send(message)
response = await block_for_response(req_id)
print(response)
await asyncio.sleep(2)
async def send_second_message(websocket):
while True:
req_id = random.randint(0,65535)
message = json.dumps({'id': req_id, 'message': 'SECOND MESSAGE'})
await websocket.send(message)
response = await block_for_response(req_id)
print(response)
await asyncio.sleep(5)
async def block_for_response(id):
while True:
response = unhandled_responses.pop(id, None)
if response:
return response
await asyncio.sleep(0.1)
async def receive_message(websocket):
while True:
response = json.loads(await websocket.recv())
unhandled_responses[response['id']] = response
async def main():
async with websockets.connect(f"ws://{IP}:{PORT}") as websocket:
asyncio.create_task(send_first_message(websocket))
asyncio.create_task(send_second_message(websocket))
asyncio.create_task(receive_message(websocket))
await asyncio.Future()
asyncio.run(main())
For completeness, the server code the clients were talking to in my tests.
server.py
import asyncio
import websockets
async def server_endpoint(websocket):
try:
while True:
recv_msg = await websocket.recv()
response = recv_msg
await websocket.send(response)
except Exception as ex:
print(str(ex))
async def main():
async with websockets.serve(server_endpoint, "localhost", 8765):
await asyncio.Future() # run forever
if __name__ == "__main__":
asyncio.run(main())
Related
I'm using websockets Python package. I have a websocket server that listens for a connection.
I am including my code below. The expected result is that after opening the connections, the server should await for a message from the client. After that it should send a message "hi" and wait for another message. After that it should send another message ("hi2") etc.
However, the program doesn't behave in the expected way. Everything is all right to the second call of await websocket.recv(). That call returns "hi" immediately, without waiting for the message sent from the client. So it returns the message that it has just sent before, instead of waiting for the client to send the message.
from asyncio import get_event_loop
from websockets import serve
async def run(websocket, path):
print("path", path)
print("connection open! hooooraaaaayyyyy")
message = await websocket.recv()
print(message)
await websocket.send('hi')
print("I've just said hi")
message = await websocket.recv() # returns 'hi' immediately (its own message)
print(message)
await websocket.send('hi2')
message = await websocket.recv() # returns 'hi2'
print(message)
await websocket.send('hi3')
print("end of run")
start_server = serve(run, "localhost", 8765)
get_event_loop().run_until_complete(start_server)
get_event_loop().run_forever()
I decided to ugly fix it with an additional await websocket.recv() line after each await websocket.send() line. That way, the recv() after the send() will return the message that has just been sent and the second await websocket.recv() will await for the message from the client. So the code now looks like that:
async def run(websocket, path):
print("path", path)
print("connection open! hooooraaaaayyyyy")
message = await websocket.recv()
print(message)
await websocket.send('hi')
await websocket.recv() # will return 'hi'
print("I've just said hi")
message = await websocket.recv() # will wait for the client
print(message)
await websocket.send('hi2')
await websocket.recv()
message = await websocket.recv()
print(message)
await websocket.send('hi3')
await websocket.recv()
print("end of run")
That solved the problem. But at some point when I run the program, the program randomly started to behave differently - it started to await for the client to return the message. So now it was awaiting two times. So I brought it back to what it was before and it worked as expected.
But the program seems to randomly sometimes act in the expected way (await websocket.recv() returning the message from the client) and sometimes in the unexpected way (await websocket.recv() return the message that has just been sent by the server). So how can I make it work so that it works always?
Using Quart I am trying to receive data from one client via a websocket, then have the Quart websocket server send it to a different client via websocket.
The two clients will be alone sharing the same url, other pairs of clients will have their own urls. This echo test works for both clients individually:
#copilot_ext.websocket('/ws/<unique_id>')
async def ws(unique_id):
while True:
data = await websocket.receive()
await websocket.send(f"echo {data}")
I have tried broadcasting using the example here https://pgjones.gitlab.io/quart/tutorials/websocket_tutorial.html#broadcasting although I can catch and print the different websockets, have not had much luck sending data from one client to the other :(
connected_websockets = set()
def collect_websocket(func):
#wraps(func)
async def wrapper(*args, **kwargs):
global connected_websockets
send_channel, receive_channel = trio.open_memory_channel(2)
connected_websockets.add(send_channel)
try:
return await func(send_channel, *args, **kwargs)
finally:
connected_websockets.remove(send_channel)
return wrapper
#copilot_ext.websocket('/ws/<unique_id>')
#collect_websocket
async def ws(que, unique_id):
while True:
data = await websocket.receive()
for send_channel in connected_websockets:
await send_channel.send(f"message {data}")
print(send_channel)
Just storing the websocket object and iterating through them doesn't work either
connected_websockets = set()
#copilot_ext.websocket('/ws/<unique_id>')
async def ws(unique_id):
global connected_websockets
while True:
data = await websocket.receive()
connected_websockets.add(websocket)
for websockett in connected_websockets:
await websockett.send(f"message {data}")
print(type(websockett))
I think this snippet can form the basis of what you want to achieve. The idea is that rooms are a collection of queues keyed by the room id. Then each connected client has a queue in the room which any other clients put messages to. The send_task then runs in the background to send any messages to the client that are on its queue. I hope this makes sense,
import asyncio
from collections import defaultdict
from quart import Quart, websocket
app = Quart(__name__)
websocket_rooms = defaultdict(set)
async def send_task(ws, queue):
while True:
message = await queue.get()
await ws.send(message)
#app.websocket("/ws/<id>/")
async def ws(id):
global websocket_rooms
queue = asyncio.Queue()
websocket_rooms[id].add(queue)
try:
task = asyncio.ensure_future(send_task(websocket._get_current_object(), queue))
while True:
message = await websocket.receive()
for other in websocket_rooms[id]:
if other is not queue:
await other.put(message)
finally:
task.cancel()
await task
websocket_rooms[id].remove(queue)
I tried out the example below (from this page):
nc = NATS()
await nc.connect(servers=["nats://demo.nats.io:4222"])
future = asyncio.Future()
async def cb(msg):
nonlocal future
future.set_result(msg)
await nc.subscribe("updates", cb=cb)
await nc.publish("updates", b'All is Well')
await nc.flush()
# Wait for message to come in
msg = await asyncio.wait_for(future, 1)
But this only seems useful for receiving one message. How would I subscribe and keep receiving messages?
I've also seen the package example, but it seems to just play both sides of the conversation, then quit.
You can find a long running service example as well: https://github.com/nats-io/nats.py/blob/master/examples/service.py
import asyncio
from nats.aio.client import Client as NATS
async def run(loop):
nc = NATS()
async def disconnected_cb():
print("Got disconnected...")
async def reconnected_cb():
print("Got reconnected...")
await nc.connect("127.0.0.1",
reconnected_cb=reconnected_cb,
disconnected_cb=disconnected_cb,
max_reconnect_attempts=-1,
loop=loop)
async def help_request(msg):
subject = msg.subject
reply = msg.reply
data = msg.data.decode()
print("Received a message on '{subject} {reply}': {data}".format(
subject=subject, reply=reply, data=data))
await nc.publish(reply, b'I can help')
# Use queue named 'workers' for distributing requests
# among subscribers.
await nc.subscribe("help", "workers", help_request)
print("Listening for requests on 'help' subject...")
for i in range(1, 1000000):
await asyncio.sleep(1)
try:
response = await nc.request("help", b'hi')
print(response)
except Exception as e:
print("Error:", e)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(run(loop))
loop.run_forever()
loop.close()
Don't know much about python, but looks like you are just waiting for one message and program ends. You should look at the subscriber example here. As you can see, there is a loop to wait forever or for the SIGTERM signal.
I want to perform async operations in a aiohttp request handler after sending the response. As per docs I should write
async def ping_handler(request):
"""Send PONG and increase DB counter."""
# explicitly send the response
resp = web.json_response({'message': 'PONG'})
await resp.prepare(request)
await resp.write_eof()
# job after sending resp
time.sleep(1)
return resp
This recipe works great for non-async functions. But when I use asyncio.sleep, it failes:
async def ping_handler(request):
"""Send PONG and increase DB counter."""
# explicitly send the response
resp = web.json_response({'message': 'PONG'})
await resp.prepare(request)
await resp.write_eof()
# job after sending resp
print('Enter job')
await asyncio.sleep(1)
print('Exit job')
return resp
I see Enter job, but no Exit job.
What do I do wrong?
http://websockets.readthedocs.io/en/stable/intro.html#consumer contains the following example:
async def consumer_handler(websocket, path):
while True:
message = await websocket.recv()
await consumer(message)
and http://websockets.readthedocs.io/en/stable/intro.html#producer
async def producer_handler(websocket, path):
while True:
message = await producer()
await websocket.send(message)
But there is no example for consumer() and producer() implementation or any explanation. Can somebody provide any simple example for that?
In the first example, consumer_handler listens for the messages from a websocket connection. It then passes the messages to a consumer. In its simplest form, a consumer can look like this:
async def consumer(message):
# do something with the message
In the second example, producer_handler receives a message from a producer and sends it to the websocket connection. A producer can look like this:
async def producer():
message = "Hello, World!"
await asyncio.sleep(5) # sleep for 5 seconds before returning message
return message