Webチャットで最低限必要な要素を実装する
前回に続きオライリーのGo言語によるWebアプリケーション開発勉強記です。
今回はチャットルームのモデル化について。
基本的な仕様
全ユーザーは接続と同時に全体に公開されたチャットルームに入室しそこで会話を行います。
モデル化と実装
チャットの要素として下記の2つをGolang上で実装します。
- チャットルーム
- クライアント
オブジェクト指向系の言語であればこれをクラスとして定義すると思いますが、Golangでは構造体とそのメソッドとして表現します。
クライアント
type client struct { socket *websocket.Conn // クライアントのWebSocket send chan []byte // メッセージが送られるチャネル room *room // 参加しているチャットルーム }
クライアント構造体はブラウザとの通信を担うWebSocketへの参照、受信したメッセージのバッファ、チャットルームへの参照を持ちます。
チャットルーム
type room struct { forward chan []byte // 他のクライアントに転送するためのメッセージを保持するチャネル join chan *client // チャットルームに参加を試みるクライアントのためのチャネル leave chan *client // チャットルームからの退室を試みるクライアントのためのチャネル clients map[*client]bool // 在室している全てのクライアントが保持される }
チャットルーム構造体はクライアントへ送信を行うためのチャネル、チャットルームへの参加・退室用チャネル、チャットルーム参加者のクライアントへの参照一覧を持ちます。
ざっくりした動作
ブラウザがWebアプリケーションにアクセスするとWebSocketによる接続を確立し、そこを通じてデータ(=会話内容)をやり取りします。旧き良きGCIチャットのようなリロード方式ではなく、送信と同時に受信側にもjQueryでリアルタイムに会話内容が表示されます。
見ての通りアプリケーションがステート(つまり会話内容やユーザーの接続状態)を持つ作りなので、例えばこのWebアプリが動くサーバーを2台用意してロードバランサー配下にしたとするとどちらのサーバーに振り分けされたかによって会話できる相手が異なるという事態になってしまいます。これを解決するには状態を外に出してWebサーバーから参照するような形にする必要がありますが、少なくとも今はこの作りのまま進めます。
モデルの振る舞い
接続確立しチャットに参加した時
WebSocketの接続が確立され room.join
チャネルにクライアント構造体 client
を送信するとそれが room.clients
に追加され送受信の準備が整います(使われ方は後述)。
メッセージを送信した時
room.forward
チャネルにメッセージを送信すると room.clients
にあるクライアントの一覧全員に対し送信を行います。送信先は各クライアントの client.send
チャネルです。
メッセージを受信した時
client.send
でメッセージを受信するとWebSocketからブラウザ側にその内容が送信され、ブラウザ上に表示されます。つまり自分が送信したメッセージも一度サーバーを経由して自分に送信されることになります。
実用アプリケーションではレスポンス向上と負荷対策のため、送信時にはその内容をJavaScriptが自分で画面に反映させ、サーバー側も自分自身には送信しないような実装をすることになるかと思います。
チャットから退室する時
room.leave
チャネルにクライアント構造体を送信すると room.clients
から構造体が削除されメッセージ送信の対象から外れます。また退室する client.send
チャネルを close()
しそれ以上メッセージが送信されないようにします。
※長くなったのでチャネルについてはまた次回以降に書きます。