はじめに
HTML5のWebSocketを使ってみます。
チャット
WebSocketオブジェクト生成時に接続に行きます。
パラメータは、ws://
から始まる、URLです。
WebSocketオブジェクト生成後、イベントハンドラを登録します。
名称 | 説明 |
---|---|
onopen | 接続イベント |
onclose | 切断イベント |
onmessage | メッセージ受信イベント |
onerror | エラーイベント |
WebSocketオブジェクトのメソッド
メソッド | 説明 |
---|---|
send | メッセージ送信 |
close | 切断 |
<!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に送信するだけの簡単なものです。
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-ui
のdraggable
を利用して、ボールを動かすようなサンプルです。
1ブラウザだけでやってると意味ありませんが、複数ブラウザでやると全部同じ動きをします。
<!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"> </div>
<div data-name="square" style="width: 400px; height: 300px; background-color: gray"></div>
</body>
</html>
サーバ側は、static
で保持しているJSONデータを接続時onOpen
には、接続した相手へ、
データ受信時onMessage
は、データ送信者以外に配布しているだけです。
(データ形式が複雑になると、JSONデータを解析しなければならないと思います。例えば、ボールが4つになった時など)
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オブジェクトを送信します。
<!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"> </div>
<input type="file" data-name="sendfile" />
</body>
</html>
サーバ側は、onMessage(byte[] bs, boolean last, Session session)
メソッドでデータを受信します。
ブラウザ側でデータを分割送信している為、第二引数のlast
変数がtrue
になるまで、データを溜め込みます。
溜め込んだデータはとりあえず、文字列として出力します。
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) {
// 何もしません。
}
}
感想
サーバ側からプッシュ出来る仕組みは素晴らしいと思いつつも、
使う場所は限定されるんだろうと思いました。