WebSocketを死活監視に使ってみた
はじめに
Websocketを使って、キープアライブのようなこと*1をやってみました。
やってみたキッカケは、お仕事でTomcatのWebSocket Sevletを作った際にAPIを見ていたら
接続が切れた時にイベントが拾えるようで、キープアライブに使えるのでは?と思ったからです。
やってみたら、案外ちゃんと使えたので内容を公開します。
ちなみに、WebSocketの実装にはTomcatのWebSocket Servletを使用します。
WebSocket Servletについては、色々解説しているサイトがあるので、ここでは説明を省きます。
環境
ライブラリ
- Java-WebSocket v1.2.0
仕組みについて
ざっくりと流れはこんな感じ
- クライアントがサーバにWebSocketで接続する
- 接続後に、クライアントからサーバにメッセージを送る
- この時に、クライアントを識別する情報を渡してあげると、クライアントが識別できます
- サーバは、クライアントからのメッセージを受けて、クライアントに応答を返す
- クライアントは、サーバからのメッセージを受けて、サーバに応答を返す
- 正常時は、上のサーバとクライアントのやり取りが繰り返される
- (クライアントが切れた時)サーバ側のonCloseが呼ばれる
- ここで、切れた時に行いたい処理を呼び出す
ソース
サーバ側
以下のライブラリにパスを通す
import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.util.HashSet; import java.util.Set; import java.util.concurrent.TimeUnit; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServletRequest; import org.apache.catalina.websocket.MessageInbound; import org.apache.catalina.websocket.StreamInbound; import org.apache.catalina.websocket.WebSocketServlet; import org.apache.catalina.websocket.WsOutbound; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @WebServlet(name = "KeepAliveServlet", urlPatterns = { "/KeepAliveServlet" }) public class KeepAliveServlet extends WebSocketServlet { private static final long serialVersionUID = -1L; private static final Log log = LogFactory.getLog(KeepAliveServlet.class); private static Set<MessageInbound> messages = new HashSet<MessageInbound>(); @Override protected StreamInbound createWebSocketInbound(String arg0, HttpServletRequest arg1) { log.info("called createWebSocketInbound."); return new KeepAliveInbound(); } private class KeepAliveInbound extends MessageInbound { private WsOutbound outbound; private String clientId; @Override public void onOpen(WsOutbound outbound) { log.info("onOpen."); this.outbound = outbound; messages.add(this); } @Override public void onClose(int status) { log.info("onClose."); if (clientId != null) { log.info(String.format("クライアントID[%s]が切れた", clientId)); } messages.remove(this); } @Override public void onTextMessage(CharBuffer cb) throws IOException { if (clientId == null) { clientId = cb.toString(); } log.info("onTextMessage. " + cb); // すぐにメッセージを返すと通信量が多すぎるので、間隔を設ける try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } outbound.writeTextMessage(CharBuffer.wrap("echo from server.")); } @Override public void onBinaryMessage(ByteBuffer bb) throws IOException { } @Override public int getReadTimeout() { // 初期状態だとクライアントと通信が切れても待ち続けるので、時間を指定しています。 return 10000; } } }
クライアント側
以下のライブラリにパスを通す
- java_websocket.jar
import java.net.URI; import java.net.URISyntaxException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.java_websocket.client.WebSocketClient; import org.java_websocket.drafts.Draft_17; import org.java_websocket.handshake.ServerHandshake; public class KeepAliveClient extends WebSocketClient { private static final Log log = LogFactory.getLog(KeepAliveClient.class); private static final String CLIENT_ID = "123"; private KeepAliveClient(URI serverURI) { super(serverURI, new Draft_17()); } @Override public void onClose(int arg0, String arg1, boolean arg2) { // サーバ側と接続が切れた時に呼ばれる log.info("onClose."); } @Override public void onError(Exception arg0) { log.error("onError. " + arg0.toString()); } @Override public void onMessage(String arg0) { log.info(arg0); this.send("echo from client."); } @Override public void onOpen(ServerHandshake arg0) { log.info("onOpen called. " + arg0.getHttpStatus()); this.send(CLIENT_ID); } public static KeepAliveClient getInstance() { URI uri; try { // 環境に合わせて接続先を変更 uri = new URI("ws://localhost:8080/TomcatProject/KeepAliveServlet"); } catch (URISyntaxException e) { throw new RuntimeException(e); } KeepAliveClient client = new KeepAliveClient(uri); return client; } }
実行用のメインクラス
public class Main { public static void main(String[] args) { KeepAliveClient client = KeepAliveClient.getInstance(); client.connect(); } }
動かしてみる
まず、Mainクラスを適当に実行 クライアント側のログはこんな感じ
KeepAliveClient onOpen 情報: onOpen called. KeepAliveClient onMessage 情報: echo from server. KeepAliveClient onMessage 情報: echo from server. KeepAliveClient onMessage 情報: echo from server.
サーバ側のログはこんな感じ
sample.KeepAliveServlet createWebSocketInbound 情報: called createWebSocketInbound. KeepAliveServlet onOpen 情報: onOpen. KeepAliveServlet onTextMessage 情報: onTextMessage. 123 KeepAliveServlet onTextMessage 情報: onTextMessage. echo from client. KeepAliveServlet onTextMessage 情報: onTextMessage. echo from client.
ここで、クライアント側の接続を切る(LANを切るとか) そうすると、しばらくしてサーバ側にクライアントが切れたとログが出る
KeepAliveServlet onClose 情報: クライアント[123]が切れた
このような感じで、クライアント側の死活を監視することができる
終わりに
WebSocketのちょっと変わった使い方を紹介してみました。
あまりミッション・クリティカルな用途には向きませんが
比較的簡単に、キープアライブ的なことができるのでお試しあれ!*2