WebSocket についてまとめてみる

# WebSocket とは？

Web 上でクライアント(Web ブラウザ)・サーバー間を`常時接続にしておいて、双方向通信を低コストで`実現するための技術規格。プロトコル。

# WebSocket の何がありがたいのか？

近年 SNS アプリなどではインタラクティブで `リアルタイム` なやりとりが求められるようになってきました。

例えば、チャットアプリでは、複数のユーザーが同じページを見ているような状況で、誰かの発言が他のユーザーのページにも`ページのリロードなしでリアルタイムに更新`されるようにしたい、ということがあると思います。
この `リアルタイムに更新` という機能を実現するためには、誰かが発言したということを `サーバーからクライアントに伝える必要があります。` このような機能を WebSocket は実現します。

`クライアントからのリクエストがなくても、常時接続しているのでサーバーからクライアントに好きなタイミングで通信ができる` ということを実現できるのが WebSocket なのです。

# HTTP ではダメなの？

Web サイトを閲覧するするときは普通、HTTP を使うかと思います。
この HTTP は、`クライアント(ブラウザ)がサーバーにリクエストを送って、一時的にコネクションを張り、サーバーがレスポンスを返す` という流れになります。そして一つのコネクションにつき、一つのリクエストしか送れません...。

つまり、基本的に HTTP は `クライアントが何らかのリクエストを送らない限り、サーバーはレスポンスを返せないプロトコル`なのです。

# HTTP で擬似的に双方向通信を実現することはできるが...

WebSocket はこのような HTTP(XMLHttpRequest) の欠点を補おうというニーズから生まれた規格なので、HTTP しか手段がなかった時は HTTP でなんとかしようとしていました。

具体的には、HTTP のコネクションを張りっぱなしにしておいて、クライアントから一定間隔でサーバーにポーリングし続ける。サーバーから情報を送りたいタイミングになって初めてレスポンスを返すといった具合です。
※ XMLHttpReqest の `ロングポール` といい、Comet (サーバで発生したイベントをクライアントからの要請なしにクライアントに送信することができる技術) の実現に必要だった。

ただ、この方法では以下のような問題が生じました。

- ブラウザの HTTP コネクションの`タイムアウト`（30秒）があるため、接続し直す処理が必要になる。
- 擬似的に双方向通信を行っているので、通信が発生するごとに `TCP ハンドシェイクを再度行うことが必要`になる。
- HTTP `コネクションを長時間占有`するため、その間サーバに接続する他のアプリケーションの動作に影響を及ぼす可能性がある。

# 対して WebSocket は...

対して WebSocket には以下のような特徴があり、HTTP の問題を解決しています。

- サーバとクライアントが一度コネクションを行った後は、必要な通信を全てそのコネクション上で専用のプロトコルを用いて行い、`新たなコネクションを張る必要がない。`
- HTTP コネクションとは異なる`軽量プロトコル`を使うなどの理由で、通信ロスが減る。
- 一つのコネクションで全てのデータ送受信が行えるため同一サーバに接続する`他のアプリケーションへの影響が少ない。`

# WebSocket の機能・特徴まとめ

以上のことから WebSocket の機能・特徴をまとめると以下のようになるかと思います。

- 常時双方向通信によるサーバプッシュ機能 : 一度コネクションを確立したあとは、`サーバとクライアントのどちらからも通信を行うことが可能`になる。
- データ通信量削減 : Payload 以外の情報(ヘッダ)は最小2byte, 最大でも14byteに収まる様になっていてとても小さい。
- 低コスト : HTTP のように通信のたびにコネクションを張らず、`一度コネクションを確立するとそのコネクション上で通信を行う。`

# WebSocket の通信の流れ

### 1. WebSocket opening ハンドシェイク

`WebSocket opening ハンドシェイク`は HTTP 通信で行われます。
クライアントのリクエストには以下のように `Upgrade ヘッダ`、 `Connection ヘッダ`、`Sec-WebSocket-Version ヘッダ`、`Sec-WebSocket-Key ヘッダ`が付けられます。

```
Upgrade: websocket
Connection: upgrade
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: XXXXXXXXXXXXXXXXXX==
```

サーバーからのレスポンスヘッダは以下のようになります。

```
HTTP/1.1 101 OK
Upgrade: websocket
Connection: upgrade
Sec-WebSocket-Accept: ZZZZZZZZZZZZZZZZZZZ==
```

ステータスコード `101 は「Switching Protocols」`で、これでコネクションが確立したことになります。

### 2. 双方向通信

コネクション確立後は、HTTP ではなく WebSocket プロトコルで通信を行います。
「フレーム」という単位でデータのやりとりが行われます。

# 実際に使ってみる

### クライアントからサーバーにメッセージ(データ)を送信してみる

実際に WebSocket を使ってみます。まずはクライアントからサーバーにデータを送信する例からです。
Node.js を使います。
ws モジュールが必要になるので、インストールしておきます。

```shell
$ npm install ws
```

サーバーサイドのコードは以下のような感じです。

```javascript
// index.js

const server = require('ws').Server;
const ws = new server({ port: 8081 });

ws.on('connection', socket => {
  console.log('connected!');

  socket.on('message', ms => {
    console.log(ms);
  });

  socket.on('close', () => {
    console.log('good bye.');
  });
});
```

書き終わったら、`node index.js` で起動させておきます。

Chrome の Developer tool でもなんでも良いのですが、クライアントのコードは以下のような感じで実行してみます。

```javascript
const con = new WebSocket('ws://localhost:8081');
con.send('Hello WebSocket!');
con.close();
```

サーバーに以下のようなログが流れるはずです。
簡単ですね。

```
connected!
Hello WebSocket!
good bye.
```

### サーバーから複数クライアントにメッセージ(データ)を送ってみる

上の例は HTTP でもよくある、クライアントがリクエスト投げてサーバーがレスポンス返すみたいなパターンだったので、サーバーから複数クライアントにメッセージを送ってみます。(WebSocket の恩恵を感じられるはず...)

まずはサーバーサイドからです。

```javascript
const server = require('ws').Server;
const ws = new server({ port: 8081 });

ws.on('connection', socket => {
  socket.on('message', ms => {
    ws.clients.forEach(client => {
      client.send('Hello, this message comes from server!');
    });
  });

  socket.on('close', () => {
    console.log('good bye.');
  });
});
```

クライアントからメッセージが届くと、接続している全てのクライアントにメッセージを送るようにしています。同様に書き終わったら `node index.js` で起動します。

続いてクライアントサイド。**擬似的にクライアントを多数にするため、Chrome の複数タブで Developer tool を開いてください。**

```javascript
const con = new WebSocket('ws://localhost:8081');
con.onmessage = (m) => { console.log(m.data) }; // ここまでは、全てのタブ共通で入力する

con.send('Hello'); // ここは一つのタブでだけ入力する
```

メッセージを send したタブでサーバからのレスポンスが表示されているのは、それはそうでしょという感じですが...
<img width="567" alt="スクリーンショット 2019-11-26 12.40.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182916/cbded3ee-81e7-4443-a983-8267d6d3bdfd.png">
send していない別タブにもサーバーからメッセージが届いています...！
<img width="567" alt="スクリーンショット 2019-11-26 12.40.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182916/70f3dc23-1daf-6d3f-f3a3-64f97a776d05.png">
感動ですね。

# おまけ

### セキュア通信は wss で
 
HTTP と同じく、WebSocket にもセキュア通信用のプロトコルが用意されており `wss` になっています。

### Event の登録は addEventListener でもできる

```javascript
con.onmessage = (m) => { console.log(m.data) }; 
con.addEventListener('message', (m) => { console.log(m.data) });
```

### よく利用する Event 処理とメソッド

```javascript
// Event 処理
- onopen    // 接続された時
- onerror   // エラーが発生した時
- onmessage // データを受け取った時
- onclose   // 切断された時

// メソッド
- send()    // データを送信する
- close()   // 通信を切断する
```