kivantium活動日記

プログラムを使っていろいろやります

FlaskでWebSocketを使う

WebSocketを使うとリアルタイム性の高いWebアプリケーションを作ることができます。今回はFlaskでWebSocketを使うライブラリのFlask-SocketIOを使って、複数のブラウザでテキストボックスを同期するアプリを作成します。

インストール

Flask-SocketIOはpipでインストールできます。

pip install flask-socketio

サンプル

Google Documentの同時編集みたいなイメージで、以下の機能を持つアプリを作成します。

  • 接続中のユーザー数が表示される
  • 接続中のユーザー全員でテキストボックスに入力した内容が同期される

DOMの操作はjQueryで行うことにします。作成したソースコードは以下の通りです。

server.py

from flask import Flask, render_template, request
from flask_socketio import SocketIO, send, emit

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'

# cors_allowed_originは本来適切に設定するべき
socketio = SocketIO(app, cors_allowed_origins='*')

# ユーザー数
user_count = 0
# 現在のテキスト
text = ""

@app.route('/')
def index():
    return render_template('index.html')

# ユーザーが新しく接続すると実行
@socketio.on('connect')
def connect(auth):
    global user_count, text
    user_count += 1
    # 接続者数の更新(全員向け)
    emit('count_update', {'user_count': user_count}, broadcast=True)
    # テキストエリアの更新
    emit('text_update', {'text': text})


# ユーザーの接続が切断すると実行
@socketio.on('disconnect')
def disconnect():
    global user_count
    user_count -= 1
    # 接続者数の更新(全員向け)
    emit('count_update', {'user_count': user_count}, broadcast=True)


# テキストエリアが変更されたときに実行
@socketio.on('text_update_request')
def text_update_request(json):
    global text
    text = json["text"]
    # 変更をリクエストした人以外に向けて送信する
    # 全員向けに送信すると入力の途中でテキストエリアが変更されて日本語入力がうまくできない
    emit('text_update', {'text': text}, broadcast=True, include_self=False)


if __name__ == '__main__':
    # 本番環境ではeventletやgeventを使うらしいが簡単のためデフォルトの開発用サーバーを使う
    socketio.run(app, debug=True)

templates/index.html

<html>
  <head>
    <title>FlaskでWebSocket</title>
  </head>
  <body>
    <p>現在の接続者数<span id="user_count"></span></p>
    <textarea id="text" name="text" rows="10" cols="60"></textarea>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js" integrity="sha512-q/dWJ3kcmjBLU4Qc47E4A9kTB4m3wuTY7vkFJDTZKjTs8jhyGQnaUrxa0Ytd0ssMZhbNua9hE+E7Qv1j+DyZwA==" crossorigin="anonymous"></script>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>

    <script type="text/javascript" charset="utf-8">
      var socket = io();

      // 接続者数の更新
      socket.on('count_update', function(msg) {
        $('#user_count').html(msg.user_count);
      });
      
      // テキストエリアの更新
      socket.on('text_update', function(msg) {
        $('#text').val(msg.text);
      });
      
      // テキストエリアが変更されると呼び出される
      $('#text').on('change keyup input', function() {
        socket.emit('text_update_request', {text: $(this).val()});
      });
    </script>
  </body>
</html>

グローバル変数で接続者数をカウントするのはスレッドセーフではないのですが、GitHubでの作者コメントを読む限りでは通常このコードで問題ないようです。

python server.pyで実行して、ブラウザの画面を複数開くと同期している様子を見ることができます。 入力や削除の様子まで同期されていることが分かると思います。

f:id:kivantium:20211018102437g:plain

以上です。

広告コーナー