KR_WebSocket - somaz94/python-study GitHub Wiki

๐Ÿ“Œ Python WebSocket ๊ฐœ๋… ์ •๋ฆฌ


1๏ธโƒฃ WebSocket ๊ธฐ์ดˆ

WebSocket์€ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๊ฐ„์˜ ์–‘๋ฐฉํ–ฅ ์‹ค์‹œ๊ฐ„ ํ†ต์‹ ์„ ์ œ๊ณตํ•˜๋Š” ํ”„๋กœํ† ์ฝœ์ž…๋‹ˆ๋‹ค.

import websockets
import asyncio

# ๊ธฐ๋ณธ WebSocket ์„œ๋ฒ„
async def echo(websocket, path):
    async for message in websocket:
        await websocket.send(f"Echo: {message}")

# ์„œ๋ฒ„ ์‹œ์ž‘
async def start_server():
    server = await websockets.serve(echo, "localhost", 8765)
    await server.wait_closed()

# ์‹คํ–‰
asyncio.run(start_server())

# ๊ธฐ๋ณธ WebSocket ํด๋ผ์ด์–ธํŠธ
async def connect_to_server():
    async with websockets.connect("ws://localhost:8765") as websocket:
        await websocket.send("Hello!")
        response = await websocket.recv()
        print(response)

โœ… ํŠน์ง•:

  • ์–‘๋ฐฉํ–ฅ ํ†ต์‹ 
  • ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ
  • ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์ „์†ก

2๏ธโƒฃ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋ง

class WebSocketServer:
    def __init__(self):
        self.clients = set()
    
    async def register(self, websocket):
        self.clients.add(websocket)
        try:
            await self.handle_client(websocket)
        finally:
            self.clients.remove(websocket)
    
    async def handle_client(self, websocket):
        try:
            async for message in websocket:
                await self.broadcast(message)
        except websockets.exceptions.ConnectionClosed:
            pass
    
    async def broadcast(self, message):
        if self.clients:
            await asyncio.gather(
                *[client.send(message) for client in self.clients]
            )

โœ… ํŠน์ง•:

  • ํด๋ผ์ด์–ธํŠธ ๊ด€๋ฆฌ
  • ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ
  • ์—ฐ๊ฒฐ ๊ด€๋ฆฌ

3๏ธโƒฃ ์—๋Ÿฌ ์ฒ˜๋ฆฌ์™€ ์žฌ์—ฐ๊ฒฐ

class WebSocketClient:
    def __init__(self, uri):
        self.uri = uri
        self.websocket = None
        self.retry_count = 0
        self.max_retries = 5
    
    async def connect(self):
        while self.retry_count < self.max_retries:
            try:
                self.websocket = await websockets.connect(self.uri)
                self.retry_count = 0
                return True
            except websockets.exceptions.ConnectionClosed:
                self.retry_count += 1
                await asyncio.sleep(2 ** self.retry_count)
        return False

โœ… ํŠน์ง•:

  • ์ž๋™ ์žฌ์—ฐ๊ฒฐ
  • ์—๋Ÿฌ ์ฒ˜๋ฆฌ
  • ์ง€์ˆ˜ ๋ฐฑ์˜คํ”„

4๏ธโƒฃ ํ”„๋กœํ† ์ฝœ ๊ตฌํ˜„

import json

class WebSocketProtocol:
    def __init__(self):
        self.handlers = {}
    
    def register_handler(self, event_type, handler):
        self.handlers[event_type] = handler
    
    async def handle_message(self, websocket, message):
        try:
            data = json.loads(message)
            event_type = data.get('type')
            payload = data.get('payload')
            
            if event_type in self.handlers:
                response = await self.handlers[event_type](payload)
                await websocket.send(json.dumps({
                    'type': f"{event_type}_response",
                    'payload': response
                }))
        except json.JSONDecodeError:
            await websocket.send(json.dumps({
                'type': 'error',
                'payload': '์ž˜๋ชป๋œ JSON ํ˜•์‹'
            }))

โœ… ํŠน์ง•:

  • ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์ฒ˜๋ฆฌ
  • JSON ๋ฉ”์‹œ์ง€ ํฌ๋งท
  • ์—๋Ÿฌ ํ•ธ๋“ค๋ง

5๏ธโƒฃ ๋ณด์•ˆ ๊ตฌํ˜„

import jwt
from datetime import datetime, timedelta

class SecureWebSocket:
    def __init__(self, secret_key):
        self.secret_key = secret_key
    
    def create_token(self, user_id):
        payload = {
            'user_id': user_id,
            'exp': datetime.utcnow() + timedelta(hours=1)
        }
        return jwt.encode(payload, self.secret_key, algorithm='HS256')
    
    async def authenticate_connection(self, websocket, path):
        try:
            token = await websocket.recv()
            user_id = self.verify_token(token)
            return user_id
        except ValueError as e:
            await websocket.close()
            return None
            
    def verify_token(self, token):
        try:
            payload = jwt.decode(token, self.secret_key, algorithms=['HS256'])
            return payload['user_id']
        except jwt.PyJWTError:
            raise ValueError("์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ")

โœ… ํŠน์ง•:

  • JWT ์ธ์ฆ
  • ํ† ํฐ ๊ฒ€์ฆ
  • ๋ณด์•ˆ ์—ฐ๊ฒฐ

6๏ธโƒฃ ์„ฑ๋Šฅ ์ตœ์ ํ™”

import asyncio
import orjson  # orjson์€ ๊ธฐ๋ณธ json๋ณด๋‹ค ๋น ๋ฅธ ์ฒ˜๋ฆฌ ์ œ๊ณต
import zlib
from concurrent.futures import ProcessPoolExecutor

class OptimizedWebSocket:
    def __init__(self):
        self.clients = set()
        self.process_pool = ProcessPoolExecutor(max_workers=4)
        self.message_queue = asyncio.Queue(maxsize=1000)
        
    async def register(self, websocket):
        self.clients.add(websocket)
        try:
            await self.handle_client(websocket)
        finally:
            self.clients.remove(websocket)
            
    async def handle_client(self, websocket):
        # ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ๋ฃจํ”„
        try:
            async for message in websocket:
                # ์••์ถ•๋œ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ
                if websocket.extensions and 'permessage-deflate' in websocket.extensions:
                    message = zlib.decompress(message)
                    
                # ๋ฉ”์‹œ์ง€ ํ์— ์ถ”๊ฐ€
                await self.message_queue.put((websocket, message))
        except websockets.exceptions.ConnectionClosed:
            pass
            
    async def process_message_queue(self):
        """๋ฉ”์‹œ์ง€ ํ ์ฒ˜๋ฆฌ ๋ฃจํ”„"""
        while True:
            websocket, message = await self.message_queue.get()
            try:
                # ๋ฌด๊ฑฐ์šด ์ฒ˜๋ฆฌ๋Š” ํ”„๋กœ์„ธ์Šค ํ’€์—์„œ ์‹คํ–‰
                data = await asyncio.get_event_loop().run_in_executor(
                    self.process_pool, 
                    self.process_message,
                    message
                )
                # ๊ฒฐ๊ณผ ์ „์†ก
                await websocket.send(orjson.dumps(data))
            except Exception as e:
                print(f"๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {str(e)}")
            finally:
                self.message_queue.task_done()
                
    def process_message(self, message):
        """CPU ์ง‘์•ฝ์ ์ธ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ (๋ณ„๋„ ํ”„๋กœ์„ธ์Šค์—์„œ ์‹คํ–‰)"""
        # ์‹ค์ œ ๊ตฌํ˜„์—์„œ๋Š” ๋ณต์žกํ•œ ์—ฐ์‚ฐ ์ˆ˜ํ–‰
        return {"status": "processed", "original": message}
    
    async def start_server(self, host='localhost', port=8765):
        """์„œ๋ฒ„ ์‹œ์ž‘"""
        # ๋ฉ”์‹œ์ง€ ํ ์ฒ˜๋ฆฌ ํƒœ์Šคํฌ ์‹œ์ž‘
        asyncio.create_task(self.process_message_queue())
        
        # ์„œ๋ฒ„ ์‹œ์ž‘
        server = await websockets.serve(
            self.register, 
            host, 
            port,
            compression=None,  # ํด๋ผ์ด์–ธํŠธ ์ง€์› ์‹œ ์ž๋™์œผ๋กœ ์••์ถ• ํ™œ์„ฑํ™”
            max_size=10 * 1024 * 1024,  # ์ตœ๋Œ€ ๋ฉ”์‹œ์ง€ ํฌ๊ธฐ (10MB)
            max_queue=64  # ๋‚ด๋ถ€ ๋ฉ”์‹œ์ง€ ํ ํฌ๊ธฐ
        )
        
        return server

โœ… ํŠน์ง•:

  • ๋ฉ”์‹œ์ง€ ํ๋ฅผ ํ†ตํ•œ ๋ฐฑํ”„๋ ˆ์…” ์ œ์–ด
  • ํ”„๋กœ์„ธ์Šค ํ’€์„ ํ™œ์šฉํ•œ CPU ๋ฐ”์šด๋“œ ์ž‘์—… ์ฒ˜๋ฆฌ
  • ์••์ถ•์„ ํ†ตํ•œ ๋ฐ์ดํ„ฐ ์ „์†ก๋Ÿ‰ ๊ฐ์†Œ
  • orjson ํ™œ์šฉ์œผ๋กœ JSON ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™” ์„ฑ๋Šฅ ํ–ฅ์ƒ
  • ๋ฉ”์‹œ์ง€ ํฌ๊ธฐ ๋ฐ ํ ์ œํ•œ์œผ๋กœ ๋ฆฌ์†Œ์Šค ๋ณดํ˜ธ

7๏ธโƒฃ ์›น ํ”„๋ ˆ์ž„์›Œํฌ ํ†ตํ•ฉ

from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from starlette.websockets import WebSocketState
import asyncio
import json
import logging

# FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ค์ •
app = FastAPI(title="WebSocket API")

# CORS ์„ค์ •
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # ์‹ค์ œ ํ™˜๊ฒฝ์—์„œ๋Š” ํŠน์ • ์ถœ์ฒ˜๋งŒ ํ—ˆ์šฉ
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# ๋กœ๊น… ์„ค์ •
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# ์—ฐ๊ฒฐ ๊ด€๋ฆฌ์ž
class ConnectionManager:
    def __init__(self):
        self.active_connections: dict = {}
        self.lock = asyncio.Lock()
        
    async def connect(self, websocket: WebSocket, client_id: str):
        await websocket.accept()
        async with self.lock:
            self.active_connections[client_id] = websocket
        logger.info(f"ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ: {client_id}")
        
    async def disconnect(self, client_id: str):
        async with self.lock:
            if client_id in self.active_connections:
                del self.active_connections[client_id]
                logger.info(f"ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ ํ•ด์ œ: {client_id}")
    
    async def send_personal_message(self, message: str, client_id: str):
        async with self.lock:
            if client_id in self.active_connections:
                websocket = self.active_connections[client_id]
                if websocket.client_state == WebSocketState.CONNECTED:
                    await websocket.send_text(message)
                    return True
        return False
                
    async def broadcast(self, message: str, exclude: str = None):
        tasks = []
        async with self.lock:
            for client_id, websocket in self.active_connections.items():
                if exclude != client_id and websocket.client_state == WebSocketState.CONNECTED:
                    tasks.append(websocket.send_text(message))
        
        if tasks:
            await asyncio.gather(*tasks)

# ์—ฐ๊ฒฐ ๊ด€๋ฆฌ์ž ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
manager = ConnectionManager()

# WebSocket ์—”๋“œํฌ์ธํŠธ
@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: str):
    await manager.connect(websocket, client_id)
    try:
        while True:
            # ๋ฉ”์‹œ์ง€ ์ˆ˜์‹ 
            data = await websocket.receive_text()
            
            # ๋ฉ”์‹œ์ง€ ํŒŒ์‹ฑ
            try:
                message_data = json.loads(data)
                message_type = message_data.get("type")
                
                # ๋ฉ”์‹œ์ง€ ์œ ํ˜•์— ๋”ฐ๋ฅธ ์ฒ˜๋ฆฌ
                if message_type == "chat":
                    # ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ
                    formatted_message = json.dumps({
                        "type": "chat",
                        "sender": client_id,
                        "content": message_data.get("content", ""),
                        "timestamp": asyncio.get_event_loop().time()
                    })
                    await manager.broadcast(formatted_message)
                    
                elif message_type == "private":
                    # ๊ฐœ์ธ ๋ฉ”์‹œ์ง€
                    target_id = message_data.get("target")
                    if target_id:
                        formatted_message = json.dumps({
                            "type": "private",
                            "sender": client_id,
                            "content": message_data.get("content", ""),
                            "timestamp": asyncio.get_event_loop().time()
                        })
                        success = await manager.send_personal_message(formatted_message, target_id)
                        # ์ „์†ก ๊ฒฐ๊ณผ ์•Œ๋ฆผ
                        await websocket.send_text(json.dumps({
                            "type": "status",
                            "status": "delivered" if success else "failed",
                            "target": target_id
                        }))
                
            except json.JSONDecodeError:
                await websocket.send_text(json.dumps({
                    "type": "error", 
                    "message": "์ž˜๋ชป๋œ JSON ํ˜•์‹"
                }))
                
    except WebSocketDisconnect:
        await manager.disconnect(client_id)
        # ์—ฐ๊ฒฐ ํ•ด์ œ ์•Œ๋ฆผ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ
        await manager.broadcast(json.dumps({
            "type": "system",
            "content": f"ํด๋ผ์ด์–ธํŠธ {client_id}๊ฐ€ ๋‚˜๊ฐ”์Šต๋‹ˆ๋‹ค"
        }))

# ์ผ๋ฐ˜ HTTP ์—”๋“œํฌ์ธํŠธ
@app.get("/")
async def get_root():
    return {"message": "WebSocket ์„œ๋ฒ„๊ฐ€ ์‹คํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค. '/ws/{client_id}'๋กœ ์—ฐ๊ฒฐํ•˜์„ธ์š”."}

# ์‹คํ–‰ ์Šคํฌ๋ฆฝํŠธ
if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

โœ… ํŠน์ง•:

  • FastAPI์™€ WebSocket ํ†ตํ•ฉ
  • ํด๋ผ์ด์–ธํŠธ ID๋ณ„ ์—ฐ๊ฒฐ ๊ด€๋ฆฌ
  • ๋ฉ”์‹œ์ง€ ํƒ€์ž…์— ๋”ฐ๋ฅธ ๋ผ์šฐํŒ…
  • ๊ฐœ์ธ ๋ฉ”์‹œ์ง€ ๋ฐ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ์ง€์›
  • ๋™์‹œ์„ฑ ์•ˆ์ „ํ•œ ์—ฐ๊ฒฐ ๊ด€๋ฆฌ (Lock ์‚ฌ์šฉ)
  • CORS ๋ฏธ๋“ค์›จ์–ด ์„ค์ •
  • ๊ตฌ์กฐํ™”๋œ ๋กœ๊น…
  • Starlette WebSocket ์ƒํƒœ ํ™•์ธ

8๏ธโƒฃ ์‹ค์‹œ๊ฐ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

# ์‹ค์‹œ๊ฐ„ ์ฃผ์‹ ์‹œ์„ธ ์—…๋ฐ์ดํŠธ ์‹œ์Šคํ…œ
import asyncio
import json
import random
import logging
import websockets
from datetime import datetime
from typing import Dict, List, Set, Any

# ๋กœ๊น… ์„ค์ •
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class StockTickerServer:
    def __init__(self):
        # ํด๋ผ์ด์–ธํŠธ ๊ด€๋ฆฌ 
        self.clients: Set[websockets.WebSocketServerProtocol] = set()
        # ํด๋ผ์ด์–ธํŠธ ๊ตฌ๋… ์ •๋ณด
        self.subscriptions: Dict[str, Set[websockets.WebSocketServerProtocol]] = {}
        # ์ฃผ์‹ ์‹œ์„ธ ๋ฐ์ดํ„ฐ
        self.stock_data: Dict[str, Dict[str, Any]] = {
            "AAPL": {"price": 150.0, "change": 0.0, "volume": 0},
            "MSFT": {"price": 250.0, "change": 0.0, "volume": 0},
            "GOOGL": {"price": 2800.0, "change": 0.0, "volume": 0},
            "AMZN": {"price": 3300.0, "change": 0.0, "volume": 0},
            "META": {"price": 330.0, "change": 0.0, "volume": 0},
        }
        
    async def register(self, websocket: websockets.WebSocketServerProtocol):
        """์ƒˆ ํด๋ผ์ด์–ธํŠธ ๋“ฑ๋ก"""
        self.clients.add(websocket)
        logger.info(f"ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ: {len(self.clients)}๊ฐœ ์—ฐ๊ฒฐ ํ™œ์„ฑํ™”")
        
    async def unregister(self, websocket: websockets.WebSocketServerProtocol):
        """ํด๋ผ์ด์–ธํŠธ ๋“ฑ๋ก ํ•ด์ œ"""
        self.clients.discard(websocket)
        
        # ๊ตฌ๋…์—์„œ๋„ ์ œ๊ฑฐ
        for symbol in list(self.subscriptions.keys()):
            if websocket in self.subscriptions[symbol]:
                self.subscriptions[symbol].discard(websocket)
                # ๋น„์–ด์žˆ๋Š” ๊ตฌ๋… ์ •๋ฆฌ
                if not self.subscriptions[symbol]:
                    del self.subscriptions[symbol]
                    
        logger.info(f"ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ ํ•ด์ œ: {len(self.clients)}๊ฐœ ์—ฐ๊ฒฐ ํ™œ์„ฑํ™”")
        
    async def process_message(self, websocket: websockets.WebSocketServerProtocol, message: str):
        """ํด๋ผ์ด์–ธํŠธ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ"""
        try:
            data = json.loads(message)
            action = data.get("action")
            
            if action == "subscribe":
                # ์ฃผ์‹ ๊ตฌ๋…
                symbols = data.get("symbols", [])
                for symbol in symbols:
                    if symbol in self.stock_data:
                        if symbol not in self.subscriptions:
                            self.subscriptions[symbol] = set()
                        self.subscriptions[symbol].add(websocket)
                        
                        # ํ˜„์žฌ ์‹œ์„ธ ์ฆ‰์‹œ ์ „์†ก
                        await websocket.send(json.dumps({
                            "type": "stock_update",
                            "symbol": symbol,
                            "data": self.stock_data[symbol],
                            "timestamp": datetime.now().isoformat()
                        }))
                
                await websocket.send(json.dumps({
                    "type": "subscription_success",
                    "symbols": symbols
                }))
                        
            elif action == "unsubscribe":
                # ๊ตฌ๋… ํ•ด์ œ
                symbols = data.get("symbols", [])
                for symbol in symbols:
                    if symbol in self.subscriptions and websocket in self.subscriptions[symbol]:
                        self.subscriptions[symbol].discard(websocket)
                        
                await websocket.send(json.dumps({
                    "type": "unsubscription_success",
                    "symbols": symbols
                }))
                
            elif action == "get_available_stocks":
                # ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ฃผ์‹ ๋ชฉ๋ก ์š”์ฒญ
                await websocket.send(json.dumps({
                    "type": "available_stocks",
                    "symbols": list(self.stock_data.keys())
                }))
                
        except json.JSONDecodeError:
            await websocket.send(json.dumps({
                "type": "error", 
                "message": "์ž˜๋ชป๋œ JSON ํ˜•์‹"
            }))
        except Exception as e:
            logger.error(f"๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {str(e)}")
            await websocket.send(json.dumps({
                "type": "error", 
                "message": f"์„œ๋ฒ„ ์˜ค๋ฅ˜: {str(e)}"
            }))
    
    async def handle_client(self, websocket: websockets.WebSocketServerProtocol):
        """ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ ์ฒ˜๋ฆฌ"""
        await self.register(websocket)
        try:
            async for message in websocket:
                await self.process_message(websocket, message)
        except websockets.exceptions.ConnectionClosedError:
            pass
        finally:
            await self.unregister(websocket)
    
    async def update_stocks(self):
        """์ฃผ์‹ ์‹œ์„ธ ์—…๋ฐ์ดํŠธ ์ƒ์„ฑ"""
        while True:
            # ๋ชจ๋“  ์ฃผ์‹ ์‹œ์„ธ ์—…๋ฐ์ดํŠธ
            for symbol in self.stock_data:
                # ๋žœ๋ค ๊ฐ€๊ฒฉ ๋ณ€๋™
                change_percent = (random.random() - 0.5) * 2.0  # -1.0% ~ +1.0%
                price = self.stock_data[symbol]["price"]
                change = price * change_percent / 100.0
                
                # ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ
                self.stock_data[symbol] = {
                    "price": round(price + change, 2),
                    "change": round(change, 2),
                    "change_percent": round(change_percent, 2),
                    "volume": random.randint(100, 10000),
                    "last_update": datetime.now().isoformat()
                }
                
                # ํ•ด๋‹น ์ฃผ์‹์„ ๊ตฌ๋…ํ•œ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์—…๋ฐ์ดํŠธ ์ „์†ก
                if symbol in self.subscriptions:
                    update_message = json.dumps({
                        "type": "stock_update",
                        "symbol": symbol,
                        "data": self.stock_data[symbol]
                    })
                    
                    for client in self.subscriptions[symbol].copy():
                        try:
                            await client.send(update_message)
                        except websockets.exceptions.ConnectionClosed:
                            # ๋‹ซํžŒ ์—ฐ๊ฒฐ ์ฒ˜๋ฆฌ๋Š” handle_client์—์„œ ์ˆ˜ํ–‰
                            pass
            
            # ์—…๋ฐ์ดํŠธ ์ฃผ๊ธฐ (1์ดˆ)
            await asyncio.sleep(1)
    
    async def start_server(self, host: str = 'localhost', port: int = 8765):
        """์„œ๋ฒ„ ์‹œ์ž‘"""
        # ์ฃผ์‹ ์—…๋ฐ์ดํŠธ ํƒœ์Šคํฌ ์‹œ์ž‘
        asyncio.create_task(self.update_stocks())
        
        # WebSocket ์„œ๋ฒ„ ์‹œ์ž‘
        server = await websockets.serve(
            self.handle_client, 
            host, 
            port,
            ping_interval=20,
            ping_timeout=60
        )
        
        logger.info(f"์ฃผ์‹ ์‹œ์„ธ ์„œ๋ฒ„ ์‹œ์ž‘: {host}:{port}")
        return server

# ์„œ๋ฒ„ ์‹คํ–‰ ์ฝ”๋“œ
async def main():
    server = StockTickerServer()
    await server.start_server()
    await asyncio.Future()  # ๋ฌดํ•œ ์‹คํ–‰

if __name__ == "__main__":
    asyncio.run(main())

โœ… ํŠน์ง•:

  • ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆฌ๋ฐ
  • ๊ตฌ๋… ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜
  • ํด๋ผ์ด์–ธํŠธ๋ณ„ ๊ด€์‹ฌ ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง
  • ์ฃผ๊ธฐ์  ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ
  • ์—ฐ๊ฒฐ ์ƒํƒœ ๋ชจ๋‹ˆํ„ฐ๋ง
  • ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐ ๋ณต๊ตฌ
  • ํƒ€์ž… ํžŒํŒ…
  • ๋กœ๊น… ์‹œ์Šคํ…œ
  • ํ•‘/ํ ๋ฉ”์ปค๋‹ˆ์ฆ˜์œผ๋กœ ์—ฐ๊ฒฐ ์œ ์ง€

์ฃผ์š” ํŒ

โœ… ๋ชจ๋ฒ” ์‚ฌ๋ก€:

  • ์—ฐ๊ฒฐ ์ƒํƒœ ๊ด€๋ฆฌ
  • ์žฌ์—ฐ๊ฒฐ ๋กœ์ง ๊ตฌํ˜„
  • ๋ฉ”์‹œ์ง€ ์ง๋ ฌํ™”
  • ์—๋Ÿฌ ์ฒ˜๋ฆฌ
  • ๋ณด์•ˆ ๊ณ ๋ ค
  • ์„ฑ๋Šฅ ์ตœ์ ํ™”
  • ๋กœ๊น… ๊ตฌํ˜„
  • ํ…Œ์ŠคํŠธ ์ž‘์„ฑ
  • ๊ตฌ๋… ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜
  • ๋ฐฑํ”„๋ ˆ์…” ์ฒ˜๋ฆฌ
  • ๋น„๋™๊ธฐ I/O์™€ ๋™์‹œ์„ฑ ํŒจํ„ด ํ™œ์šฉ
  • ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ๋””๋ฒ„๊น… ์‹œ์Šคํ…œ ๊ตฌ์ถ•
  • ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ๊ตฌ์กฐ ์„ค๊ณ„
  • ๋ฉ”์‹œ์ง€ ๋ฒ„์ „ ๊ด€๋ฆฌ
  • ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ œ๊ณต
โš ๏ธ **GitHub.com Fallback** โš ๏ธ