JavaとJavaScriptでwebブラウザとのソケット通信②

# 背景

過去記事：https://qiita.com/take4eng/items/d0b009c48ee8c3fe420a

上記の過去記事に記述しているように、Javaでサーバープログラムを作成しソケット通信を実施。
⇒ HTTP通信をゴリ押しで解析しているため、無駄に複雑なコードになっている。

Java EEにはsocket通信に関するAPIが多数存在し、非常に簡単に実装することが可能。
既に多くの人がまとめてくれてはいるが、実装した内容をまとめておく。
>便利なAPIが用意されているのにゴリ押しで解析なんて誰もやらないよね
そりゃググってもなかなか出てこないわ…

# 実践内容
1. WebSocket API の使用方法
2. APIを使用したサーバープログラムを作成
3. 過去記事内のクライアントプログラムコードを編集しチャットアプリ作成

## WebSocket API の使用方法
基本的なWebSocket APIについて説明する。
ここで紹介するもの以外にも多数のAPIが存在するが、必要ならググると良い。

### Endpointクラスの作成

```java
import javax.websocket.server.ServerEndpoint;
 
@ServerEndpoint("/コンテキストパス")
public class SanpleEndpoint {
}
```

1. ServerEndpointクラスをインポート
2. クラスにアノテーション@ServerEndpointを付与
3. ファイルの場所を示すコンテキストパスを記述

### 処理メソッドの作成

```java
/*
各クラスをインポートしておく
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
*/

//クライアントと接続したときの処理
@OnOpen
public void onOpen(Session session) {
}
 
//クライアントからメッセージを受け取ったときの処理
@OnMessage
public void onMessage(String message) {
}

//エラーが発生したときの処理
@OnError
public void onError(Throwable error) {
}

//クライアントと接続が切れたときの処理
@OnClose
public void onClose(Session session) {
}
```

1. 必要なクラスをインポート
2. メソッドに対応した各アノテーションを付与
3. 引数は必要に応じて変更 , 追加可能



## APIを使用したサーバープログラムを作成

```java:サーバープログラム
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

// Webソケットのサーバ側クラスであること表すアノテーション。
// 引数(wSck)はクライアントから接続時、使われるURIを表す。
@ServerEndpoint(value = "/wSck")
public class SocketFree2 {

	//クライアントのセッションスレッドを作成(クライアント毎にそれぞれのセッションを保存)
	//Set：重複要素のないコレクション
	//CopyOnWriteArrayList：java.util.Setをスレッドセーフにしたもの
	private static Set<Session> user = new CopyOnWriteArraySet<>();

	@OnOpen//クライアントと接続したとき
    public void onOpen(Session mySession) {
        System.out.println("connect ID:"+mySession.getId());//session.getId():セッションIDを取得
        user.add(mySession);//クライアント毎のセッションをリストに追加
    }

    @OnMessage//クライアントからデータが送信されたとき
    public void onMessage(String text , Session mySession) {//引数は送信されたテキストと送信元のセッション
    	System.out.println(text);
    	//getAsyncRemote()：RemoteEndpointのインスタンスを取得
    	//sendText(String)：クライアントにテキストを送信
    	for (Session user : user) {
    		user.getAsyncRemote().sendText(text);
            System.out.println(user.getId()+"番目に"+mySession.getId()+"番目のメッセージを送りました！");
    	}
    	if(text.equals("bye")) onClose(mySession);//textが「bye」なら切断する
    }

    @OnClose//クライアントが切断したとき
    public void onClose(Session mySession) {
    	System.out.println("disconnect ID:"+mySession.getId());
        user.remove(mySession);//切断したクライアントのセッションをリストから削除
        try {
			mySession.close();//closeメソッドで切断
		} catch (IOException e) {
			System.err.println("エラーが発生しました: " + e);
		}
    }

}
```

### コードの解説

1．クラスにEndpointアノテーション@ServerEndpointを付与し、コンテキストパスを記述
`@ServerEndpoint(value = "/wSck")`

2．各クライアントを識別するリストを作成
`private static Set<Session> user = new CopyOnWriteArraySet<>();`

3．onOpenメソッド：クライアントのセッションをリストに追加
`user.add(mySession);`

4．onMessageメソッド：受信したテキストをそのままクライアントへ送信
⇒ for文で接続しているクライアント全員に送信する
`user.getAsyncRemote().sendText(text);`

5．onCloseメソッド：接続が切れたクライアントを削除
5-1．userリストから削除：`user.remove(mySession);`
5-2．セッションを削除し接続を切る：`mySession.close();`

### 実行結果

クライアントプログラムのアドレスを変更し、webブラウザで実行。
※コード内容は過去記事を参照
`var wSck= new WebSocket("ws://localhost:8080/プロジェクト名/コンテキストパス");`
(今回なら`"ws://localhost:8080/freeWeb2/wSck"`)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634805/b29d0e44-90c2-156e-2097-99f7f7da78b7.png)
送信した内容が表示されるチャットアプリが完成。
複数のブラウザからのアクセスにも対応している。

# 感想
APIを使用することで非常に簡単にソケット通信を行うことができた。
過去記事で記述しのものと比べるとコードの記述量は約1/4。すごい。超簡単。

他にもエンコード、デコード処理やjsonデータの扱いについても簡単にできるらしい。
今回は使用していないが、本格的な開発をするなら必要になるだろう。
詳細は参考ページを参照。


# 参考ページ
- http://dukelab.hatenablog.com/entry/2014/02/23/145656
- https://cloudear.jp/blog/?p=2053
- https://qiita.com/opengl-8080/items/7ca8484c8579d264e239