WebSocketについて

2024/10/10 倉科空明

皆さん、こんにちは!とうとう10回目のブログを書くことになった倉科空明です!この頃季節の変わり目で体調崩してしまったのですが皆さんはどのようにお過ごしでしょうか? 寒暖差が激しい季節となっているのでお体にはお気をつけください。さて、今回のブログのテーマは「ITスキル」についてです。そこで、私が以前学んだことがあるWebSocketについて復習も兼ねてまとめていきたいと思います。

WebSocketとは

    WebSocketはクライアントとサーバー間で一度接続されると接続を維持し続けデータを即座に双方向に交換することができるリアルタイムのウェブアプリケーションに特化した通信プロトコルだそうです。 WebSocket通信はクライアントが「Upgrade: websocket」ヘッダーを含むHTTPリクエストをサーバーに送信しサーバーが承認することでHTTPからWebSocketに切り替わり通信が開始されるそうです。

HTTPとWebSocket

WebSocketはウェブアプリケーションに特化した通信プロトコルの中の一つでありほかにもHTTPなどの通信プロトコルがあります。 左の表が主なHTTPとWebSocketの違いで、簡単にまとめるとHTTPはリクエストとレスポンスモデルに基づいて各通信ごとに接続が閉じられる通信プロトコルでWebSocketは一度接続を確立すると持続的な双方向通信が可能でリアルタイムのデータ送受信に適しているそうです。

WebSocketのサンプル

    以前WebSocketを学んだ時に使用したプログラムを参考にローカルサーバー内で上の写真のような簡単な掲示板的なものを作成してみました。 図[1]がディレクトリ構成で図[2]から[4]までが使用したコードの一部なのでWebSocketを少し触れてみたい方はぜひ試してみてください! こちらからダウンロードすることができるので活用してください。

実装方法

[1]

websocket               
├── localserver
│   ├── server.py #サーバーをたてる
├── server                  
│   ├── public_html             
│       ├── cgi-bin
│           └── websocket_sever.py #websocket
│       └── static
│           ├── app.js #送信部分
│           └── index.html #クライアント
├── test.txt #保存しておく場所


                                    
    [1]:上のリンクからファイルをダウンロードしていただいたら「websocket_server.py」、「server.py」の「~~~」に作成する実行環境でのパスを入力してください!
    [2]:「websocket_server.py」と「server.py」を実行してください。エラーが出なかったら「http://localhost:8000/static/index.html」で掲示板のようなものを実行できます!
    ※「ModuleNotFoundError: No module named 'websockets'」のようなエラーが出たら、「pip install websockets」などでインストールしてください
[3]
「index.html」

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Chat App</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>雑談部屋</h1>
        </div>
        <div class="chat-board" id="chatLog"></div>
        <div class="input-section">
            <input type="text" id="inputMessage" placeholder="メッセージを入力...">
            <button onclick="sendMessage()">Send</button>
        </div>
    </div>
    <script src="app.js"></script>
</body>
</html>
        
[4]
「app.js」
// チャットログとメッセージ入力フィールドの要素を取得
const chatLog = document.getElementById("chatLog");
const inputMessage = document.getElementById("inputMessage");

// WebSocketサーバーへの接続を開始
const socket = new WebSocket("ws://localhost:8084");

// WebSocket接続が開かれた時のイベントハンドラ
socket.onopen = (event) => {
    console.log("Connected to the WebSocket server");
};

// WebSocketからメッセージを受信した時のイベントハンドラ
socket.onmessage = (event) => {
    // 受信したメッセージを表示用のdiv要素に変換
    const entry = document.createElement('div');
    // 改行文字をHTMLの改行タグに置き換えて表示
    entry.innerHTML = event.data.replace(/\n/g, '
'); // チャットログに追加 chatLog.appendChild(entry); }; // WebSocketでエラーが発生した時のイベントハンドラ socket.onerror = (error) => { console.error(`WebSocket Error: ${error}`); }; // メッセージ送信用の関数 function sendMessage() { const message = inputMessage.value; // トリムしたメッセージが空でない場合に送信 if (message.trim() !== "") { socket.send(message); console.log(message + "send") // 入力フィールドをクリア inputMessage.value = ""; } }
[2]
「websocket_sever.py」

import asyncio
import os
import websockets
import random
import string
from datetime import datetime

connected_clients = {}  # 接続されているクライアントを保持する辞書

def generate_id():
    # クライアントIDをランダムに生成する関数
    return ''.join(random.choices(string.ascii_letters + string.digits, k=8))

async def handler(websocket, path):
    # 各クライアント接続のための非同期ハンドラー関数
    client_id = generate_id()
    while client_id in connected_clients:
        client_id = generate_id()

    connected_clients[client_id] = websocket  
    # クライアントIDとWebSocket接続をマッピング

    # test.txt の絶対パスを設定
    file_path = "~~~/websocket/server/public_html/cgi-bin/test.txt"
    
    # 新しいクライアントに過去のメッセージを送信
    if os.path.exists(file_path):
        with open(file_path, "r") as f:
            past_messages = f.read()
            await websocket.send(past_messages)

    try:
        async for message in websocket:
            # クライアントからメッセージを受信
            timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            formatted_message = f"名無しさん@{client_id}{timestamp}\n{message}\n\n"
            
            # 受信したメッセージをファイルに保存
            with open("test.txt", "a") as f:
                f.write(formatted_message)
            
            # 接続されている全クライアントにメッセージを送信
            for client in connected_clients.values():
                await client.send(formatted_message)

    except:
        # 例外発生時の処理(ここでは何もしない)
        pass
    finally:
        # WebSocket接続が閉じられると、クライアントリストから削除
        del connected_clients[client_id]

# WebSocketサーバーを起動
start_server = websockets.serve(handler, "0.0.0.0", 8084)

# イベントループを開始し、サーバーを実行
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()





                                        
参考文献:
[1]https://ginga-sys.jp/blog/websocket-http/#WebSocket
[2]https://www.freshvoice.net/knowledge/word/6323/
[3]福井雄斗さんのコードを参考にさせていただきました。
[4]https://qiita.com/sand/items/fd0be5b8b0eb9bb38bbe
[5]https://qiita.com/sand/items/4b3e222dbb9315e2b0c0