Python websockets fast one way but 10x slower with response - python

I have a sanic webserver running websockets, behind the scenes its using the "websockets" library.
Server
from asyncio import sleep
from sanic import Sanic
app = Sanic("websocket test")
#app.websocket("/")
async def test(_, ws):
while True:
data = await ws.recv()
data = await ws.send('Hi')
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8000)
Client
import asyncio
import websockets
async def hello():
uri = "ws://localhost:8000"
async with websockets.connect(uri) as websocket:
while iteration:
await websocket.send("Hi")
await websocket.recv()
asyncio.get_event_loop().run_until_complete(hello())
When I remove ws.send('Hi') from the server and await websocket.recv() from the client i can get 58000 messages a second, once I start listening for a response it goes all the way down to 6000 messages a second, I am just curious what is making this run 10x slower when the server responds.

I think the solution here would be to seperate your send and recv into seperate tasks so they can yield concurrently.
async def producer(send):
while True:
await send("...")
await asyncio.sleep(1)
async def consumer(recv):
while True:
message = await recv
print(message)
async def test(request, ws):
request.app.add_task(producer(ws.send)))
request.app.add_task(consumer(ws.recv))
Obviously, this is a very simple example, and at the very least you should use some sort of a Queue for your producer.
But, when you break them into seperate tasks, then your recv is not blocked by send.

Related

Websocket getting closed immediately after connecting to FastAPI Endpoint

I'm trying to connect a websocket aiohttp client to a fastapi websocket endpoint, but I can't send or recieve any data because it seems that the websocket gets closed immediately after connecting to the endpoint.
server
import uvicorn
from fastapi import FastAPI, WebSocket
app = FastAPI()
#app.websocket('/ws')
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
...
if __name__ == '__main__':
uvicorn.run('test:app', debug=True, reload=True)
client
import aiohttp
import asyncio
async def main():
s = aiohttp.ClientSession()
ws = await s.ws_connect('ws://localhost:8000/ws')
while True:
...
asyncio.run(main())
When I try to send data from the server to the client when a connection is made
server
#app.websocket('/ws')
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
await websocket.send_text('yo')
client
while True:
print(await ws.receive())
I always get printed in my client's console
WSMessage(type=<WSMsgType.CLOSED: 257>, data=None, extra=None)
While in the server's debug console it says
INFO: ('127.0.0.1', 59792) - "WebSocket /ws" [accepted]
INFO: connection open
INFO: connection closed
When I try to send data from the client to the server
server
#app.websocket('/ws')
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
await websocket.receive_text()
client
ws = await s.ws_connect('ws://localhost:8000/ws')
await ws.send_str('client!')
Nothing happens, I get no message printed out in the server's console, just the debug message saying the client got accepted, connection opened and closed again.
I have no idea what I'm doing wrong, I followed this tutorial in the fastAPI docs for a websocket and the example there with the js websocket works completely fine.
The connection is closed by either end (client or server), as shown from your code snippets. You would need to have a loop in both the server and the client for being able to await for messages, as well as send messages, continuously (have a look here and here).
Additionally, as per FastAPI's documentation:
When a WebSocket connection is closed, the await websocket.receive_text() will raise a WebSocketDisconnect
exception, which you can then catch and handle like in this example.
Thus, on server side, you should use a try-except block to catch and handle WebSocketDisconnect exceptions. Below is a working example demonstrating a client (in aiohttp) - server (in FastAPI) communication using websockets.
Working Example
Server
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
import uvicorn
app = FastAPI()
#app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
# await for connections
await websocket.accept()
try:
# send "Connection established" message to client
await websocket.send_text("Connection established!")
# await for messages and send messages
while True:
msg = await websocket.receive_text()
if msg.lower() == "close":
await websocket.close()
break
else:
print(f'CLIENT says - {msg}')
await websocket.send_text(f"Your message was: {msg}")
except WebSocketDisconnect:
print("Client disconnected")
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8000)
Client
import aiohttp
import asyncio
async def main():
async with aiohttp.ClientSession() as session:
async with session.ws_connect('ws://127.0.0.1:8000/ws') as ws:
# await for messages and send messages
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
print(f'SERVER says - {msg.data}')
text = input('Enter a message: ')
await ws.send_str(text)
elif msg.type == aiohttp.WSMsgType.ERROR:
break
asyncio.run(main())

Use the same websocket connection in multiple asynchronous loops (Python)

I am running two loops asynchronously, and want both to have access to the same websocket connection. One function periodic_fetch() fetches some data periodically (every 60 seconds) and sends a message to the websocket if a condition is met. The other retrieve_websocket() receives messages from the websocket and perform some action if a condition is met. As of now, I connect to the websocket in both functions, but that means retrieve_websocket() will not receive the response to the websocket message sent by periodic_fetch(). How do I create one websocket connection and use the same one in both loops as they run asynchronously? My code:
# Imports
import asyncio
import websockets
from datetime import datetime
websocket_url = "wss://localhost:5000/"
# Simulate fetching some data
async def fetch_data():
print("Fetching started")
await asyncio.sleep(2)
return {"data": 2}
# Receive and analyze websocket data
async def retrieve_websocket():
async with websockets.connect(websocket_url) as ws:
while True:
msg = await ws.recv()
print(msg)
# Perform some task if condition is met
# Periodically fetch data and send messages to websocket
async def periodic_fetch():
async with websockets.connect(websocket_url) as ws:
while True:
print(datetime.now())
fetch_task = asyncio.create_task(fetch_data())
wait_task = asyncio.create_task(asyncio.sleep(60))
res = await fetch_task
# Send message to websocket
await ws.send("Websocket message")
# Wait the remaining wait duration
await wait_task
loop = asyncio.get_event_loop()
cors = asyncio.wait([periodic_fetch(), retrieve_websocket()])
loop.run_until_complete(cors)
The solution was to open the connection in a separate function and use asyncio.gather() passing in the two functions with the websocket as parameter.
async def run_script():
async with websockets.connect(websocket_url) as ws:
await asyncio.gather(periodic_fetch(ws), retrieve_websocket(ws))
asyncio.run(run_script())

Execute async request every N seconds while websocket keeps streaming python

Using Async with websocket and web requests, how does one continuously watch data from websocket while also, with N seconds interval, updating data from web request.
I've tried different versions of
async def websocket_stream():
while True:
get_data_from_stream()
do_stuff_with_data()
async def web_requests():
await asyncio.sleep(30)
async with httpx.AsyncClient() as client:
for n in list_of_things:
html= client.get('www.webpage.com/stuff')
do_other_stuff_with_data(html)
async def do_sometimes(timeout, stuff):
while True:
await asyncio.sleep(timeout)
await stuff()
async def do_nonstop(stuff):
while True:
await stuff()
taskone=asyncio.create_task(do_nonstop(web_stream))
tasktwo=asyncio.create_task(do_sometimes(10, web_request()))
Which obviously doesnt work and just streams web_streams.
How do I define so that web_request() only does one loop thru list_of_things and then sleeps for N seconds while web_streams() websocket keeps streaming?

Python Websocket only receives once from client

I want to send values from a for loop from the client-server but the server only receives the first value and the connection is cut shortly
Client
import asyncio
import websockets
import time
async def message():
async with websockets.connect("ws://-------:5051") as socket:
for i in range(20):
await socket.send(f"{i}")
print(i)
time.sleep(4)
asyncio.get_event_loop().run_until_complete(message())
Server
import asyncio
import websockets
async def consumer_handler(websocket,path):
client_type = await websocket.recv()
print(client_type)
start_server = websockets.serve(consumer_handler,"ws://-------:5051", 5051)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
So, your consumer_handler receives message once and finishes.
You need to add loop.
Try something like this:
async def consumer_handler(websocket, path):
async for msg in websocket:
print(msg)

listen to multiple socket with websockets and asyncio

I am trying to create a script in python that listens to multiple sockets using websockets and asyncio, the problem is that no matter what I do it only listen to the first socket I call.
I think its the infinite loop, what are my option to solve this? using threads for each sockets?
async def start_socket(self, event):
payload = json.dumps(event)
loop = asyncio.get_event_loop()
self.tasks.append(loop.create_task(
self.subscribe(event)))
# this should not block the rest of the code
await asyncio.gather(*tasks)
def test(self):
# I want to be able to add corotines at a different time
self.start_socket(event1)
# some code
self.start_socket(event2)
this is what I did eventually, that way its not blocking the main thread and all subscriptions are working in parallel.
def subscribe(self, payload):
ws = websocket.WebSocket(sslopt={"cert_reqs": ssl.CERT_NONE})
ws.connect(url)
ws.send(payload)
while True:
result = ws.recv()
print("Received '%s'" % result)
def start_thread(self, loop):
asyncio.set_event_loop(loop)
loop.run_forever()
def start_socket(self, **kwargs):
worker_loop = asyncio.new_event_loop()
worker = Thread(target=self.start_thread, args=(worker_loop,))
worker.start()
worker_loop.call_soon_threadsafe(self.subscribe, payload)
def listen(self):
self.start_socket(payload1)
# code
self.start_socket(payload2)
# code
self.start_socket(payload3)
Your code appears incomplete, but what you've shown has two issues. One is that run_until_complete accepts a coroutine object (or other kind of future), not a coroutine function. So it should be:
# note parentheses after your_async_function()
asyncio.get_event_loop().run_until_complete(your_async_function())
the problem is that no matter what I do it only listen to the first socket I call. I think its the infinite loop, what are my option to solve this? using threads for each sockets?
The infinite loop is not the problem, asyncio is designed to support such "infinite loops". The problem is that you are trying to do everything in one coroutine, whereas you should be creating one coroutine per websocket. This is not a problem, as coroutines are very lightweight.
For example (untested):
async def subscribe_all(self, payload):
loop = asyncio.get_event_loop()
# create a task for each URL
for url in url_list:
tasks.append(loop.create_task(self.subscribe_one(url, payload)))
# run all tasks in parallel
await asyncio.gather(*tasks)
async def subsribe_one(self, url, payload):
async with websockets.connect(url) as websocket:
await websocket.send(payload)
while True:
msg = await websocket.recv()
print(msg)
One way to efficiently listen to multiple websocket connections from a websocket server is to keep a list of connected clients and essentially juggle multiple conversations in parallel.
E.g. A simple server that sends random # to each connected client every few secs:
import os
import asyncio
import websockets
import random
websocket_clients = set()
async def handle_socket_connection(websocket, path):
"""Handles the whole lifecycle of each client's websocket connection."""
websocket_clients.add(websocket)
print(f'New connection from: {websocket.remote_address} ({len(websocket_clients)} total)')
try:
# This loop will keep listening on the socket until its closed.
async for raw_message in websocket:
print(f'Got: [{raw_message}] from socket [{id(websocket)}]')
except websockets.exceptions.ConnectionClosedError as cce:
pass
finally:
print(f'Disconnected from socket [{id(websocket)}]...')
websocket_clients.remove(websocket)
async def broadcast_random_number(loop):
"""Keeps sending a random # to each connected websocket client"""
while True:
for c in websocket_clients:
num = str(random.randint(10, 99))
print(f'Sending [{num}] to socket [{id(c)}]')
await c.send(num)
await asyncio.sleep(2)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
try:
socket_server = websockets.serve(handle_socket_connection, 'localhost', 6789)
print(f'Started socket server: {socket_server} ...')
loop.run_until_complete(socket_server)
loop.run_until_complete(broadcast_random_number(loop))
loop.run_forever()
finally:
loop.close()
print(f"Successfully shutdown [{loop}].")
A simple client that connects to the server and listens for the numbers:
import asyncio
import random
import websockets
async def handle_message():
uri = "ws://localhost:6789"
async with websockets.connect(uri) as websocket:
msg = 'Please send me a number...'
print(f'Sending [{msg}] to [{websocket}]')
await websocket.send(msg)
while True:
got_back = await websocket.recv()
print(f"Got: {got_back}")
asyncio.get_event_loop().run_until_complete(handle_message())
Mixing up threads and asyncio is more trouble than its worth and you still have code that will block on the most wasteful steps like network IO (which is the essential benefit of using asyncio).
You need to run each coroutine asynchronously in an event loop, call any blocking calls with await and define each method that interacts with any awaitable interactions with an async
See a working e.g.: https://github.com/adnantium/websocket_client_server

Categories