NodeJSのSocket.ioを用いたシンプルなWebSocketサーバの作成


# 初めに

API を用いた通信では，サーバにリクエストした処理が全て終了してからレスポンスが返される．そのため処理に時間がかかるプロセスを実行したい場合に，クライアント側がかなりの時間サーバからのレスポンスを待つ状態になってしまう．
リアルタイムに逐次的にサーバの実行結果を返してくれる通信方法には WebSocket があるので，リアルタイム通信の手段として nodejs の WebSocket を用いて実装した．

WebSocket の通信流れとしては

1. クライアントとサーバが Socket 接続を確立
2. 受け取ったサーバがサーバサイドで処理の実行
3. サーバがクラアントに処理結果を逐次的に送信
4. 処理が終了したら Socket 接続を切断

# 実装

開発環境は以下の通り．

- node 10.9.0
- npm 6.2.0

## ライブラリのインストール

WebSocket のライブラリは有名な socket.io を使う．
しかし socket.io はサーバサイド専用のライブラリなので，クライントとして用いることはできない．そのため Socket 通信のクラアントを作成する場合は`socket.io-client`ライブラリを使用する必要がある．

```sh:
$ npm install --save socket.io
$ npm install --save socket.io-client
```

socket.io ではサーバクライアント問わず原則，

- socket.on():待ち受け
- socket.emmit():送信

で通信を行う．

## Socket サーバの作成

処理を実行するサーバサイドを実装する．
初めに`io.on('connection')`でクライアントからのコネクションを待ち受ける．そして，クライアントから接続要求を受け取り，コネクションを開始する．ソケット通信中は，socket オブジェクトでクライアントからの処理のトリガーを待ち受け，トリガーを受け取ったら処理を開始する．サーバからクライアントに処理結果を返す場合は，`socket.emit()`にトリガー名を指定して実行し，クライアントに送信する．

```javascript:server.js
const io = require('socket.io')(8023);
const exec = require('child_process').exec;

var execCmd;

/*
 * サーバの接続
 */
io.on('connection', function ( socket ) {
  // コマンドの実行
  socket.on('exec', function ( command ) {
    execCmd = exec(command);
    console.log(execCmd.pid);


    execCmd.stdout.on('data',function(data) {// 実行中の出力を受け取る
      console.log(data);
      data = data.split(/\r\n|\n/);
      io.sockets.emit('response', {data:data});
    });

    execCmd.stderr.on('data', function (data) {// エラーの出力を受け取る
      console.log(data);
      data = data.split(/\r\n|\n/);
      io.sockets.emit('response', {data:data});
    });

    execCmd.on('exit', function (code) {// 処理が終了したことをクライアントに送信
      io.sockets.emit('exit', {data:code});
    });
  });
});

console.log('Start socket server : http://127.0.0.1:8023');

```

ソケット通信開始後に`exec`のトリガーを受け取ったら，`child_process`で nodejs からターミナルコマンドの実行をし，1 行出力されるたびにその出力結果を`response`トリガーでクライアントに返すというサーバを作成した．

## クラアント側の作成

今回は実験のために，1 秒ごとに現在の時間をターミナル出力する処理を 5 回繰り返すコマンドを作成し，それをサーバ側で実行してもらう．

```javascript
client.js
cconst io = require('socket.io-client');

// 1秒ごとに現在の時間をプリントするコマンド
var command = 'for i in `seq 1 5`; do date; sleep 1s; done';
var socket = io.connect('http://localhost:8023');//接続先のサーバを指定

console.log(command);

socket.on('connect' ,function (data) {//コネクションの接続
  socket.emit('exec',command,function(msg){//シェルコマンドを送る
    console.log(msg);
  });

  socket.on('response',function(msg){//サーバからのレスポンスを受け取る
    msg = msg['data'];
    console.log(msg);
  });

  socket.on('exit',function(msg){//終了を受け取ったらSocket通信を終了する
    console.log(msg);
    socket.disconnect()
  });
});

```

クライアントではサーバからの`socket.on('response')`トリガーを待ち受け，受け取ったレスポンスをクライアント側のターミナルに出力する．
重要なのは`socket.on('exit')`の部分．処理が終わったら Socket 通信を切断しなければ，通信しっぱなしになってしまうので，終了処理は必ず入れておく．

# 実行

初めに Socket サーバの起動

```sh:サーバ側
$ node server.js
Start socket server : http://127.0.0.1:8023
```

クライアントから Socket サーバに向けて処理の実行要求．

```sh:クライアント側
$ node client.js
```

実行結果は以下の通り．
![client.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/ef2d49b9-62b3-bf56-2e4e-b03bba888bd1.gif)

最初の出力は被ったが，概ね正常に 1 秒ごとに 5 回処理が繰り返され，接続が切断されたことが確認できた．

# 終わりに

処理に時間がかかるもの，　
API と WebSocket，用途によって使い分けられると良さそう．