目的


WebSocketってなにかをこの前勉強した。

WebSocketのキャッチアップやワレィ!

で、それをPythonとNext.jsを使って実装してみた。

でも、そのコードで実際に何をやってるかは深く理解できていないので、このメモで深く理解していこうと思う。ww

今回理解するコードはこちら。

"use client";

import { useEffect, useState, useRef } from "react";

export default function Home() {
  const [messages, setMessages] = useState([]);
  const [isConnected, setIsConnected] = useState(false);
  const [input, setInput] = useState(""); // Added input state for sending messages
  const socketRef = useRef(null);

  useEffect(() => {
    const connectWebSocket = () => {
      socketRef.current = new WebSocket("ws://localhost:8000/ws");

      socketRef.current.onopen = () => {
        console.log("WebSocket connection established");
        setIsConnected(true);
      };

      socketRef.current.onmessage = (event) => {
        console.log("Message from server:", event.data);
        setMessages((prev) => [...prev, event.data]);
      };

      socketRef.current.onerror = (error) => {
        console.error("WebSocket error:", error);
      };

      socketRef.current.onclose = () => {
        console.log("WebSocketサーバとのコネクションをクローズしました");
        setIsConnected(false);
      };
    };

    connectWebSocket();

    return () => {
      if (socketRef.current) {
        socketRef.current.close();
      }
    };
  }, []);

  const sendMessage = () => {
    if (socketRef.current && isConnected) {
      socketRef.current.send(input);
      console.log(
        "WebSocketサーバにメッセージを送信しました。 message -> ",
        input
      );
      setInput("");
    } else {
      console.error("WebSocketサーバとの通信を確立できません");
    }
  };

  const closeConnection = () => {
    if (socketRef.current) {
      socketRef.current.close();
      console.log("WebSocketサーバとのコネクションをクローズします");
      setIsConnected(false);
    }
  };

  return (
    <div>
      <h1 className="text-3xl font-bold p-4">WebSocket Messages</h1>
      <div className="p-4">
        <input
          type="text"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Enter a message"
          className="border rounded p-2 mr-2"
        />
        <button
          onClick={sendMessage}
          className="bg-blue-500 text-white p-2 rounded"
        >
          Send
        </button>
        <button
          onClick={closeConnection}
          className="bg-red-500 text-white p-2 rounded ml-2"
        >
          Close Connection
        </button>
      </div>
      <p className="ml-4">
        Status: {isConnected ? "Connected" : "Disconnected, reconnecting..."}
      </p>
      <ul className="ml-4">
        {messages.map((msg, index) => (
          <li key={index}>{msg}</li>
        ))}
      </ul>
    </div>
  );
}

from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
import asyncio
import logging
from starlette.websockets import WebSocketState

app = FastAPI()

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("websocket_server")

@app.get("/")
async def get():
    return HTMLResponse("<h1>WebSocket Server is running</h1>")

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    logger.info("クライアントが接続されました")

    try:
        send_task = asyncio.create_task(send_messages_periodically(websocket))

        while True:
            data = await websocket.receive_text()
            logger.info(f"Received message from client: {data}")

    except WebSocketDisconnect:
        logger.warning("クライアントが切断されました")
    except Exception as e:
        logger.error(f"エラーが発生しました: {e}")
    finally:
        send_task.cancel()
        await websocket.close()
        logger.info("WebSocket接続が終了しました")

async def send_messages_periodically(websocket: WebSocket):
    """Periodically send messages to the client"""
    for i in range(100):
        if websocket.application_state != WebSocketState.CONNECTED:
            break
        await websocket.send_text(f"test text No.{i}")
        await asyncio.sleep(0.05)

わいのメモ


Ptyhon側のWebSocket通信の実装について

from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
import asyncio
import logging
from starlette.websockets import WebSocketState

app = FastAPI()

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("websocket_server")

@app.get("/")
async def get():
    return HTMLResponse("<h1>WebSocket Server is running</h1>")

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    logger.info("クライアントが接続されました")

    try:
        send_task = asyncio.create_task(send_messages_periodically(websocket))

        while True:
            data = await websocket.receive_text()
            logger.info(f"Received message from client: {data}")

    except WebSocketDisconnect:
        logger.warning("クライアントが切断されました")
    except Exception as e:
        logger.error(f"エラーが発生しました: {e}")
    finally:
        send_task.cancel()
        await websocket.close()
        logger.info("WebSocket接続が終了しました")

async def send_messages_periodically(websocket: WebSocket):
    """Periodically send messages to the client"""
    for i in range(100):
        if websocket.application_state != WebSocketState.CONNECTED:
            break
        await websocket.send_text(f"test text No.{i}")
        await asyncio.sleep(0.05)

<aside> 💡

まずはサーバ側の実装から理解していく。