WebSocketで個別送信をする
2015.11.02
経緯
社内で使用する簡易な採点システムを作成するのに、WebSocketを使用しました。
その中で、特定のクライアントにだけメッセージを送信したい、と思ったのですが、意外とWebSocketを
紹介しているサイトで例を見かけなかったので、紹介してみようと思います。
なお、本記事では、サーバ側の処理(コード)を紹介します。
クライアント側については、必要な機能には触れていますが、処理(コード)までは言及しません。
採点システム
今回作成した採点システムについてです。
概要
プレゼンのリアルタイム採点システムです。
採点画面・点数表示画面があり、システムを利用する際にサーバに接続を行います。
スクリーンにプレゼン資料と点数表示画面を表示します。採点者は手元の端末に採点画面を表示します。
採点画面には、採点ボタンとスクリーンに表示されている点数表示画面と同じ点数が表示されます。
採点ボタンが1回押される度に、点数が1点加算されます。採点者は、決められた持ち点まで好きな
タイミングで採点する事が出来、採点数が持ち点に達すると採点ボタンは押下出来なくなります。
また、後で振り返る為に、誰が何時採点したかをログに記録します。
仕様
1.クライアント接続時
- ログイン画面にて、IDを入力する。
- IDをクエリ文字列に設定し、サーバに接続要求を行う。
- サーバにて、クエリ文字列で受け取ったIDをキーに、セッションをマップに格納する。
- IDにより、採点画面又は点数表示画面を表示する。
2.メッセージ送信時
- 採点画面:採点ボタン押下時、IDと採点数をサーバに送信する。
- サーバ:マップから保持しているセッションを順に取得する。
- サーバ:得点を各クライアントに送信する。
点数表示画面・採点画面:点数の表示を更新する。 - サーバ:セッションを保持しているマップから、採点画面より受信したIDのセッションを取得する。
- サーバ:採点画面より受信した採点数に1加算した値を新しい採点数として、採点画面に送信する。
採点画面:サーバから受信した採点数が持ち点に達していた場合、採点ボタンを押下不可とする。 - サーバ:採点画面より受信したIDをログに出力する。
開発環境
- Java 1.8.0_40
- Tomcat 8.0.20
ちなみに、JavaEEのWebSocket APIを使用していますが、その他の実装でも流用可能かと思います。
実装方法
一般的に紹介されている一斉送信のみを行う場合の実装方法と、今回作成した個別送信も行う場合の
実装方法を紹介します。
一斉送信
色々なサイトで紹介されている、接続しているクライアント全てにメッセージを送信するパターンです。
ここでの動きは、以下の通りです。
- クライアントはサーバにメッセージを送信する。
- サーバは接続している全てのクライアントに受信したメッセージを送信する。
では、実際のコードを見てみます。
まずは、クライアントからの接続リクエストに応答するonOpenイベントハンドラの処理です。
private static List sessionList = new ArrayList(); @OnOpen public void onOpen(Session session) { sessionList.add(session); }
4行目のonOpen()の引数として受け取ったセッションを、5行目でリストに格納しています。
続いて、クライアントからのメッセージに応答するonMessageイベントハンドラの処理です。
@OnMessage public void onMessage(String message) throws IOException { for (Session session : sessionList) { session.getBasicRemote().sendText(message); } }
3行目でリストからセッションを1つづつ取り出し、4行目でセッションを使用してメッセージを送信しています。
個別通信
つづいて、本題の特定のクライアントに、メッセージを送る方法です。
一斉送信のみの場合、リストでセッションを保持していましたが、マップにてセッションを保持する様にします。
マップに保存する際のキーとして、今回はログイン時に入力したIDを使用します。WSプロトコルが持つ
Sec-WebSocket-Keyなどもキーの候補となりますが、システムにて自動生成された値ではログに出力し後で
確認する、という要件を満たせない為に今回は使用していません。
では、コードを見てみましょう。
まずは、クライアントからの接続リクエストに応答するonOpenイベントハンドラの処理です。
private static Map sessionMap = new HashMap(); @OnOpen public void onOpen(Session session) { sessionMap.put(session.getQueryString(),session); }
4行目でクエリ文字列を取得し、5行目でキーとしてonOpen()の引数として受け取ったセッションを、マップに
格納しています。
続いて、クライアントからのメッセージに応答するonMessageイベントハンドラの処理です。
採点画面から送信されるメッセージは
“id,採点数”
の形式とします。
private static Map sessionMap = new HashMap(); private int score = 0; @OnMessage public void onMessage(String message) throws IOException { String[] splitMessage = message.split(","); score++; for (Session session : sessionMap.values()) { session.getBasicRemote().sendText(score); } Session sessionSender = sessionMap.get(splitMessage[0]); scoreMap.put(sessionSender.getQueryString(), splitMessage[1]); sessionSender.getBasicRemote().sendText(Integer.parseInt(splitMessage[1]) + 1); }
9~11行目で、マップから順にセッションを取り出し、全てのクライアントに得点を送信しています。
その後、13~15行目で、マップからメッセージを送信した採点画面のセッションを取得し、受信した採点数に
1点加算した値を送信しています。
公開
記事の主題から少し外れますが、外部に実際に公開をする際の注意事項です。
APサーバ
APサーバにはTomcat8.0を使用しました。Tomcat7からWebSocketに対応しています。
HTTPサーバ
HTTPサーバには、Nginxを使用しました。WebSocketはコネクションの確立にはHTTPプロトコルを
使用し、その後WSプロトコルにプロトコルを切り替えて通信を行います。この際、HTTP1.1のUpgradeヘッダを
使用しています。Nginxにて、Upgradeヘッダに対応したのは1.3.13の為、これ以降のバージョンが必要と
なります。Linuxの一部のディストリビューションでは、yumに古いバージョンのリポジトリが設定されている
場合があるようです。最新のバージョンのリポジトリを設定して、インストールする様に気を付けてください。
まとめ
簡単にですが、WebSocketにて特定の相手にメッセージを送る方法を紹介させていただきました。
手法としては、セッションの管理をリストからマップに変更するという、非常にシンプルな方法を取っています。
後は作成するシステムの要件に合わせて、マップのキーに何を使用するかを使い分けていけば良いかと思います。
CONTACT
お問い合わせ
あなたの「想い」に挑戦します。
どうぞお気軽にお問い合わせください。
受付時間:平日9:00〜18:00 日・祝日・弊社指定休業日は除く