1. 概要

WebSocketは、双方向、全二重、リアルタイムのクライアント/サーバー通信を提供することにより、サーバーとWebブラウザー間の効率的な通信の制限に代わるものを提供します。 サーバーはいつでもクライアントにデータを送信できます。 TCP上で実行されるため、低遅延の低レベル通信も提供し、各メッセージのオーバーヘッドを削減します

この記事では、チャットのようなアプリケーションを作成して、WebSocket用のJavaAPIを見ていきます。

2. JSR 356

JSR 356またはWebSocket用のJavaAPIは、Java開発者がサーバー側とJavaクライアント側の両方でWebSocketをアプリケーションと統合するために使用できるAPIを指定します。

このJavaAPIは、サーバー側とクライアント側の両方のコンポーネントを提供します。

  • サーバーjavax.websocket.serverパッケージのすべて。
  • Client javax.websocket パッケージのコンテンツ。これは、クライアント側のAPIと、サーバーとクライアントの両方に共通のライブラリで構成されています。

3. WebSocketを使用したチャットの構築

非常にシンプルなチャットのようなアプリケーションを作成します。 すべてのユーザーは、任意のブラウザーからチャットを開き、名前を入力し、チャットにログインして、チャットに接続しているすべてのユーザーとの通信を開始できます。

まず、pom.xmlファイルに最新の依存関係を追加します。

<dependency>
    <groupId>javax.websocket</groupId>
    <artifactId>javax.websocket-api</artifactId>
    <version>1.1</version>
</dependency>

最新バージョンはここにあります。

Java Objects をJSON表現に、またはその逆に変換するために、Gsonを使用します。

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.0</version>
</dependency>

最新バージョンは、 MavenCentralリポジトリーで入手できます。

3.1. エンドポイント構成

エンドポイントを構成するには、アノテーション-ベースと拡張ベースの2つの方法があります。 javax.websocket.Endpoint クラスを拡張するか、専用のメソッドレベルのアノテーションを使用できます。 アノテーションモデルはプログラムモデルと比較してよりクリーンなコードにつながるため、アノテーションは従来のコーディングの選択肢になりました。 この場合、WebSocketエンドポイントライフサイクルイベントは次のアノテーションによって処理されます。

  • @ServerEndpoint: @ServerEndpointで装飾されている場合、コンテナーは、特定のURIスペースをリッスンするWebSocketサーバーとしてクラスの可用性を保証します
  • @ClientEndpoint :このアノテーションで装飾されたクラスは、WebSocketクライアントとして扱われます
  • @OnOpen :新しい WebSocket 接続が開始されると、@OnOpenのJavaメソッドがコンテナーによって呼び出されます。
  • @OnMessage @OnMessageで注釈が付けられたJavaメソッド、は、メッセージがエンドポイントに送信されるときにWebSocketコンテナーから情報を受け取ります
  • @OnError :通信に問題がある場合、@OnErrorのメソッドが呼び出されます
  • @OnClose WebSocket接続が閉じたときにコンテナーによって呼び出されるJavaメソッドを装飾するために使用されます

3.2. サーバーエンドポイントの作成

@ServerEndpoint でアノテーションを付けることにより、JavaクラスWebSocketサーバーエンドポイントを宣言します。 エンドポイントが展開されるURIも指定します。 URIはサーバーコンテナのルートに対して相対的に定義され、スラッシュで始める必要があります。

@ServerEndpoint(value = "/chat/{username}")
public class ChatEndpoint {

    @OnOpen
    public void onOpen(Session session) throws IOException {
        // Get session and WebSocket connection
    }

    @OnMessage
    public void onMessage(Session session, Message message) throws IOException {
        // Handle new messages
    }

    @OnClose
    public void onClose(Session session) throws IOException {
        // WebSocket connection closes
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        // Do error handling here
    }
}

上記のコードは、チャットのようなアプリケーションのサーバーエンドポイントスケルトンです。 ご覧のとおり、それぞれのメソッドにマップされた4つの注釈があります。 以下に、そのようなメソッドの実装を示します。

@ServerEndpoint(value="/chat/{username}")
public class ChatEndpoint {
 
    private Session session;
    private static Set<ChatEndpoint> chatEndpoints 
      = new CopyOnWriteArraySet<>();
    private static HashMap<String, String> users = new HashMap<>();

    @OnOpen
    public void onOpen(
      Session session, 
      @PathParam("username") String username) throws IOException {
 
        this.session = session;
        chatEndpoints.add(this);
        users.put(session.getId(), username);

        Message message = new Message();
        message.setFrom(username);
        message.setContent("Connected!");
        broadcast(message);
    }

    @OnMessage
    public void onMessage(Session session, Message message) 
      throws IOException {
 
        message.setFrom(users.get(session.getId()));
        broadcast(message);
    }

    @OnClose
    public void onClose(Session session) throws IOException {
 
        chatEndpoints.remove(this);
        Message message = new Message();
        message.setFrom(users.get(session.getId()));
        message.setContent("Disconnected!");
        broadcast(message);
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        // Do error handling here
    }

    private static void broadcast(Message message) 
      throws IOException, EncodeException {
 
        chatEndpoints.forEach(endpoint -> {
            synchronized (endpoint) {
                try {
                    endpoint.session.getBasicRemote().
                      sendObject(message);
                } catch (IOException | EncodeException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

新しいユーザーがログインすると( @OnOpen )は、アクティブユーザーのデータ構造にすぐにマッピングされます。 次に、メッセージが作成され、broadcastメソッドを使用してすべてのエンドポイントに送信されます。

このメソッドは、接続しているユーザーのいずれかによって新しいメッセージ( @OnMessage )が送信されるたびにも使用されます。これがチャットの主な目的です。

ある時点でエラーが発生した場合、アノテーション@OnErrorを持つメソッドがそれを処理します。 この方法を使用して、エラーに関する情報をログに記録し、エンドポイントをクリアできます。

最後に、ユーザーがチャットに接続しなくなると、メソッド @OnClose はエンドポイントをクリアし、ユーザーが切断されたことをすべてのユーザーにブロードキャストします。

4. メッセージタイプ

WebSocket仕様は、テキストとバイナリの2つのオンワイヤデータ形式をサポートしています。 APIは、これらの両方の形式をサポートし、仕様で定義されているように、Javaオブジェクトとヘルスチェックメッセージ(ピンポン)を操作する機能を追加します。

  • Text :任意のテキストデータ( java.lang.String 、プリミティブまたは同等のラッパークラス)
  • Binary :バイナリデータ(例: java.nio.ByteBufferまたはbyte[] (バイト配列)で表されるオーディオ、画像など)
  • Javaオブジェクト:APIを使用すると、コード内のネイティブ(Javaオブジェクト)表現を操作し、カスタムトランスフォーマー(エンコーダー/デコーダー)を使用して、互換性のあるオンワイヤ形式(テキスト、バイナリ)に変換できます。 WebSocketプロトコルによる
  • Ping-Pong javax.websocket.PongMessage は、ヘルスチェック(ping)要求に応答してWebSocketピアによって送信される確認応答です。

私たちのアプリケーションでは、 Javaオブジェクト。 メッセージをエンコードおよびデコードするためのクラスを作成します。

4.1. エンコーダー

エンコーダーはJavaオブジェクトを受け取り、JSON、XML、バイナリ表現などのメッセージとして送信するのに適した一般的な表現を生成します。 エンコーダーは、 Encoder.Text また Encoder.Binary インターフェイス。

以下のコードでは、エンコードするクラス Message を定義し、メソッド encode では、JavaオブジェクトをJSONにエンコードするためにGsonを使用します。

public class Message {
    private String from;
    private String to;
    private String content;
    
    //standard constructors, getters, setters
}
public class MessageEncoder implements Encoder.Text<Message> {

    private static Gson gson = new Gson();

    @Override
    public String encode(Message message) throws EncodeException {
        return gson.toJson(message);
    }

    @Override
    public void init(EndpointConfig endpointConfig) {
        // Custom initialization logic
    }

    @Override
    public void destroy() {
        // Close resources
    }
}

4.2. デコーダ

デコーダーはエンコーダーの反対であり、データをJavaオブジェクトに変換するために使用されます。 デコーダーは、 Decoder.Text また Decoder.Binary インターフェイス。

エンコーダーで見たように、 decode メソッドは、エンドポイントに送信されたメッセージで取得されたJSONを取得し、Gsonを使用して Message:というJavaクラスに変換します。

public class MessageDecoder implements Decoder.Text<Message> {

    private static Gson gson = new Gson();

    @Override
    public Message decode(String s) throws DecodeException {
        return gson.fromJson(s, Message.class);
    }

    @Override
    public boolean willDecode(String s) {
        return (s != null);
    }

    @Override
    public void init(EndpointConfig endpointConfig) {
        // Custom initialization logic
    }

    @Override
    public void destroy() {
        // Close resources
    }
}

4.3. サーバーエンドポイントでのエンコーダーとデコーダーの設定

クラスレベルのアノテーション@ServerEndpointでデータをエンコードおよびデコードするために作成されたクラスを追加して、すべてをまとめましょう。

@ServerEndpoint( 
  value="/chat/{username}", 
  decoders = MessageDecoder.class, 
  encoders = MessageEncoder.class )

メッセージがエンドポイントに送信されるたびに、メッセージは自動的にJSONまたはJavaオブジェクトに変換されます。

5. 結論

この記事では、WebSocket用のJava APIとは何か、そしてそれがこのリアルタイムチャットなどのアプリケーションの構築にどのように役立つかについて説明しました。

エンドポイントを作成するための2つのプログラミングモデル、アノテーションとプログラマティックを見ました。 アプリケーションのアノテーションモデルとライフサイクルメソッドを使用して、エンドポイントを定義しました。

また、サーバーとクライアント間で通信できるようにするには、JavaオブジェクトをJSONに、またはその逆に変換するためのエンコーダーとデコーダーが必要であることがわかりました。

JSR 356 APIは非常にシンプルで、WebSocketアプリケーションの構築を非常に簡単にする注釈ベースのプログラミングモデルです。

この例で作成したアプリケーションを実行するには、warファイルをWebサーバーにデプロイし、次のURLにアクセスするだけです。 http:// localhost:8080 /java-websocket/。 リポジトリへのリンクを見つけることができますここ