[ python asyncio使用 ]


fastapi启动websocket服务, 并且使用async

from fastapi import FastAPI, WebSocket, Depends, WebSocketDisconnect, WebSocketException
from fastapi.responses import HTMLResponse

from extends.redis.conn import redis_connection
from config.settings import get_settings, Settings
from typing import TypedDict, Literal


from contextlib import asynccontextmanager
from asyncio import sleep
import asyncio

import logging
import redis
import orjson

"""
    websocket server
"""



@asynccontextmanager
async def lifespan(app: FastAPI):
    yield
    redis_connection.disconnect_redis_connection()

# 设置日志格式和级别
logging.basicConfig(
    level=logging.INFO,  # 可以根据需要设置为DEBUG、WARNING、ERROR等
    format="[%(name)s]-[%(levelname)s]: %(message)s",
)

# 获取一个logger实例,通常与您的应用或模块名称相关联
logger = logging.getLogger("websocket")

settings: Settings = get_settings()
logger.debug(settings)

app = FastAPI(lifespan=lifespan)

active_connections = set()
stop_flags: dict[WebSocket, asyncio.Event] = {}
task_running_flags: dict[WebSocket, asyncio.Event] = {}

html = """
<!DOCTYPE html>
<html>
    <head>
        <title>Chat</title>
    </head>
    <body>
        <h1>hhs data send to console</h1>
        <script>
            var ws = new WebSocket("ws://localhost:{}");
            // 接受服务端数据
            ws.onopen = function(event) {{
                ws.send("get_status");
                console.log("Sent request for data");
            }};
            ws.onmessage = function(event) {{
                console.log(JSON.parse(event.data))
            }};
            ws.onclose = function(event) {{
                ws.send("stop_get_status");
                console.log("Sent stop request");
            }};
        </script>
    </body>
</html>
""".format(settings.WEBSOCKET_PORT)


@app.get("/ws")
async def get():
    return HTMLResponse(html)


class InitData(TypedDict):
    current_result: dict
    previous_result: dict
    is_getting_status: bool


async def handle_send_data(websocket: WebSocket, redis_conn: redis.Redis, stop_flag: asyncio.Event, task_running: asyncio.Event):
    """处理发送给客户端的数据"""
    current_result = {}
    previous_result = {}
    while not stop_flag.is_set():
        if task_running.is_set():  
            data = redis_conn.get(settings.DATA_RESULT_KEY)
            if data:
                current_result = orjson.loads(data)
                if current_result != previous_result:
                    await websocket.send_json(current_result)  
                    previous_result = current_result
            await asyncio.sleep(1)
        else:  
            await task_running.wait()


@app.websocket("/")
async def websocket_endpoint(websocket: WebSocket, redis_conn: redis.Redis = Depends(redis_connection.get_redis_connection)):
    active_connections.add(websocket)
    stop_flags[websocket] = asyncio.Event()
    task_running_flags[websocket] = asyncio.Event()  

    await websocket.accept()
    send_data_task = None

    try:
        while True:
            data = await websocket.receive_text()
            if data == "get_status":
                if not task_running_flags[websocket].is_set():  
                    stop_flags[websocket].clear()  
                    task_running_flags[websocket].set()  
                    send_data_task = asyncio.create_task(handle_send_data(websocket, redis_conn, stop_flags[websocket], task_running_flags[websocket]))
            elif data == "stop_get_status":
                stop_flags[websocket].set()  
                task_running_flags[websocket].clear()  
            elif data == "close":
                stop_flags[websocket].set()
                task_running_flags[websocket].clear()
                await websocket.close()
                break
            # 休眠的时间
            await asyncio.sleep(settings.GET_DATA_FREQUENCY)

    except (WebSocketDisconnect, WebSocketException):
        logger.info("websocket disconnect")
    except Exception as e:
        logger.error(e)
    finally:
        if send_data_task and not send_data_task.done():  
            send_data_task.cancel()
        active_connections.remove(websocket)
        del stop_flags[websocket]
        del task_running_flags[websocket]  

    return None


if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host=settings.WEBSOCKET_HOST, port=settings.WEBSOCKET_PORT)