6. WebSocketを使った一対多の通信
HelloEndpointのサンプルは、サーバーとクライアントの一対一の通信の例だった。そこで今度は、1つのサーバーに対して複数のクライアントが接続して一対多の通信をするチャットアプリケーションをWebSocketを使って作ってみよう。すなわち、サーバー側のエンドポイントは複数のクライアントからの接続を同時に受け付け、クライアントからメッセージを受け取った場合にはそれを接続中のすべてのクライアントに対して転送する。
まず、送信側となるクライアントのJavaScriptを考えよう。せっかくならば発言したユーザ名などの情報も送りたいので、メッセージ送信にはJSONを利用してみる。WebSocketではバイナリデータを送ることも可能だが、単純なメッセージであればJSONで事足りるだろう。今回は次のようなJSONデータを使うことにする。type属性でメッセージの種類を指定し、data属性でメッセージデータを格納したオブジェクトを渡す。メッセージデータ部分のname属性はユーザ名を、text属性はテキストメッセージを表す。
■クライアント→サーバー ログイン時: { type:login, data:{ name:xxx } } ログアウト時: { type:logout, data:{ name:xxx } } メッセージ送信時: { type:message, data:{ name:xxx, text:xxx } } ■サーバー→クライアント メッセージ送信時: { type:message, data:{ text:xxx } }
接続がオープンしたときには、次のようにしてログインメッセージを送信する。
webSocket.onopen = function(event) { var name = document.getElementById("name").value; // ユーザ名 var message = { type: "login", data: { name: name } }; webSocket.send(window.JSON.stringify(message)); }
接続をクローズするときには、次のようにログアウトメッセージを送信してからclose()メソッドを呼び出す。
if (webSocket.readyState == webSocket.OPEN) { var name = document.getElementById("name").value; // ユーザ名 var message = { type: "logout", data: { name: name } }; webSocket.send(window.JSON.stringify(message)); webSocket.close(); }
そしてテキストメッセージを投稿するときには、ユーザ名とメッセージの内容を送信する。送信するメッセージの内容がJSON形式になっただけで、基本的にはテキストメッセージを送る方法と違いはない。
if (webSocket.readyState == webSocket.OPEN) { var name = document.getElementById("name").value; // ユーザ名 var inputmessage = document.getElementById('inputmessage').value; // メッセージ var message = { type: "message", data: { name: name, text: inputmessage } }; webSocket.send(window.JSON.stringify(message)); document.getElementById('inputmessage').value = ""; }
サーバーから送られてくるメッセージもJSON形式なので、そこからメッセージテキスト部分を取り出して画面のコンテンツに追加する。
webSocket.onmessage = function(event) { var receive = JSON.parse(event.data); var message = receive.data.text; var msgBox = document.getElementById("message"); var currentText = msgBox.innerHTML; msgBox.innerHTML = "<div>" + message + "</div>" + currentText; }
さて、サーバー側では受け取ったJSON形式のデータを解析して、type属性の値に応じて処理を変えなければならない。JavaプログラムでJSONデータを扱うために、今回はLibertyプロファイルに付属するJSONライブラリ(com.ibm.json.javaパッケージ)を利用する。このライブラリは「com.ibm.websphere.appserver.api.json_1.0.1.jar」というファイルに納められている。プロジェクトのプロパティ設定で、ビルド・パスに「WebSphere Application Server Liberty Profile」を追加しておけば使うことができる。もちろん、この部分はJSR 353として標準化されている「Java API for JSON Processing(javax.json)」を使うように置き換えてもよい。
com.ibm.json.javaでは、JSONデータをJSONObjectというクラスとして定義している。このクラスはMapクラスを継承している。従って、基本的なデータの出し入れはMapクラスと同じ方法でできると考えてよい。JSON形式のテキストデータからJSONObjectオブジェクトに変換するには、JSONObject.parse()メソッドを使用する。次の例は、受け取ったJSONデータから各属性の値を取り出し、type属性に応じてそれぞれ異なるメッセージをクライアントに送信する例である。メッセージの送信は後述するsendMessage()メソッドで行う。
@OnMessage public void receiveMessage(String msg, Session session) throws IOException { JSONObject json = JSONObject.parse(msg); String type = (String)json.get("type"); JSONObject data = (JSONObject)json.get("data"); String name = (String)data.get("name"); switch (type) { case "login": sendMessage(name + "さんが入室しました。", session); break; case "logout": sendMessage(name + "さんが退室しました。", session); break; case "message": String text = (String)data.get("text"); sendMessage(name + ": " + text, session); break; default: break; } }
接続が確立しているすべてのセッションに同時にメッセージを送信するには、それらのセッションの情報を知る必要がある。この情報は、1つのSessionオブジェクトから簡単に取得できるようになっている。従ってメッセージを受け取った際には、その送信元となったSessionオブジェクトを取得し、そこから他のセッションのリストを取り出してそれぞれに対して受け取った内容を転送すればいいわけだ。具体的には、次のようにgetOpenSessions()メソッドを呼び出してSessionオブジェクトが格納されたSetオブジェクトを取得し、それぞれにsendText()メソッドを実行するようなコードになる。
private void sendMessage(String msg, Session currentSession) { // 返すメッセージのJSON形式 // { type:message, data:{ message: xxxx } } JSONObject data = new JSONObject(); data.put("text", msg); JSONObject response = new JSONObject(); response.put("type", "message"); response.put("data", data); System.out.println("送信: " + response.toString()); Set<Session> sessions = currentSession.getOpenSessions(); for(Session session: sessions) { try { session.getBasicRemote().sendText(response.serialize()); } catch (IOException e) { e.printStackTrace(); } } }
以上のことを踏まえて、チャットアプリケーションを実現するクライアント側とサーバー側のプログラムは、添付サンプルコードの「chat.html」および「ChatEndpoint.java」のようになった。
このプロジェクトを実行してWebブラウザからアクセスすると、最初に図6.9のように表示される。名前を入力して[入室]ボタンを押せば、WebSocketによる通信が開始されて、図6.10のようにサーバーから受け取ったメッセージが表示される。複数のWebブラウザで同時に接続すれば、図6.11のようにチャットを楽しむことができる。