WebSocket(ブラウザAPI)とws(Node.js) の基本、自分用のまとめ

この投稿は、nodejsの**ws**とブラウザapiの**WebSocket**について、自分の勉強のためにドキュメントを読みながら試しただけのものになります。

なのでWebSocketについての詳細な情報が欲しい方は、以下のリンクを見て、**本記事は読まなくて良いです**

- [GitHub - websockets/ws: Simple to use, blazing fast and thoroughly tested WebSocket client and server for Node.js](https://github.com/websockets/ws)
- [WebSocket - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)

wsのAPIドキュメントは↓のようです

- https://github.com/websockets/ws/blob/master/doc/ws.md

間違っているなどあればご指摘お願いします :bow: 

---

WebSocket仕様関連

- [The WebSocket API](https://www.w3.org/TR/websockets/)
- [HTML Standard 9.3 Web sockets](https://html.spec.whatwg.org/multipage/web-sockets.html)
- [RFC 6455 - The WebSocket Protocol](https://tools.ietf.org/html/rfc6455)

# ブラウザ対応状況

https://caniuse.com/#search=websocket

![image.png](https://qiita-image-store.s3.amazonaws.com/0/142910/bd782c99-c0f0-c942-53ba-ae8b1a31a6a3.png)

多くのブラウザでサポートされています

# イベントリスナー

`WebSocket`apiは以下の4つのイベントを持っています

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <button id="btn">Hello</button>

    <script>
        const sock = new WebSocket("ws://127.0.0.1:5001");

        sock.addEventListener("open", e => {
            console.log("接続が開かれたときに呼び出されるイベント");
        });

        sock.addEventListener("message", e => {
            console.log("サーバーからメッセージを受信したときに呼び出されるイベント");
        });

        sock.addEventListener("close", e => {
            console.log("接続が閉じられたときに呼び出されるイベント");
        });

        sock.addEventListener("error", e => {
            console.log("エラーが発生したときに呼び出されるイベント");
        });

        btn.addEventListener("click", e => {
            sock.send("hello");
        });
    </script>
</body>
</html>
```

## WebSocketのサーバを動作させる

```bash:bash
npm init -y
npm i -D ws
```

```json:package.json
{
  "name": "01_sample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "ws": "^6.1.4"
  }
}
```

```js:index.js
const server = require("ws").Server;
const s = new server({ port: 5001 });

s.on("connection", ws => {
    ws.on("message", message => {
        console.log("Received: " + message);

        if (message === "hello") {
            ws.send("hello from server");
        }
    });
});
```


```bash:bash
node index.js
```

### open、messageイベントの確認

イベント起こしやすいようにボタンを追加しました

```html:index.html
    <button id="btn">Hello</button>

    <script>
        const sock = new WebSocket("ws://127.0.0.1:5001");

        sock.addEventListener("open", e => {
            console.log("接続が開かれたときに呼び出されるイベント");
        });

        sock.addEventListener("message", e => {
            console.log("サーバーからメッセージを受信したときに呼び出されるイベント");
        });

        sock.addEventListener("close", e => {
            console.log("接続が閉じられたときに呼び出されるイベント");
        });

        sock.addEventListener("error", e => {
            console.log("エラーが発生したときに呼び出されるイベント");
        });

        btn.addEventListener("click", e => {
            sock.send("hello");
        });
    </script>
```

接続された時、サーバーからメッセージを受信した時にそれぞれイベントが実行されています

![iJ3CrM4BgX.gif](https://qiita-image-store.s3.amazonaws.com/0/142910/ee30564e-13f4-b6b5-84f0-5055479c18dd.gif)

messageイベントは`e.data`からサーバーからの受信内容を受け取ることができます

```js:javascript
        sock.addEventListener("message", e => {
            console.log(e.data);
        });
```

![Screen Shot 2019-03-03 at 16.00.13.png](https://qiita-image-store.s3.amazonaws.com/0/142910/9527c39c-1a79-a440-e7fd-4f7a6d17418e.png)


---

Networkタブの`WS`からでもメッセージの内容を見ることができます。

- 緑の矢印が送信したデータ
- 赤の矢印がサーバーから受信したデータです


![hX1Xp2HI75.gif](https://qiita-image-store.s3.amazonaws.com/0/142910/c470acc3-681a-7b32-0077-fe0432612286.gif)

### close、errorイベントの確認

`ws.close()`を実行すると、closeイベントが実行されるのが確認できました、

また、サーバーを停止した状態で接続しに行くと、エラーイベントが実行されるのも確認できました。

![9TXHLR64Rq.gif](https://qiita-image-store.s3.amazonaws.com/0/142910/8b90dd6e-0b6c-14d6-c485-bd514cdf3d3d.gif)

[動作サンプル](https://github.com/okumurakengo/00008_ws/tree/master/01_sample)

# サーバー側で接続が切れた場合の処理

```js:index.js
const server = require("ws").Server;
const s = new server({ port: 5001 });

s.on("connection", ws => {
    ws.on("message", message => {
        ws.send("hello from server");
    });

    // 接続が切れた場合
    ws.on('close', () => {
        console.log('I lost a client');
    });
});
```

![1P4bQhHCAy.gif](https://qiita-image-store.s3.amazonaws.com/0/142910/ff533547-0ab3-ee2f-a7c6-3b89cf32d1ba.gif)


# 複数のクライアントにデータ送信する

```js:index.js
const server = require("ws").Server;
const s = new server({ port: 5001 });

s.on("connection", ws => {
    ws.on('message', message => {

        ws.send("送信してきたクライアントのみに返す");

        s.clients.forEach(client => {
            client.send('接続しているクライアント全てに送信');
        });

        s.clients.forEach(client => {
            if (client !== ws)
                client.send('接続している自分以外のクライアント全てに送信');
        });
    });
});
```

![bKQhunk5Uz.gif](https://qiita-image-store.s3.amazonaws.com/0/142910/d29a0d2f-23fb-45cd-f60f-9013a7cea65d.gif)

[動作サンプル](https://github.com/okumurakengo/00008_ws/tree/master/02_sample)

# 複数データの送受信

オブジェクトを`JSON.stringify()`とする

```js:javascript
        const sock = new WebSocket("ws://127.0.0.1:5001");

        sock.addEventListener("message", e => {
            // サーバーから受信
            const { hoge, fuga, piyo } = JSON.parse(e.data);
            console.log(hoge, fuga, piyo);
        });

        btn.addEventListener("click", e => {
            // サーバーへ送信
            sock.send(JSON.stringify({
                name: 'alice',
                age: 25,
            }));
        });
```

```js:index.js
const server = require("ws").Server;
const s = new server({ port: 5001 });

s.on("connection", ws => {
    ws.on('message', message => {
        // クライアントから受信
        const { name, age } = JSON.parse(message);
        console.log(name, age);

        // クライアントへ送信
        ws.send(JSON.stringify({
            hoge: true,
            fuga: [1, 3, 5],
            piyo: 0.5,
        }));
    });
});
```

![UE7zT9jtvp.gif](https://qiita-image-store.s3.amazonaws.com/0/142910/7e611154-dc1e-9456-824f-f881c3367ac4.gif)

[動作サンプル](https://github.com/okumurakengo/00008_ws/tree/master/03_sample)

# urlを確認する

`WebSocket.url`で取得できる

![image.png](https://qiita-image-store.s3.amazonaws.com/0/142910/cb341c26-b61d-43c9-b09a-92a671d084dd.png)


# 接続状態を確認する

接続状態の定数が用意されています

参考：[WebSocket - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)

| Constant | Value |
|:--|--:|
| WebSocket.CONNECTING | 0 |
| WebSocket.OPEN | 1 |
| WebSocket.CLOSING | 2 |
| WebSocket.CLOSED | 3 |

`readyState`から現在の接続状態を取得できました

```js:index.js
        const sock = new WebSocket("ws://127.0.0.1:5001");
        console.log(`readyState:${sock.readyState}`);

        sock.addEventListener("open", e => {
            console.log(`readyState:${sock.readyState}`);
        });

        sock.addEventListener("close", e => {
            console.log(`readyState:${sock.readyState}`);
        });
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/142910/9282d406-60e6-63d1-7565-44af7c006a6a.png)
 
---

最後まで読んでいただいてありがとうございました :bow: