【Node.js 2021（2つ目）】 Node.js での UDP・TCP通信をシンプルに試す（2021年12月）

この記事は、[2021年の Node.js のアドベントカレンダー](https://qiita.com/advent-calendar/2021/nodejs) の 10日目の記事です。

内容は、以下の記事を書く中で使った「zigsim-ws」の中で利用されている「dgram」が気になって調べたり試したりしたことを、記事として書いた形です。

●【IoTLT 2021】 ZIG SIM から送られるデータを p5.js Web Editor上で活用してみる - Qiita
　https://qiita.com/youtoy/items/caca41a68ab3bff6ffa6

## zigsim-ws のプログラムと UDP通信
zigsim-ws は、プロトタイピングに役立つスマホアプリの「ZIG SIM」を、ブラウザ（HTML＋JavaScript のプログラム）との間で通信させる時に利用するものです。
軽く補足をすると、ZIG SIM がデータを送る時に用いる通信が UDP・TCP で、ブラウザ上の JavaScript のプログラムでは直接は扱えないものになるので、それらの間で通信できるようにするためには仲介役が必要です。zigsim-ws は、その仲介役として UDP 通信をブラウザ上で動く JavaScript が扱える WebSocket の通信に変換する役割を担います。

●zigsim-ws/index.js at master · acrylicode/zigsim-ws
　https://github.com/acrylicode/zigsim-ws/blob/master/index.js

そのプログラムは、以下のような実装になっています。

```javascript
const dgram = require('dgram');
const WebSocket = require('ws');

function init(updPort = 50000, wsServerPort = 8080) {
    const updServer = dgram.createSocket('udp4');
    const wss = new WebSocket.Server({ port: wsServerPort });
    console.log(`websocket server listening on port: ${wsServerPort}`)

    wss.on('connection', (ws) => {
        console.log(`New client connected to the websocket. Number of clients: ${wss.clients.size}`)
    });

    updServer.on('error', (err) => {
        console.log(`upd server error:\n${err.stack}`);
        updServer.close();
    });

    updServer.on('message', (msg) => {
        wss.clients.forEach(client => {
            if (client.readyState === WebSocket.OPEN) {
                client.send(msg.toString())
            }
        });
    });

    updServer.on('listening', () => {
        const address = updServer.address();
        console.log(`udp server listening on port: ${address.port}`);
    });
    updServer.bind(updPort);
}

module.exports.init = init;
```

冒頭の 2行目の部分は、WebSocketサーバーを動かすためのものを読み込んでいますが、1行目で「dgram」を読み込んでいます。

### dgram について見てみる
npm のページを見て、さらに情報をチェックしようと思って以下にアクセスしました。
　●dgram - npm
　　https://www.npmjs.com/package/dgram
そうすると、以下をのような記載が...（deprecated になってる...）
![dgram.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/e41eeeaa-f0a4-f2b8-4a78-c01cb16efa2f.jpeg)

そして、さらに調査をしていきました。

### dgram についてさらにチェック
先ほどの npm のページの記載をあらためて見てみると **「As it's a core Node module, we will not transfer it ...」** と書かれていました。

さらに、Node.js の公式情報を合わせて見てみると、Node.js自体の機能の話で「UDP/datagram sockets」というものが出てきました。

●UDP/datagram sockets | Node.js v17.2.0 Documentation
　https://nodejs.org/api/dgram.html

結論を書くと、先ほどの zigsim-ws は、こちらの Node.js内のものを呼んでいたようです。
（※ 自分が zigsim-ws のプログラムを実行していたフォルダ等を見ると、node_modulesフォルダ以下に dgram のフォルダがなかった）

そして、Node.js での UDP通信の話を見ていくと、以下の内容も見つかりました。
こちらの記事でも、Node.js に元から入っていそうな dgram の話をされています。

●Node.jsでUDP通信する - Re: note
　https://hikoleaf.hatenablog.jp/entry/2019/06/08/235753

## Node.js で UDP通信を実行する
それでは、実際に Node.js での UDP通信を、自分の環境で実行してみます。

### 2者間の単純な通信
先ほど、[調べて出てきたと書いていたサイト](https://hikoleaf.hatenablog.jp/entry/2019/06/08/235753)を見てみると、2者間でのシンプルな通信の例が掲載されていました。

そこで、まずはこの例をそのまま利用します。
1点、元の記事で `$ npm install dgram` を実行する手順が書いてありますが、これは不要だと思われます。
（これをやってしまうと、上記で deprecated となってたやつを読み込んでしまいそうな予感...）

以下の2つのプログラムを用意し、2つのターミナルを開いて、それぞれで 1つずつのプログラムを実行すると、定期的にデータのやりとりが行われている様子が確認できました。

```javascript
const dgram = require('dgram');

const PORT_A = 3002;
const HOST_A = '127.0.0.1';

const PORT_B = 3003;
const HOST_B = '127.0.0.1';

const socket = dgram.createSocket('udp4');

socket.on('listening', () => {
    const address = socket.address();
    console.log('UDP socket listening on ' + address.address + ":" + address.port);
});

socket.on('message', (message, remote) => {
    console.log(remote.address + ':' + remote.port +' - ' + message);

    socket.send(message, 0, message.length, PORT_B, HOST_B, (err, bytes) => {
        if (err) throw err;
    });
});

socket.bind(PORT_A, HOST_A);
```

```javascript
const dgram = require('dgram');

const PORT_A = 3002;
const HOST_A ='127.0.0.1';

const PORT_B = 3003;
const HOST_B ='127.0.0.1';

const socket = dgram.createSocket('udp4');

var count = 0;

setInterval(() => {
    count++;
    const data = Buffer.from(String(count));
    socket.send(data, 0, data.length, PORT_A, HOST_A, (err, bytes) => {
        if (err) throw err;
    });
}, 500);


socket.on('message', (message, remote) => {
    console.log(remote.address + ':' + remote.port +' - ' + message);
});

socket.bind(PORT_B, HOST_B);
```

これらを実行すると、一方から一定間隔で他方へカウントアップする文字列を送り、それを受信した側はエコーバックするという動作が確認できました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">2つのプログラム間での UDP通信！ <a href="https://t.co/PHpipZ0fZs">https://t.co/PHpipZ0fZs</a> <a href="https://t.co/2evFUu7Um8">pic.twitter.com/2evFUu7Um8</a></p>&mdash; you (@youtoy) <a href="https://twitter.com/youtoy/status/1467358053821743104?ref_src=twsrc%5Etfw">December 5, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

そういえば、●● over UDP という形のものを使うことはよくあったけど、UDP を直接扱うようなことはこれまでなかったかも。

## Node.js で TCP通信を実行する
この後は、UDP通信を行うプログラムに手を入れて何かやろうかとも思ったのですが、ふと「TCP はどんな感じなんだろう？」という考えが頭をよぎりました。

そして、記事を検索したところ、以下のものが出てきました。
非常にシンプルなプログラムが掲載されていて、試してみるのにちょうど良さそうです。

●Node.jsでTCP通信する - Re: note
　https://hikoleaf.hatenablog.jp/entry/2019/06/09/131620

UDP に関する記事と同様に `$ npm install net` を実行する手順が書いてありますが、これは不要な感じが。

```javascript
const net = require('net');

const server = net.createServer(socket => {
    socket.on('data', data => {
        console.log(data + ' from ' + socket.remoteAddress + ':' + socket.remotePort);
        socket.write('server -> Repeating: ' + data);
    });

    socket.on('close', () => {
        console.log('client closed connection');
    });
}).listen(3000);

console.log('listening on port 3000');
```

```javascript
const net = require('net');

const client = net.connect('3000', 'localhost', () => {
    console.log('connected to server');
    client.write('Hello World!');
});

client.on('data', data => {
    console.log('client-> ' + data);
    client.destroy();
});

client.on('close', () => {
    console.log('client-> connection is closed');
});
```

そのまま動かしてみたら、以下のように通信が行えました。
1度データを送信すると、送信側は終了する挙動のようだったので、何度か送信をやってみています。

<blockquote class="twitter-tweet" data-conversation="none"><p lang="ja" dir="ltr">先ほどの例をそのまま動かしただけ、なのだけど、<br>Node.js で TCP！ <a href="https://t.co/osz9wifMfQ">pic.twitter.com/osz9wifMfQ</a></p>&mdash; you (@youtoy) <a href="https://twitter.com/youtoy/status/1467363779499655168?ref_src=twsrc%5Etfw">December 5, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

そういえば、●● over TCP という形のものを...（以降、省略）

## おわりに
既存のサンプルを動かしただけではありますが、プログラムをざっくり見てみると、Node.js ではかなりシンプルな実装で UDP や TCP の通信を扱えるんだな、というのを感じました。

たしか、「M5Stack系の UIFlow で、UDP を扱うブロックがあったな」とか思い浮かんだので、それとつないでみるとか、何かこれを使ったデバイス/アプリ/サービス間通信を試せたら、とか思っています。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">たしかあったよな...、と思って確認したら、やはり！<a href="https://twitter.com/hashtag/UIFlow?src=hash&amp;ref_src=twsrc%5Etfw">#UIFlow</a> でソケット通信カテゴリと、その中の UDP。<a href="https://twitter.com/hashtag/M5Stack?src=hash&amp;ref_src=twsrc%5Etfw">#M5Stack</a> <a href="https://t.co/c2fYvyB70D">pic.twitter.com/c2fYvyB70D</a></p>&mdash; you (@youtoy) <a href="https://twitter.com/youtoy/status/1467369023335956480?ref_src=twsrc%5Etfw">December 5, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>