LoginSignup
182

More than 5 years have passed since last update.

いまさらHTML5 (WebSocket編)

Last updated at Posted at 2014-06-18

はじめに

HTML5のWebSocketを使ってみます。

チャット

WebSocketオブジェクト生成時に接続に行きます。
パラメータは、ws://から始まる、URLです。

WebSocketオブジェクト生成後、イベントハンドラを登録します。

名称 説明
onopen 接続イベント
onclose 切断イベント
onmessage メッセージ受信イベント
onerror エラーイベント

WebSocketオブジェクトのメソッド

メソッド 説明
send メッセージ送信
close 切断
websocket01.html
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>WebSocket Sample</title>
    <script type="text/javascript" src="./jquery.js"></script>
    <script type="text/javascript">
        // 接続先URI
        var uri = "ws://" + location.host + "/sample/chat";

        // WebSocketオブジェクト
        var webSocket = null;

        // 初期処理
        function init() {
            // ボタン押下イベント設定
            $("[data-name='message']").keypress(press);
            // 接続
            open();
        }

        // 接続
        function open() {
            if (webSocket == null) {
                // WebSocket の初期化
                webSocket = new WebSocket(uri);
                // イベントハンドラの設定
                webSocket.onopen = onOpen;
                webSocket.onmessage = onMessage;
                webSocket.onclose = onClose;
                webSocket.onerror = onError;
            }
        }

        // 接続イベント
        function onOpen(event) {
            chat("接続しました。");
        }

        // メッセージ受信イベント
        function onMessage(event) {
            if (event && event.data) {
                chat(event.data);
            }
        }

        // エラーイベント
        function onError(event) {
            //chat("エラーが発生しました。");
        }

        // 切断イベント
        function onClose(event) {
            chat("切断しました。3秒後に再接続します。(" + event.code + ")");
            webSocket = null;
            setTimeout("open()", 3000);
        }

        // キー押下時
        function press(event) {
            // キーがEnterか判定
            if (event && event.which == 13) {
                // メッセージ取得
                var message = $("[data-name='message']").val();
                // 存在チェック
                if (message && webSocket) {
                    // メッセージ送信
                    webSocket.send("" + message);
                    // メッセージ初期化
                    $("[data-name='message']").val("");
                }
            }
        }

        // チャットに表示
        function chat(message) {
            // 100件まで残す
            var chats = $("[data-name='chat']").find("div");
            while (chats.length >= 100) {
                chats = chats.last().remove();
            }
            // メッセージ表示
            var msgtag = $("<div>").text(message);
            $("[data-name='chat']").prepend(msgtag);
        }

        // 初期処理登録
        $(init);
    </script>
</head>
<body>
    <input type="text" data-name="message" size="100" />
    <hr />
    <div data-name="chat"></div>
</body>
</html>

サーバ側は、受信したメッセージをオンラインのWebSocketに送信するだけの簡単なものです。

WebSocketChat.java
package sample;

import java.util.Set;

import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/chat")
public class WebSocketChat {

    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
        String id = session.getId();
        echo("(" + id + ")が入室しました。", session);
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        String id = session.getId();
        echo("(" + id + ") " + message, session);
    }

    @OnClose
    public void onClose(Session session, CloseReason reason) {
        String id = session.getId();
        echo("(" + id + ")が退出しました。", session);
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        throwable.printStackTrace();
    }

    /**
     * メッセージエコー
     * 
     * @param message メッセージ
     * @param session セッション
     */
    private void echo(String message, Session session) {
        System.out.println(message);
        // 開いているセッション取得
        Set<Session> sessions = session.getOpenSessions();
        // セッション数分ループ
        for (Session tmp : sessions) {
            try {
                // メッセージ送信
                tmp.getBasicRemote().sendText(message);
            } catch (Exception e) {
                // 例外の場合
                e.printStackTrace();
            }
        }
    }
}

JSONデータ

テキスト送信出来るので、そのままJsonデータを送受信してみます。
jquery-uidraggableを利用して、ボールを動かすようなサンプルです。
1ブラウザだけでやってると意味ありませんが、複数ブラウザでやると全部同じ動きをします。

websocket02.html
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>WebSocket Sample</title>
    <script type="text/javascript" src="./jquery.js"></script>
    <script type="text/javascript" src="./jquery-ui.js"></script>
    <script type="text/javascript">
        // 接続先URI
        var uri = "ws://" + location.host + "/sample/json";

        // WebSocketオブジェクト
        var webSocket = null;

        // ボール
        var balls = null;

        // ボールの場所
        var ballpos = { t: 0, l: 0 };

        // 初期処理
        function init() {
            // ボール生成
            ball = $("<div>").width(20).height(20)
                .css("background-color", "#f00").css("border-radius", "10px");
            // ボールの可動範囲設定
            ball.draggable({ containment: "[data-name='square']" });
            // ボールのイベント設定
            ball.on("drag", dragball);
            // ボール描写
            $("[data-name='square']").append(ball);
            // ボール移動(早さ0)
            move(0);
            // 接続
            open();
        }

        // ボール移動
        function move(spped) {
            // 各ボール移動
            ball.stop();
            ball.animate({ top: ballpos.t, left: ballpos.l }, spped);
        }

        // ボール移動イベント
        function dragball(event, ui) {
            // ボール位置設定
            ballpos.t = ui.position.top.toFixed(0);
            ballpos.l = ui.position.left.toFixed(0);
            // ボール位置設定
            var msg = JSON.stringify(ballpos);
            if (webSocket) {
                webSocket.send(msg);
            }
            $("[data-name='status']").text(msg);
        }

        // 接続
        function open() {
            if (webSocket == null) {
                // WebSocket の初期化
                webSocket = new WebSocket(uri);
                // イベントハンドラの設定
                webSocket.onopen = onOpen;
                webSocket.onmessage = onMessage;
                webSocket.onclose = onClose;
                webSocket.onerror = onError;
            }
        }

        // 接続イベント
        function onOpen(event) {
            $("[data-name='status']").text("OPEN");
        }

        // メッセージ受信イベント
        function onMessage(event) {
            if (event && event.data) {
                var pos = JSON.parse(event.data);
                ballpos = pos;
                move(5);
                $("[data-name='status']").text(event.data);
            }
        }

        // エラーイベント
        function onError(event) {
            $("[data-name='status']").text("ERROR");
        }

        // 切断イベント
        function onClose(event) {
            $("[data-name='status']").text("CLOSE");
            webSocket = null;
            setTimeout("open()", 3000);
        }

        // 初期処理登録
        $(init);
    </script>
</head>
<body>
    <div data-name="status">&nbsp;</div>
    <div data-name="square" style="width: 400px; height: 300px; background-color: gray"></div>
</body>
</html>

サーバ側は、staticで保持しているJSONデータを接続時onOpenには、接続した相手へ、
データ受信時onMessageは、データ送信者以外に配布しているだけです。
(データ形式が複雑になると、JSONデータを解析しなければならないと思います。例えば、ボールが4つになった時など)

WebSocketJson.java
package sample;

import java.util.Set;

import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/json")
public class WebSocketJson {

    private static String json = "";

    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
        if (json.length() > 0) {
            try {
                session.getAsyncRemote().sendText(json);
            } catch (Exception e) {
                // 例外の場合
                System.out.println("" + e.getMessage());
            }
        }
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        String id = session.getId();
        synchronized (json) {
            json = message;
            // 開いているセッション取得
            Set<Session> sessions = session.getOpenSessions();
            // セッション数分ループ
            for (Session tmp : sessions) {
                if (!tmp.getId().equals(id)) {
                    try {
                        // メッセージ送信
                        tmp.getAsyncRemote().sendText(json);
                    } catch (Exception e) {
                        // 例外の場合
                        System.out.println("" + e.getMessage());
                    }
                }
            }
        }
    }

    @OnClose
    public void onClose(Session session, CloseReason reason) {
        // 何もしません。
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        // 何もしません。
    }
}

ファイル

WebSocketオブジェクトは、sendメソッドでBlobオブジェクトを送信できます。
というわけで、ファイルをWebSocketで送信してみます。

inputタグのvalue属性が変更になったイベントを契機に、Fileオブジェクトを取得します。
WebSocketを接続し、Fileオブジェクトを送信します。

websocket03.html
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>WebSocket Sample</title>
    <script type="text/javascript" src="./jquery.js"></script>
    <script type="text/javascript">
        // 接続先URI
        var uri = "ws://" + location.host + "/sample/blob";

        // WebSocketオブジェクト
        var webSocket = null;

        // ファイル
        var file = null;

        // 初期処理
        function init() {
            // ファイル選択時
            $("[data-name='sendfile']").change(filechange);
        }

        // ファイル選択時
        function filechange() {
            // ファイル存在チェック
            if (file == null) {
                // ファイル取得
                var files = $(this).get(0).files;
                if (files.length > 0) {
                    file = files[0];
                    // 接続
                    open();
                }
            }
        }

        // 接続
        function open() {
            if (webSocket == null) {
                // WebSocket の初期化
                webSocket = new WebSocket(uri);
                // イベントハンドラの設定
                webSocket.onopen = onOpen;
                webSocket.onmessage = onMessage;
                webSocket.onclose = onClose;
                webSocket.onerror = onError;
            }
        }

        // 接続イベント
        function onOpen(event) {
            $("[data-name='status']").text("OPEN");
            try {
                // WebSoket存在判定
                if (webSocket != null) {
                    // ファイル存在判定
                    if (file != null) {
                        // ファイル送信
                        webSocket.send(file);
                        file = null;
                    }
                    // 切断
                    webSocket.close();
                    webSocket = null;
                }
            } catch (e) {
                $("[data-name='status']").text("Error:" + e.message);

            }
        }

        // メッセージ受信イベント
        function onMessage(event) {
            if (event && event.data) {
                $("[data-name='status']").text("MESSAGE:" + event.data);
            }
        }

        // エラーイベント
        function onError(event) {
            $("[data-name='status']").text("ERROR");
        }

        // 切断イベント
        function onClose(event) {
            $("[data-name='status']").text("CLOSE");
            webSocket = null;
        }

        // 初期処理登録
        $(init);
    </script>
</head>
<body>
    <div data-name="status">&nbsp;</div>
    <input type="file" data-name="sendfile" />
</body>
</html>

サーバ側は、onMessage(byte[] bs, boolean last, Session session)メソッドでデータを受信します。
ブラウザ側でデータを分割送信している為、第二引数のlast変数がtrueになるまで、データを溜め込みます。
溜め込んだデータはとりあえず、文字列として出力します。

WebSocketBlob.java
package sample;

import java.io.ByteArrayOutputStream;

import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/blob")
public class WebSocketBlob {

    // バッファ
    private ByteArrayOutputStream baos = null;

    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
        // 何もしません。
    }

    @OnMessage
    public void onMessage(byte[] bs, boolean last, Session session) {
        try {
            System.out.println(session.getId() + "/" + bs.length + "/" + last);
            // バッファ存在チェック
            if (baos == null) {
                // バッファ生成
                baos = new ByteArrayOutputStream();
            }
            // 書き込み
            baos.write(bs);
            baos.flush();
            // 最後であるか判定
            if (last) {
                // 最後なので、byte配列化
                byte[] data = baos.toByteArray();
                // とりあえず、文字列にして出力
                System.out.println(new String(data));
                // バッファ後処理
                baos.close();
                baos = null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @OnClose
    public void onClose(Session session, CloseReason reason) {
        // 何もしません。
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        // 何もしません。
    }
}

感想

サーバ側からプッシュ出来る仕組みは素晴らしいと思いつつも、
使う場所は限定されるんだろうと思いました。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
182