Ubuntu 16.04でPM2とNginxを使用してNode.js TCPサーバーアプリケーションを開発する方法

_著者はhttps://www.brightfunds.org/organizations/open-sourcing-mental-illness-ltd[OSMI]を選択して、https://do.co/w4do-cta [Donationsの書き込み]の一部として寄付を受け取りました]プログラム。

前書き

https://nodejs.org [Node.js]は、ChromeのV8 Javascriptエンジン上に構築された人気のあるオープンソースJavaScriptランタイム環境です。 Node.jsは、サーバーサイドおよびネットワークアプリケーションの構築に使用されます。 TCPサーバーはTCP接続要求を受け入れることができ、接続が確立されると、両側でデータストリームを交換できます。

このチュートリアルでは、基本的なNode.js TCPサーバーと、サーバーをテストするクライアントを作成します。 PM2と呼ばれる強力なNode.jsプロセスマネージャーを使用して、サーバーをバックグラウンドプロセスとして実行します。 次に、https://nginx.org/ [Nginx]をTCPアプリケーションのリバースプロキシとして構成し、ローカルマシンからのクライアントサーバー接続をテストします。

前提条件

このチュートリアルを完了するには、次のものが必要です。

手順1-Node.js TCPアプリケーションの作成

TCPソケットを使用してNode.jsアプリケーションを作成します。 これは、Node.jsのhttps://nodejs.org/api/net.html[Net]ライブラリを理解するのに役立つサンプルアプリケーションであり、未加工のTCPサーバーおよびクライアントアプリケーションを作成できます。

まず、サーバー上にNode.jsアプリケーションを配置するディレクトリを作成します。 このチュートリアルでは、 `+〜/ tcp-node js-app`ディレクトリにアプリケーションを作成します:

mkdir ~/tcp-nodejs-app

次に、新しいディレクトリに切り替えます。

cd ~/tcp-nodejs-app

プロジェクト用に「+ package.json」という名前の新しいファイルを作成します。 このファイルには、アプリケーションが依存するパッケージがリストされています。 このファイルを作成すると、この依存関係のリストを他の開発者と共有しやすくなるため、ビルドの再現性が高まります。

nano package.json

`+ npm init `コマンドを使用して ` package.json +`を生成することもできます。これにより、アプリケーションの詳細を入力するように求められますが、スタートアップを含む追加のピースを追加するには、ファイルを手動で変更する必要がありますコマンド。 したがって、このチュートリアルでは手動でファイルを作成します。

次のJSONをファイルに追加します。これは、アプリケーションの名前、バージョン、メインファイル、アプリケーションを起動するコマンド、およびソフトウェアライセンスを指定します。

package.json

{
 "name": "tcp-nodejs-app",
 "version": "1.0.0",
 "main": "server.js",
 "scripts": {
   "start": "node server.js"
 },
 "license": "MIT"
}

`+ scripts `フィールドでは、アプリケーションのコマンドを定義できます。 ここで指定した設定により、「 node server.js 」を実行する代わりに「 npm start +」を実行してアプリを実行できます。

`+ package.json +`ファイルにはランタイムと開発の依存関係のリストを含めることもできますが、このアプリケーションにはサードパーティの依存関係はありません。

プロジェクトディレクトリと `+ package.json`のセットアップができたので、サーバーを作成しましょう。

アプリケーションディレクトリで、 `+ server.js +`ファイルを作成します。

nano server.js

Node.jsは、TCPサーバーとクライアントの通信を可能にする「+ net 」というモジュールを提供します。 ` require()`で ` net +`モジュールをロードし、サーバーのポートとホストを保持する変数を定義します。

server.js

const net = require('net');
const port = 7070;
const host = '127.0.0.1';

このアプリにはポート「7070」を使用しますが、使用可能な任意のポートを使用できます。 `+ HOST `に ` 127.0.0.1 +`を使用しているため、サーバーはローカルネットワークインターフェースのみをリッスンします。 後でNginxをこのアプリの前にリバースプロキシとして配置します。 Nginxは、複数の接続と水平スケーリングの処理に精通しています。

次に、このコードを追加して、 `+ net `モジュールの ` createServer()`関数を使用してTCPサーバーを生成します。 次に、 ` net `モジュールの ` listen()+`関数を使用して、定義したポートとホストで接続のリッスンを開始します。

server.js

...
const server = net.createServer();
server.listen(port, host, () => {
   console.log('TCP Server is running on port ' + port +'.');
});

`+ server.js +`を保存してサーバーを起動します:

npm start

次の出力が表示されます。

OutputTCP Server is running on port 7070

TCPサーバーはポート `+ 7070 `で実行されています。 サーバーを停止するには、 ` CTRL + C +`を押します。

サーバーがリッスンしていることがわかったので、クライアント接続を処理するコードを記述しましょう。

クライアントがサーバーに接続すると、サーバーは「+ connection 」イベントをトリガーします。これを確認します。 接続されたクライアントの配列を定義し、これを「 sockets +」と呼び、クライアントが接続したときにこの配列に各クライアントインスタンスを追加します。

「+ data 」イベントを使用して、接続されたクライアントからのデータストリームを処理し、「 sockets +」配列を使用して、接続されたすべてのクライアントにデータをブロードキャストします。

これらの機能を実装するには、このコードを `+ server.js +`ファイルに追加します。

server.js

...

let sockets = [];

server.on('connection', function(sock) {
   console.log('CONNECTED: ' + sock.remoteAddress + ':' + sock.remotePort);
   sockets.push(sock);

   sock.on('data', function(data) {
       console.log('DATA ' + sock.remoteAddress + ': ' + data);
       // Write the data back to all the connected, the client will receive it as data from the server
       sockets.forEach(function(sock, index, array) {
           sock.write(sock.remoteAddress + ':' + sock.remotePort + " said " + data + '\n');
       });
   });
});

これは、接続されたクライアントから送信された「+ data 」イベントをリッスンするようサーバーに指示します。 接続されたクライアントがサーバーにデータを送信するとき、 ` sockets +`配列を反復処理することにより、接続されたすべてのクライアントにデータをエコーバックします。

次に、接続されたクライアントが接続を終了したときにトリガーされる「+ close 」イベントのハンドラーを追加します。 クライアントが切断するたびに、クライアントを ` sockets +`配列から削除して、ブロードキャストしないようにします。 接続ブロックの最後に次のコードを追加します。

server.js

let sockets = [];
server.on('connection', function(sock) {

   ...

   // Add a 'close' event handler to this instance of socket
   sock.on('close', function(data) {
       let index = sockets.findIndex(function(o) {
           return o.remoteAddress === sock.remoteAddress && o.remotePort === sock.remotePort;
       })
       if (index !== -1) sockets.splice(index, 1);
       console.log('CLOSED: ' + sock.remoteAddress + ' ' + sock.remotePort);
   });
});

`+ server.js`の完全なコードは次のとおりです。

server.js

const net = require('net');
const port = 7070;
const host = '127.0.0.1';

const server = net.createServer();
server.listen(port, host, () => {
   console.log('TCP Server is running on port ' + port + '.');
});

let sockets = [];

server.on('connection', function(sock) {
   console.log('CONNECTED: ' + sock.remoteAddress + ':' + sock.remotePort);
   sockets.push(sock);

   sock.on('data', function(data) {
       console.log('DATA ' + sock.remoteAddress + ': ' + data);
       // Write the data back to all the connected, the client will receive it as data from the server
       sockets.forEach(function(sock, index, array) {
           sock.write(sock.remoteAddress + ':' + sock.remotePort + " said " + data + '\n');
       });
   });

   // Add a 'close' event handler to this instance of socket
   sock.on('close', function(data) {
       let index = sockets.findIndex(function(o) {
           return o.remoteAddress === sock.remoteAddress && o.remotePort === sock.remotePort;
       })
       if (index !== -1) sockets.splice(index, 1);
       console.log('CLOSED: ' + sock.remoteAddress + ' ' + sock.remotePort);
   });
});

ファイルを保存してから、サーバーを再起動します。

npm start

マシン上で完全に機能するTCPサーバーが実行されています。 次に、サーバーに接続するクライアントを作成します。

ステップ2-Node.js TCPクライアントの作成

Node.js TCPサーバーが実行されているので、サーバーに接続してサーバーをテストするためのTCPクライアントを作成しましょう。

作成したNode.jsサーバーはまだ実行中で、現在のターミナルセッションをブロックしています。 クライアントを開発している間も実行を続けたいので、新しいターミナルウィンドウまたはタブを開きます。 次に、新しいタブからサーバーに再度接続します。

ssh @

接続したら、 `+ tcp-node js-app`ディレクトリに移動します:

cd tcp-nodejs-app

同じディレクトリに、 `+ client.js`という新しいファイルを作成します:

nano client.js

クライアントは、TCPサーバーに接続するために + server.js`ファイルで使用されているのと同じ + net + `ライブラリを使用します。 ポート `+ 7070 `でIPアドレス ` 127.0.0.1 +`を使用してサーバーに接続するには、このコードをファイルに追加します。

client.js

const net = require('net');
const client = new net.Socket();
const port = 7070;
const host = '127.0.0.1';

client.connect(port, host, function() {
   console.log('Connected');
   client.write("Hello From Client " + client.address().address);
});

このコードは、最初にTCPサーバーに接続して、作成したサーバーが実行されていることを確認します。 接続が確立されると、クライアントは + client.write a`関数を使用して、サーバーに + "Hello From Client" + client.address()。address`を送信します。 サーバーはこのデータを受信し、クライアントにエコーバックします。

クライアントがサーバーからデータを受信したら、サーバーの応答を印刷する必要があります。 次のコードを追加して、 `+ data +`イベントをキャッチし、サーバーの応答をコマンドラインに出力します。

client.js

client.on('data', function(data) {
   console.log('Server Says : ' + data);
});

最後に、次のコードを追加して、サーバーからの切断を適切に処理します。

client.js

client.on('close', function() {
   console.log('Connection closed');
});

`+ client.js +`ファイルを保存します。

次のコマンドを実行して、クライアントを起動します。

node client.js

接続が確立され、サーバーがデータを受信し、クライアントにエコーバックします。

client.js OutputConnected
Server Says : 127.0.0.1:34548 said Hello From Client 127.0.0.1

サーバーが実行されている端末に戻ると、次の出力が表示されます。

server.js OutputCONNECTED: 127.0.0.1:34550
DATA 127.0.0.1: Hello From Client 127.0.0.1

サーバーアプリとクライアントアプリの間にTCP接続を確立できることを確認しました。

サーバーを停止するには、 `+ CTRL + C `を押します。 次に、他のターミナルセッションに切り替え、 ` CTRL + C +`を押してクライアントを停止します。 これで、このターミナルセッションをサーバーから切断し、元のターミナルセッションに戻ることができます。

次のステップでは、PM2でサーバーを起動し、バックグラウンドで実行します。

ステップ3-PM2でサーバーを実行する

クライアント接続を受け入れる稼働中のサーバーがありますが、フォアグラウンドで実行されます。 PM2を使用してサーバーを実行して、バックグランドで実行し、正常に再起動できるようにします。

まず、 `+ npm +`を使用してPM2をサーバーにグローバルにインストールします。

sudo npm install pm2 -g

PM2をインストールしたら、それを使用してサーバーを実行します。 `+ npm start `を実行してサーバーを起動する代わりに、 ` pm2 +`コマンドを使用します。 サーバーを起動します。

pm2 start server.js

次のような出力が表示されます。

[secondary_label Output
[PM2] Spawning PM2 daemon with pm2_home=/home/sammy/.pm2
[PM2] PM2 Successfully daemonized
[PM2] Starting /home/sammy/tcp-nodejs-app/server.js in fork_mode (1 instance)
[PM2] Done.
┌────────┬──────┬────────┬───┬─────┬───────────┐
│ Name   │ mode │ status │ ↺ │ cpu │ memory    │
├────────┼──────┼────────┼───┼─────┼───────────┤
│ server │ fork │ online │ 0 │ 5%  │ 24.8 MB   │
└────────┴──────┴────────┴───┴─────┴───────────┘
Use `pm2 show <id|name>` to get more details about an app

サーバーは現在、バックグラウンドで実行されています。 ただし、マシンを再起動すると、マシンは動作しなくなるため、systemdサービスを作成してみましょう。

次のコマンドを実行して、PM2のsystemd起動スクリプトを生成およびインストールします。 systemdファイルが自動的にインストールされるように、これを必ず `+ sudo +`で実行してください。

sudo pm2 startup

次の出力が表示されます。

Output[PM2] Init System found: systemd
Platform systemd

...

[PM2] Writing init configuration in /etc/systemd/system/pm2-root.service
[PM2] Making script booting at startup...
[PM2] [-] Executing: systemctl enable pm2-root...
Created symlink from /etc/systemd/system/multi-user.target.wants/pm2-root.service to /etc/systemd/system/pm2-root.service.
[PM2] [v] Command successfully executed.
+---------------------------------------+
[PM2] Freeze a process list on reboot via:
$ pm2 save

[PM2] Remove init script via:
$ pm2 unstartup systemd

PM2はsystemdサービスとして実行されています。

`+ pm2 list`コマンドでPM2が管理しているすべてのプロセスをリストできます:

pm2 list

リストにアプリケーションが表示され、IDが「0」になります。

Output┌──────────┬────┬──────┬──────┬────────┬─────────┬────────┬─────┬───────────┬───────┬──────────┐
│ App name │ id │ mode │ pid  │ status │ restart │ uptime │ cpu │ mem       │ user  │ watching │
├──────────┼────┼──────┼──────┼────────┼─────────┼────────┼─────┼───────────┼───────┼──────────┤
│ server   │   │ fork │ 9075 │ online │ 0       │ 4m     │ 0%  │ 30.5 MB   │ sammy │ disabled │
└──────────┴────┴──────┴──────┴────────┴─────────┴────────┴─────┴───────────┴───────┴──────────┘

上記の出力では、「+ watching +」が無効になっていることがわかります。 これは、アプリケーションファイルに変更を加えたときにサーバーをリロードする機能です。 開発には役立ちますが、本番環境ではその機能は必要ありません。

実行中のプロセスに関する詳細情報を取得するには、 `+ pm2 show `コマンドに続けてそのIDを使用します。 この場合、IDは ` 0 +`です:

pm2 show

この出力は、稼働時間、ステータス、ログファイルパス、および実行中のアプリケーションに関するその他の情報を示しています。

OutputDescribing process with id  - name server
┌───────────────────┬──────────────────────────────────────────┐
│ status            │ online                                   │
│ name              │ server                                   │
│ restarts          │ 0                                        │
│ uptime            │ 7m                                       │
│ script path       │ /home/sammy/tcp-nodejs-app/server.js     │
│ script args       │ N/A                                      │
│ error log path    │ /home/sammy/.pm2/logs/server-error-0.log │
│ out log path      │ /home/sammy/.pm2/logs/server-out-0.log   │
│ pid path          │ /home/sammy/.pm2/pids/server-0.pid       │
│ interpreter       │ node                                     │
│ interpreter args  │ N/A                                      │
│ script id         │ 0                                        │
│ exec cwd          │ /home/sammy/tcp-nodejs-app               │
│ exec mode         │ fork_mode                                │
│ node.js version   │ 8.11.2                                   │
│ watch & reload    │ ✘                                        │
│ unstable restarts │ 0                                        │
│ created at        │ 2018-05-30T19:29:45.765Z                 │
└───────────────────┴──────────────────────────────────────────┘
Code metrics value
┌─────────────────┬────────┐
│ Loop delay      │ 1.12ms │
│ Active requests │ 0      │
│ Active handles  │ 3      │
└─────────────────┴────────┘
Add your own code metrics: http://bit.ly/code-metrics
Use `pm2 logs server [--lines 1000]` to display logs
Use `pm2 monit` to monitor CPU and Memory usage server

アプリケーションのステータスにエラーが表示されている場合は、*エラーログパス*を使用してエラーログを開いて確認し、エラーをデバッグできます。

cat /home/tcp/.pm2/logs/server-error-0.log

サーバーコードに変更を加えた場合、次のようにアプリケーションのプロセスを再起動して変更を適用する必要があります。

pm2 restart

PM2は現在、アプリケーションを管理しています。 次に、Nginxを使用してリクエストをサーバーにプロキシします。

手順4-Nginxをリバースプロキシサーバーとして設定する

アプリケーションは `+ 127.0.0.1 +`で実行され、リッスンしています。つまり、ローカルマシンからの接続のみを受け入れます。 Nginxをリバースプロキシとして設定し、着信トラフィックを処理してサーバーに転送します。

これを行うために、https://nginx.org/en/docs/stream/ngx_stream_core_module.html#stream [+ stream {} +]およびhttps://nginx.orgを使用するようにNginx設定を変更しますNode.jsサーバーにTCP接続を転送するためのNginxの/en/docs/stream/ngx_stream_proxy_module.html [+ stream_proxy +]機能。

メインのNginx設定ファイルを編集する必要があります。これは、TCP接続転送を設定する `+ stream `ブロックがトップレベルブロックとしてのみ機能するようにするためです。 UbuntuのデフォルトのNginx設定は、ファイルの ` http `ブロック内のサーバーブロックをロードし、 ` stream +`ブロックはそのブロック内に配置できません。

エディターでファイル「+ / etc / nginx / nginx.conf +」を開きます。

sudo nano /etc/nginx/nginx.conf

構成ファイルの最後に次の行を追加します。

/etc/nginx/nginx.conf

...

stream {
   server {
     listen 3000;
     proxy_pass 127.0.0.1:7070;
     proxy_protocol on;
   }
}

これは、ポート「3000」でTCP接続をリッスンし、ポート「7070」で実行されているNode.jsサーバーにリクエストをプロキシします。 アプリケーションが別のポートでリッスンするように設定されている場合は、プロキシパスURLポートを正しいポート番号に更新します。 `+ proxy_protocol +`ディレクティブは、クライアント情報をバックエンドサーバーに送信するためにhttps://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol/[PROXY protocol]を使用するようNginxに指示します。その後、必要に応じてその情報を処理できます。

ファイルを保存し、エディターを終了します。

Nginxの設定を確認して、構文エラーが発生していないことを確認してください。

sudo nginx -t

次に、Nginxを再起動して、TCPおよびUDPプロキシ機能を有効にします。

sudo systemctl restart nginx

次に、そのポートでサーバーへのTCP接続を許可します。 ポート `+ 3000 `での接続を許可するには、 ` ufw +`を使用します。

sudo sudo ufw allow 3000

Node.jsアプリケーションが実行中で、アプリケーションとNginxの構成が正しいと仮定すると、Nginxリバースプロキシ経由でアプリケーションにアクセスできるようになります。

手順5-クライアントサーバー接続のテスト

`+ client.js `スクリプトを使用してローカルマシンからTCPサーバーに接続して、サーバーをテストしてみましょう。 そのためには、開発した ` client.js`ファイルをローカルマシンにダウンロードし、スクリプトのポートとIPアドレスを変更する必要があります。

まず、ローカルマシンで、 + scp`を使用して + client.js`ファイルをダウンロードします。

[environment local
scp @:~/tcp-nodejs-app/client.js client.js

エディターで `+ client.js +`ファイルを開きます:

[environment local
nano client.js

+ port`を + 3000 + に変更し、 + host`をサーバーのIPアドレスに変更します。

client.js

// A Client Example to connect to the Node.js TCP Server
const net = require('net');
const client = new net.Socket();
const port = ;
const host = '';
...

ファイルを保存し、エディターを終了し、クライアントを実行してテストします。

node client.js

以前に実行したときと同じ出力が表示され、クライアントマシンがNginxを介して接続し、サーバーに到達したことが示されます。

client.js OutputConnected
Server Says : 127.0.0.1:34584 said PROXY TCP4   52920 3000
Hello From Client

Nginxはクライアント接続をサーバーにプロキシしているため、Node.jsサーバーにはクライアントの実際のIPアドレスが表示されません。 NginxのIPアドレスのみが表示されます。 Nginxは、セキュリティに影響を与える可能性のある変更をシステムに加えることなく、実際のIPアドレスを直接バックエンドに送信することをサポートしていませんが、NginxでPROXYプロトコルを有効にしたため、Node.jsサーバーは追加の「+ PROXY 」を受信して​​います実際のIPを含むメッセージ。 そのIPアドレスが必要な場合は、 ` PROXY +`リクエストを処理し、必要なデータを解析するようにサーバーを適合させることができます。

これで、Node.js TCPアプリケーションがNginxリバースプロキシの背後で実行され、サーバーをさらに開発し続けることができます。

結論

このチュートリアルでは、Node.jsでTCPアプリケーションを作成し、PM2で実行し、Nginxの背後で提供しました。 また、他のマシンから接続するクライアントアプリケーションを作成しました。 このアプリケーションを使用して、大量のデータストリームを処理したり、リアルタイムメッセージングアプリケーションを構築したりできます。